1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +00:00

[PM-15154] Domain verification copy update (#12217)

* refactor: update domain verification terminology to claimed domains

* feat: add description for claimed domains in domain verification

* Add informational link to claimed domains description in domain verification component

* Update domain verification references to claimed domains in organization layout and SSO component

* Add validation message for invalid domain name format in domain verification

* Add domain claim messages and descriptions to localization files

* Update domain verification navigation text based on feature flag

* Update domain verification dialog to support account deprovisioning feature flag

* Update domain verification component to support account deprovisioning feature flag

* Refactor domain verification dialog to use synchronous feature flag for account deprovisioning

* Refactor domain verification routing to resolve title based on account deprovisioning feature flag

* Update SSO component to conditionally display domain verification link based on account deprovisioning feature flag

* Update event service to conditionally display domain verification messages based on account deprovisioning feature flag

* Update domain verification warning message

* Refactor domain verification navigation text handling based on account deprovisioning feature flag

* Refactor domain verification dialog to use observable for account deprovisioning feature flag

* Refactor domain verification component to use observable for account deprovisioning feature flag
This commit is contained in:
Rui Tomé
2024-12-11 14:47:49 +00:00
committed by GitHub
parent d4fcb5852a
commit b502e2bc25
11 changed files with 223 additions and 44 deletions

View File

@@ -6,24 +6,37 @@
<bit-dialog [dialogSize]="'default'" [disablePadding]="false">
<span bitDialogTitle>
<span *ngIf="!data.orgDomain">{{ "newDomain" | i18n }}</span>
<span *ngIf="data.orgDomain"> {{ "verifyDomain" | i18n }}</span>
<span *ngIf="data.orgDomain">
{{
((accountDeprovisioningEnabled$ | async) ? "claimDomain" : "verifyDomain") | i18n
}}</span
>
<span *ngIf="data.orgDomain" class="tw-text-xs tw-text-muted">{{
data.orgDomain.domainName
}}</span>
<span *ngIf="data?.orgDomain && !data.orgDomain?.verifiedDate" bitBadge variant="warning">{{
"domainStatusUnverified" | i18n
((accountDeprovisioningEnabled$ | async)
? "domainStatusUnderVerification"
: "domainStatusUnverified"
) | i18n
}}</span>
<span *ngIf="data?.orgDomain && data?.orgDomain?.verifiedDate" bitBadge variant="success">{{
"domainStatusVerified" | i18n
((accountDeprovisioningEnabled$ | async) ? "domainStatusClaimed" : "domainStatusVerified")
| i18n
}}</span>
</span>
<div bitDialogContent>
<bit-form-field>
<bit-label>{{ "domainName" | i18n }}</bit-label>
<input bitInput appAutofocus formControlName="domainName" [showErrorsWhenDisabled]="true" />
<bit-hint>{{ "domainNameInputHint" | i18n }}</bit-hint>
<bit-hint>{{
((accountDeprovisioningEnabled$ | async)
? "claimDomainNameInputHint"
: "domainNameInputHint"
) | i18n
}}</bit-hint>
</bit-form-field>
<bit-form-field *ngIf="data?.orgDomain">
@@ -42,18 +55,29 @@
<bit-callout
*ngIf="data?.orgDomain && !data?.orgDomain?.verifiedDate"
type="info"
title="{{ 'automaticDomainVerification' | i18n }}"
title="{{
(accountDeprovisioningEnabled$ | async)
? ('automaticClaimedDomains' | i18n | uppercase)
: ('automaticDomainVerification' | i18n)
}}"
>
{{ "automaticDomainVerificationProcess" | i18n }}
{{
((accountDeprovisioningEnabled$ | async)
? "automaticDomainClaimProcess"
: "automaticDomainVerificationProcess"
) | i18n
}}
</bit-callout>
</div>
<ng-container bitDialogFooter>
<button type="submit" bitButton bitFormButton buttonType="primary">
<span *ngIf="!data?.orgDomain">{{ "next" | i18n }}</span>
<span *ngIf="data?.orgDomain && !data?.orgDomain?.verifiedDate">{{
"verifyDomain" | i18n
((accountDeprovisioningEnabled$ | async) ? "claimDomain" : "verifyDomain") | i18n
}}</span>
<span *ngIf="data?.orgDomain?.verifiedDate">{{
((accountDeprovisioningEnabled$ | async) ? "reclaimDomain" : "reverifyDomain") | i18n
}}</span>
<span *ngIf="data?.orgDomain?.verifiedDate">{{ "reverifyDomain" | i18n }}</span>
</button>
<button bitButton buttonType="secondary" (click)="dialogRef.close()" type="button">
{{ "cancel" | i18n }}

View File

@@ -3,14 +3,16 @@
import { DialogRef, DIALOG_DATA } from "@angular/cdk/dialog";
import { Component, Inject, OnDestroy, OnInit } from "@angular/core";
import { FormBuilder, FormControl, FormGroup, ValidatorFn, Validators } from "@angular/forms";
import { Subject, takeUntil } from "rxjs";
import { Subject, takeUntil, Observable, firstValueFrom } from "rxjs";
import { OrgDomainApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain-api.service.abstraction";
import { OrgDomainServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization-domain/org-domain.service.abstraction";
import { OrganizationDomainResponse } from "@bitwarden/common/admin-console/abstractions/organization-domain/responses/organization-domain.response";
import { OrganizationDomainRequest } from "@bitwarden/common/admin-console/services/organization-domain/requests/organization-domain.request";
import { HttpStatusCode } from "@bitwarden/common/enums";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -31,20 +33,8 @@ export interface DomainAddEditDialogData {
export class DomainAddEditDialogComponent implements OnInit, OnDestroy {
private componentDestroyed$: Subject<void> = new Subject();
domainForm: FormGroup = this.formBuilder.group({
domainName: [
"",
[
Validators.required,
domainNameValidator(this.i18nService.t("invalidDomainNameMessage")),
uniqueInArrayValidator(
this.data.existingDomainNames,
this.i18nService.t("duplicateDomainError"),
),
],
],
txt: [{ value: null, disabled: true }],
});
accountDeprovisioningEnabled$: Observable<boolean>;
domainForm: FormGroup;
get domainNameCtrl(): FormControl {
return this.domainForm.controls.domainName as FormControl;
@@ -69,11 +59,34 @@ export class DomainAddEditDialogComponent implements OnInit, OnDestroy {
private validationService: ValidationService,
private dialogService: DialogService,
private toastService: ToastService,
) {}
private configService: ConfigService,
) {
this.accountDeprovisioningEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.AccountDeprovisioning,
);
}
// Angular Method Implementations
async ngOnInit(): Promise<void> {
this.domainForm = this.formBuilder.group({
domainName: [
"",
[
Validators.required,
domainNameValidator(
(await firstValueFrom(this.accountDeprovisioningEnabled$))
? this.i18nService.t("invalidDomainNameClaimMessage")
: this.i18nService.t("invalidDomainNameMessage"),
),
uniqueInArrayValidator(
this.data.existingDomainNames,
this.i18nService.t("duplicateDomainError"),
),
],
],
txt: [{ value: null, disabled: true }],
});
// If we have data.orgDomain, then editing, otherwise creating new domain
await this.populateForm();
}
@@ -211,13 +224,22 @@ export class DomainAddEditDialogComponent implements OnInit, OnDestroy {
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("domainVerified"),
message: this.i18nService.t(
(await firstValueFrom(this.accountDeprovisioningEnabled$))
? "domainClaimed"
: "domainVerified",
),
});
this.dialogRef.close();
} else {
this.domainNameCtrl.setErrors({
errorPassthrough: {
message: this.i18nService.t("domainNotVerified", this.domainNameCtrl.value),
message: this.i18nService.t(
(await firstValueFrom(this.accountDeprovisioningEnabled$))
? "domainNotClaimed"
: "domainNotVerified",
this.domainNameCtrl.value,
),
},
});
// For the case where user opens dialog and reverifies when domain name formControl disabled.

View File

@@ -4,6 +4,20 @@
</button>
</app-header>
<p class="tw-text-muted tw-w-1/3" *ngIf="accountDeprovisioningEnabled$ | async">
{{ "claimedDomainsDesc" | i18n }}
<a
bitLink
target="_blank"
rel="noreferrer"
appA11yTitle="{{ 'learnMore' | i18n }}"
href="https://bitwarden.com/help/claimed-accounts/"
slot="end"
>
<i class="bwi bwi-question-circle" aria-hidden="true"></i>
</a>
</p>
<ng-container *ngIf="loading">
<i
class="bwi bwi-spinner bwi-spin text-muted"
@@ -40,10 +54,16 @@
</td>
<td bitCell>
<span *ngIf="!orgDomain?.verifiedDate" bitBadge variant="warning">{{
"domainStatusUnverified" | i18n
((accountDeprovisioningEnabled$ | async)
? "domainStatusUnderVerification"
: "domainStatusUnverified"
) | i18n
}}</span>
<span *ngIf="orgDomain?.verifiedDate" bitBadge variant="success">{{
"domainStatusVerified" | i18n
((accountDeprovisioningEnabled$ | async)
? "domainStatusClaimed"
: "domainStatusVerified"
) | i18n
}}</span>
</td>
<td bitCell class="tw-text-muted">
@@ -70,7 +90,10 @@
type="button"
>
<i class="bwi bwi-fw bwi-check" aria-hidden="true"></i>
{{ "verifyDomain" | i18n }}
{{
((accountDeprovisioningEnabled$ | async) ? "claimDomain" : "verifyDomain")
| i18n
}}
</button>
<button bitMenuItem (click)="deleteDomain(orgDomain.id)" type="button">
<span class="tw-text-danger">

View File

@@ -43,6 +43,7 @@ export class DomainVerificationComponent implements OnInit, OnDestroy {
organizationId: string;
orgDomains$: Observable<OrganizationDomainResponse[]>;
accountDeprovisioningEnabled$: Observable<boolean>;
constructor(
private route: ActivatedRoute,
@@ -54,7 +55,11 @@ export class DomainVerificationComponent implements OnInit, OnDestroy {
private toastService: ToastService,
private configService: ConfigService,
private policyApiService: PolicyApiServiceAbstraction,
) {}
) {
this.accountDeprovisioningEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.AccountDeprovisioning,
);
}
// eslint-disable-next-line @typescript-eslint/no-empty-function
async ngOnInit() {
@@ -105,7 +110,7 @@ export class DomainVerificationComponent implements OnInit, OnDestroy {
organizationDomains.every((domain) => domain.verifiedDate === null)
) {
await this.dialogService.openSimpleDialog({
title: { key: "verified-domain-single-org-warning" },
title: { key: "claim-domain-single-org-warning" },
content: { key: "single-org-revoked-user-warning" },
cancelButtonText: { key: "cancel" },
acceptButtonText: { key: "confirm" },
@@ -169,13 +174,22 @@ export class DomainVerificationComponent implements OnInit, OnDestroy {
this.toastService.showToast({
variant: "success",
title: null,
message: this.i18nService.t("domainVerified"),
message: this.i18nService.t(
(await firstValueFrom(this.accountDeprovisioningEnabled$))
? "domainClaimed"
: "domainVerified",
),
});
} else {
this.toastService.showToast({
variant: "error",
title: null,
message: this.i18nService.t("domainNotVerified", domainName),
message: this.i18nService.t(
(await firstValueFrom(this.accountDeprovisioningEnabled$))
? "domainNotClaimed"
: "domainNotVerified",
domainName,
),
});
// Update this item so the last checked date gets updated.
await this.updateOrgDomain(orgDomainId);

View File

@@ -1,8 +1,10 @@
import { NgModule } from "@angular/core";
import { inject, NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { authGuard } from "@bitwarden/angular/auth/guards";
import { canAccessSettingsTab } from "@bitwarden/common/admin-console/abstractions/organization/organization.service.abstraction";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { isEnterpriseOrgGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/is-enterprise-org.guard";
import { organizationPermissionsGuard } from "@bitwarden/web-vault/app/admin-console/organizations/guards/org-permissions.guard";
import { OrganizationLayoutComponent } from "@bitwarden/web-vault/app/admin-console/organizations/layouts/organization-layout.component";
@@ -26,8 +28,13 @@ const routes: Routes = [
path: "domain-verification",
component: DomainVerificationComponent,
canActivate: [organizationPermissionsGuard((org) => org.canManageDomainVerification)],
data: {
titleId: "domainVerification",
resolve: {
titleId: async () => {
const configService = inject(ConfigService);
return (await configService.getFeatureFlag(FeatureFlag.AccountDeprovisioning))
? "claimedDomains"
: "domainVerification";
},
},
},
{

View File

@@ -31,7 +31,10 @@
<input bitInput type="text" formControlName="ssoIdentifier" />
<bit-hint>
{{ "ssoIdentifierHintPartOne" | i18n }}
<a bitLink routerLink="../domain-verification">{{ "domainVerification" | i18n }}</a>
<a bitLink routerLink="../domain-verification">{{
((accountDeprovisioningEnabled$ | async) ? "claimedDomains" : "domainVerification")
| i18n
}}</a>
</bit-hint>
</bit-form-field>

View File

@@ -9,7 +9,7 @@ import {
Validators,
} from "@angular/forms";
import { ActivatedRoute } from "@angular/router";
import { concatMap, Subject, takeUntil } from "rxjs";
import { concatMap, Observable, Subject, takeUntil } from "rxjs";
import { ControlsOf } from "@bitwarden/angular/types/controls-of";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -28,6 +28,7 @@ import { SsoConfigApi } from "@bitwarden/common/auth/models/api/sso-config.api";
import { OrganizationSsoRequest } from "@bitwarden/common/auth/models/request/organization-sso.request";
import { OrganizationSsoResponse } from "@bitwarden/common/auth/models/response/organization-sso.response";
import { SsoConfigView } from "@bitwarden/common/auth/models/view/sso-config.view";
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -185,6 +186,8 @@ export class SsoComponent implements OnInit, OnDestroy {
return this.ssoConfigForm?.controls?.configType as FormControl;
}
accountDeprovisioningEnabled$: Observable<boolean>;
constructor(
private formBuilder: FormBuilder,
private route: ActivatedRoute,
@@ -195,7 +198,11 @@ export class SsoComponent implements OnInit, OnDestroy {
private organizationApiService: OrganizationApiServiceAbstraction,
private configService: ConfigService,
private toastService: ToastService,
) {}
) {
this.accountDeprovisioningEnabled$ = this.configService.getFeatureFlag$(
FeatureFlag.AccountDeprovisioning,
);
}
async ngOnInit() {
this.enabledCtrl.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((enabled) => {