1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-20 11:24:07 +00:00

refactor(email-verification-feature-flag): [PM-7882] Email Verification

Lots of fixes and removal of the register route for signup in a lot of places.
This commit is contained in:
Patrick Pimentel
2024-12-27 16:05:33 -05:00
parent 1d874b447e
commit 5eb4cbbf5f
25 changed files with 162 additions and 249 deletions

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
@@ -14,7 +12,6 @@ import { OrganizationSponsorshipRedeemRequest } from "@bitwarden/common/admin-co
import { PreValidateSponsorshipResponse } from "@bitwarden/common/admin-console/models/response/pre-validate-sponsorship.response";
import { PlanSponsorshipType, PlanType, ProductTierType } from "@bitwarden/common/billing/enums";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService, ToastService } from "@bitwarden/components";
@@ -48,22 +45,20 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy {
loading = true;
badToken = false;
token: string;
existingFamilyOrganizations: Organization[];
existingFamilyOrganizations$: Observable<Organization[]>;
token!: string;
existingFamilyOrganizations$!: Observable<Organization[]>;
showNewOrganization = false;
_organizationPlansComponent: OrganizationPlansComponent;
preValidateSponsorshipResponse: PreValidateSponsorshipResponse;
preValidateSponsorshipResponse!: PreValidateSponsorshipResponse;
_selectedFamilyOrganizationId = "";
private _destroy = new Subject<void>();
formGroup = this.formBuilder.group({
selectedFamilyOrganizationId: ["", Validators.required],
});
constructor(
private router: Router,
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private route: ActivatedRoute,
private apiService: ApiService,
@@ -85,7 +80,6 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy {
variant: "error",
title: null,
message: this.i18nService.t("sponsoredFamiliesAcceptFailed"),
timeout: 10000,
});
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
@@ -131,7 +125,7 @@ export class FamiliesForEnterpriseSetupComponent implements OnInit, OnDestroy {
}
});
this.formGroup.valueChanges.pipe(takeUntil(this._destroy)).subscribe((val) => {
this.selectedFamilyOrganizationId = val.selectedFamilyOrganizationId;
this.selectedFamilyOrganizationId = val.selectedFamilyOrganizationId ?? "";
});
}

View File

@@ -1,10 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component } from "@angular/core";
import { ActivatedRoute, Params, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { RegisterRouteService } from "@bitwarden/auth/common";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -20,9 +16,9 @@ import { EmergencyAccessService } from "../services/emergency-access.service";
templateUrl: "accept-emergency.component.html",
})
export class AcceptEmergencyComponent extends BaseAcceptComponent {
name: string;
emergencyAccessId: string;
acceptEmergencyAccessInviteToken: string;
name!: string;
emergencyAccessId!: string;
acceptEmergencyAccessInviteToken!: string;
protected requiredParameters: string[] = ["id", "name", "email", "token"];
protected failedShortMessage = "emergencyInviteAcceptFailedShort";
@@ -34,10 +30,9 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent {
i18nService: I18nService,
route: ActivatedRoute,
authService: AuthService,
registerRouteService: RegisterRouteService,
private emergencyAccessService: EmergencyAccessService,
) {
super(router, platformUtilsService, i18nService, route, authService, registerRouteService);
super(router, platformUtilsService, i18nService, route, authService);
}
async authedHandler(qParams: Params): Promise<void> {
@@ -71,25 +66,12 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent {
}
async register() {
let queryParams: Params;
let registerRoute = await firstValueFrom(this.registerRoute$);
if (registerRoute === "/register") {
queryParams = {
email: this.email,
};
} else if (registerRoute === "/signup") {
// We have to override the base component route as we don't need users to
// complete email verification if they are coming directly an emailed invite.
registerRoute = "/finish-signup";
queryParams = {
await this.router.navigate(["/signup"], {
queryParams: {
email: this.email,
acceptEmergencyAccessInviteToken: this.acceptEmergencyAccessInviteToken,
emergencyAccessId: this.emergencyAccessId,
};
}
await this.router.navigate([registerRoute], {
queryParams: queryParams,
},
});
}
}

View File

@@ -3,7 +3,7 @@
import { Component, NgZone, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom, takeUntil } from "rxjs";
import { takeUntil } from "rxjs";
import { first } from "rxjs/operators";
import { LoginComponentV1 as BaseLoginComponent } from "@bitwarden/angular/auth/components/login-v1.component";
@@ -11,7 +11,6 @@ import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstrac
import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
RegisterRouteService,
} from "@bitwarden/auth/common";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
@@ -72,7 +71,6 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit {
loginEmailService: LoginEmailServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction,
webAuthnLoginService: WebAuthnLoginServiceAbstraction,
registerRouteService: RegisterRouteService,
toastService: ToastService,
) {
super(
@@ -94,7 +92,6 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit {
loginEmailService,
ssoLoginService,
webAuthnLoginService,
registerRouteService,
toastService,
);
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
@@ -105,7 +102,7 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit {
};
private async submitFormHelper(showToast: boolean) {
await super.submit(showToast);
await super.submitLogin(showToast);
}
async ngOnInit() {
@@ -178,17 +175,14 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit {
}
async goToRegister() {
// TODO: remove when email verification flag is removed
const registerRoute = await firstValueFrom(this.registerRoute$);
if (this.emailFormControl.valid) {
await this.router.navigate([registerRoute], {
await this.router.navigate(["/signup"], {
queryParams: { email: this.emailFormControl.value },
});
return;
}
await this.router.navigate([registerRoute]);
await this.router.navigate(["/signup"]);
}
protected override async handleMigrateEncryptionKey(result: AuthResult): Promise<boolean> {

View File

@@ -1,10 +1,6 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component } from "@angular/core";
import { ActivatedRoute, Params, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { RegisterRouteService } from "@bitwarden/auth/common";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -13,6 +9,7 @@ import { BaseAcceptComponent } from "../../common/base.accept.component";
import { AcceptOrganizationInviteService } from "./accept-organization.service";
import { OrganizationInvite } from "./organization-invite";
import { ToastService } from "@bitwarden/components";
@Component({
templateUrl: "accept-organization.component.html",
@@ -27,10 +24,10 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
i18nService: I18nService,
route: ActivatedRoute,
authService: AuthService,
registerRouteService: RegisterRouteService,
private toastService: ToastService,
private acceptOrganizationInviteService: AcceptOrganizationInviteService,
) {
super(router, platformUtilsService, i18nService, route, authService, registerRouteService);
super(router, platformUtilsService, i18nService, route, authService);
}
async authedHandler(qParams: Params): Promise<void> {
@@ -41,20 +38,20 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
return;
}
this.platformUtilService.showToast(
"success",
this.i18nService.t("inviteAccepted"),
invite.initOrganization
this.toastService.showToast({
variant: "success",
title: this.i18nService.t("inviteAccepted"),
message: invite.initOrganization
? this.i18nService.t("inviteInitAcceptedDesc")
: this.i18nService.t("inviteAcceptedDesc"),
{ timeout: 10000 },
);
});
await this.router.navigate(["/vault"]);
}
async unauthedHandler(qParams: Params): Promise<void> {
const invite = OrganizationInvite.fromParams(qParams);
await this.acceptOrganizationInviteService.setOrganizationInvitation(invite);
await this.accelerateInviteAcceptIfPossible(invite);
}
@@ -92,25 +89,10 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
// if SSO is disabled OR if sso is enabled but the SSO login required policy is not enabled
// then send user to create account
// TODO: update logic when email verification flag is removed
let queryParams: Params;
let registerRoute = await firstValueFrom(this.registerRoute$);
if (registerRoute === "/register") {
queryParams = {
fromOrgInvite: "true",
await this.router.navigate(["/signup"], {
queryParams: {
email: invite.email,
};
} else if (registerRoute === "/signup") {
// We have to override the base component route as we don't need users to complete email verification
// if they are coming directly from an emailed org invite.
registerRoute = "/finish-signup";
queryParams = {
email: invite.email,
};
}
await this.router.navigate([registerRoute], {
queryParams: queryParams,
},
});
return;
}

View File

@@ -21,7 +21,10 @@ export class OrganizationInvite {
return Object.assign(new OrganizationInvite(), json);
}
static fromParams(params: Params): OrganizationInvite | null {
static fromParams(params: Params | null): OrganizationInvite {
// Conversation topic:
// What if the query parameters we passed in are null? How do we outwardly communicate
// that this happened?
if (params == null) {
return null;
}

View File

@@ -100,7 +100,6 @@ export class AdjustStorageDialogComponent {
variant: "warning",
title: null,
message: this.i18nService.t("couldNotChargeCardPayInvoice"),
timeout: 10000,
});
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises

View File

@@ -1,30 +1,25 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, OnInit } from "@angular/core";
import { ActivatedRoute, Params, Router } from "@angular/router";
import { Subject, firstValueFrom } from "rxjs";
import { first, switchMap, takeUntil } from "rxjs/operators";
import { RegisterRouteService } from "@bitwarden/auth/common";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ToastService } from "@bitwarden/components";
@Directive()
export abstract class BaseAcceptComponent implements OnInit {
loading = true;
authed = false;
email: string;
actionPromise: Promise<any>;
email!: string;
actionPromise!: Promise<any>;
protected requiredParameters: string[] = [];
protected failedShortMessage = "inviteAcceptFailedShort";
protected failedMessage = "inviteAcceptFailed";
// TODO: remove when email verification flag is removed
registerRoute$ = this.registerRouteService.registerRoute$();
private destroy$ = new Subject<void>();
constructor(
@@ -33,10 +28,11 @@ export abstract class BaseAcceptComponent implements OnInit {
protected i18nService: I18nService,
protected route: ActivatedRoute,
protected authService: AuthService,
protected registerRouteService: RegisterRouteService,
protected toastService: ToastService,
) {}
abstract authedHandler(qParams: Params): Promise<void>;
abstract unauthedHandler(qParams: Params): Promise<void>;
async ngOnInit() {
@@ -47,7 +43,7 @@ export abstract class BaseAcceptComponent implements OnInit {
let error = this.requiredParameters.some(
(e) => qParams?.[e] == null || qParams[e] === "",
);
let errorMessage: string = null;
let errorMessage: string | null = null;
if (!error) {
this.email = qParams.email;
@@ -55,7 +51,7 @@ export abstract class BaseAcceptComponent implements OnInit {
if (status !== AuthenticationStatus.LoggedOut) {
try {
await this.authedHandler(qParams);
} catch (e) {
} catch (e: { message: string } | any) {
error = true;
errorMessage = e.message;
}
@@ -69,7 +65,13 @@ export abstract class BaseAcceptComponent implements OnInit {
errorMessage != null
? this.i18nService.t(this.failedShortMessage, errorMessage)
: this.i18nService.t(this.failedMessage);
this.platformUtilService.showToast("error", null, message, { timeout: 10000 });
this.toastService.showToast({
variant: "error",
title: null,
message,
});
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/"]);

View File

@@ -111,10 +111,7 @@ const routes: Routes = [
{
path: "register",
component: TrialInitiationComponent,
canActivate: [
canAccessFeature(FeatureFlag.EmailVerification, false, "/signup", false),
unauthGuardFn(),
],
canActivate: [unauthGuardFn()],
data: { titleId: "createAccount" } satisfies RouteDataProperties,
},
{
@@ -343,7 +340,7 @@ const routes: Routes = [
children: [
{
path: "signup",
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
canActivate: [unauthGuardFn()],
data: {
pageIcon: RegistrationUserAddIcon,
pageTitle: {
@@ -368,7 +365,7 @@ const routes: Routes = [
},
{
path: "finish-signup",
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
canActivate: [unauthGuardFn()],
data: {
pageIcon: RegistrationLockAltIcon,
titleId: "setAStrongPassword",
@@ -402,7 +399,6 @@ const routes: Routes = [
},
{
path: "set-password-jit",
canActivate: [canAccessFeature(FeatureFlag.EmailVerification)],
component: SetPasswordJitComponent,
data: {
pageTitle: {
@@ -415,7 +411,7 @@ const routes: Routes = [
},
{
path: "signup-link-expired",
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
canActivate: [unauthGuardFn()],
data: {
pageIcon: RegistrationExpiredLinkIcon,
pageTitle: {
@@ -673,7 +669,7 @@ const routes: Routes = [
},
{
path: "trial-initiation",
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
canActivate: [unauthGuardFn()],
component: CompleteTrialInitiationComponent,
resolve: {
pageTitle: freeTrialTextResolver,
@@ -684,7 +680,7 @@ const routes: Routes = [
},
{
path: "secrets-manager-trial-initiation",
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
canActivate: [unauthGuardFn()],
component: CompleteTrialInitiationComponent,
resolve: {
pageTitle: freeTrialTextResolver,

View File

@@ -1,13 +1,9 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { ActivatedRoute } from "@angular/router";
import { AnonLayoutWrapperDataService } from "@bitwarden/auth/angular";
import { RegisterRouteService } from "@bitwarden/auth/common";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
@@ -43,26 +39,23 @@ import { SendAccessTextComponent } from "./send-access-text.component";
})
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
export class AccessComponent implements OnInit {
protected send: SendAccessView;
protected send!: SendAccessView;
protected sendType = SendType;
protected loading = true;
protected passwordRequired = false;
protected formPromise: Promise<SendAccessResponse>;
protected password: string;
protected formPromise!: Promise<SendAccessResponse>;
protected password!: string;
protected unavailable = false;
protected error = false;
protected hideEmail = false;
protected decKey: SymmetricCryptoKey;
protected accessRequest: SendAccessRequest;
protected decKey!: SymmetricCryptoKey;
protected accessRequest!: SendAccessRequest;
protected expiredSendIcon = ExpiredSendIcon;
protected formGroup = this.formBuilder.group({});
// TODO: remove when email verification flag is removed
registerRoute$ = this.registerRouteService.registerRoute$();
private id: string;
private key: string;
private id!: string;
private key!: string;
constructor(
private cryptoFunctionService: CryptoFunctionService,
@@ -71,8 +64,6 @@ export class AccessComponent implements OnInit {
private sendApiService: SendApiService,
private toastService: ToastService,
private i18nService: I18nService,
private configService: ConfigService,
private registerRouteService: RegisterRouteService,
private layoutWrapperDataService: AnonLayoutWrapperDataService,
protected formBuilder: FormBuilder,
) {}
@@ -119,7 +110,7 @@ export class AccessComponent implements OnInit {
);
this.accessRequest.password = Utils.fromBufferToB64(passwordHash);
}
let sendResponse: SendAccessResponse = null;
let sendResponse: SendAccessResponse;
if (this.loading) {
sendResponse = await this.sendApiService.postSendAccess(this.id, this.accessRequest);
} else {

View File

@@ -7,12 +7,12 @@
href="https://www.bitwarden.com/products/send?source=web-vault"
target="_blank"
rel="noreferrer"
>Bitwarden Send</a
>Bitwarden Send</a
>
{{ "sendAccessTaglineOr" | i18n }}
<a bitLink [routerLink]="registerRoute$ | async" target="_blank" rel="noreferrer">{{
"sendAccessTaglineSignUp" | i18n
}}</a>
<a bitLink routerLink="/signup" target="_blank" rel="noreferrer">{{
"sendAccessTaglineSignUp" | i18n
}}</a>
{{ "sendAccessTaglineTryToday" | i18n }}
</p>
</div>

View File

@@ -1,7 +1,5 @@
import { Component } from "@angular/core";
import { RegisterRouteService } from "@bitwarden/auth/common";
import { SharedModule } from "../../shared";
@Component({
@@ -11,7 +9,5 @@ import { SharedModule } from "../../shared";
imports: [SharedModule],
})
export class SendAccessExplainerComponent {
// TODO: remove when email verification flag is removed
registerRoute$ = this.registerRouteService.registerRoute$();
constructor(private registerRouteService: RegisterRouteService) {}
constructor() {}
}