1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-11 22:13:32 +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,4 +1,4 @@
<app-header [noTheme]="true"> </app-header>
<app-header [noTheme]="true"></app-header>
<div class="center-content">
<div class="content login-page">
<div class="logo-image"></div>
@@ -30,9 +30,9 @@
</form>
<p class="createAccountLink">
{{ "newAroundHere" | i18n }}
<a [routerLink]="registerRoute$ | async" (click)="setLoginEmailValues()">{{
"createAccount" | i18n
}}</a>
<a routerLink="/signup" (click)="setLoginEmailValues()">{{
"createAccount" | i18n
}}</a>
</p>
</div>
</div>

View File

@@ -1,19 +1,17 @@
// 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";
import { Subject, firstValueFrom, switchMap, takeUntil, tap } from "rxjs";
import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component";
import { LoginEmailServiceAbstraction, RegisterRouteService } from "@bitwarden/auth/common";
import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common";
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";
import { ToastService } from "@bitwarden/components";
import { AccountSwitcherService } from "./account-switching/services/account-switcher.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@Component({
selector: "app-home",
@@ -30,9 +28,6 @@ export class HomeComponent implements OnInit, OnDestroy {
rememberEmail: [false],
});
// TODO: remove when email verification flag is removed
registerRoute$ = this.registerRouteService.registerRoute$();
constructor(
protected platformUtilsService: PlatformUtilsService,
private formBuilder: FormBuilder,
@@ -40,10 +35,10 @@ export class HomeComponent implements OnInit, OnDestroy {
private i18nService: I18nService,
private loginEmailService: LoginEmailServiceAbstraction,
private accountSwitcherService: AccountSwitcherService,
private registerRouteService: RegisterRouteService,
private toastService: ToastService,
private configService: ConfigService,
private route: ActivatedRoute,
private logService: LogService,
) {}
async ngOnInit(): Promise<void> {
@@ -122,6 +117,15 @@ export class HomeComponent implements OnInit, OnDestroy {
}
async setLoginEmailValues() {
if (!this.formGroup.value.rememberEmail || !this.formGroup.value.email) {
this.logService.warning(
`Failed to setLoginEmailValues due to null value for either:
\n\tformGroup.value.rememberEmail: ${this.formGroup.value.rememberEmail}
\n\tformGroup.value.email: ${this.formGroup.value.email}`,
);
return;
}
// Note: Browser saves email settings here instead of the login component
this.loginEmailService.setRememberEmail(this.formGroup.value.rememberEmail);
await this.loginEmailService.setLoginEmail(this.formGroup.value.email);

View File

@@ -1,4 +1,4 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" [formGroup]="formGroup">
<form #form (ngSubmit)="submitLogin()" [appApiAction]="formPromise" [formGroup]="formGroup">
<header>
<h1 class="login-center">
<span class="title">{{ "logIn" | i18n }}</span>
@@ -60,7 +60,7 @@
<div class="content login-buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<span [hidden]="form.loading"
><b>{{ "logInWithMasterPassword" | i18n }}</b></span
><b>{{ "logInWithMasterPassword" | i18n }}</b></span
>
<i class="bwi bwi-spinner bwi-lg bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>

View File

@@ -10,7 +10,6 @@ import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstrac
import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
RegisterRouteService,
} from "@bitwarden/auth/common";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
@@ -52,7 +51,6 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit {
loginEmailService: LoginEmailServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction,
webAuthnLoginService: WebAuthnLoginServiceAbstraction,
registerRouteService: RegisterRouteService,
toastService: ToastService,
) {
super(
@@ -74,7 +72,6 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit {
loginEmailService,
ssoLoginService,
webAuthnLoginService,
registerRouteService,
toastService,
);
this.onSuccessfulLogin = async () => {

View File

@@ -607,7 +607,7 @@ const routes: Routes = [
children: [
{
path: "signup",
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
canActivate: [unauthGuardFn()],
data: {
elevation: 1,
pageIcon: RegistrationUserAddIcon,
@@ -633,7 +633,7 @@ const routes: Routes = [
},
{
path: "finish-signup",
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
canActivate: [unauthGuardFn()],
data: {
pageIcon: RegistrationLockAltIcon,
elevation: 1,
@@ -679,7 +679,6 @@ const routes: Routes = [
children: [
{
path: "set-password-jit",
canActivate: [canAccessFeature(FeatureFlag.EmailVerification)],
component: SetPasswordJitComponent,
data: {
pageTitle: {

View File

@@ -304,7 +304,7 @@ const routes: Routes = [
children: [
{
path: "signup",
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
canActivate: [unauthGuardFn()],
data: {
pageIcon: RegistrationUserAddIcon,
pageTitle: {
@@ -328,7 +328,7 @@ const routes: Routes = [
},
{
path: "finish-signup",
canActivate: [canAccessFeature(FeatureFlag.EmailVerification), unauthGuardFn()],
canActivate: [unauthGuardFn()],
data: {
pageIcon: RegistrationLockAltIcon,
} satisfies AnonLayoutWrapperData,
@@ -358,7 +358,6 @@ const routes: Routes = [
},
{
path: "set-password-jit",
canActivate: [canAccessFeature(FeatureFlag.EmailVerification)],
component: SetPasswordJitComponent,
data: {
pageTitle: {

View File

@@ -2,7 +2,7 @@
<form
id="login-page"
#form
(ngSubmit)="submit()"
(ngSubmit)="submitLogin()"
[appApiAction]="formPromise"
[formGroup]="formGroup"
attr.aria-hidden="{{ showingModal }}"
@@ -50,7 +50,7 @@
</div>
<div class="sub-options">
<p class="no-margin">{{ "newAroundHere" | i18n }}</p>
<button type="button" class="text text-primary" [routerLink]="registerRoute$ | async">
<button type="button" class="text text-primary" routerLink="/signup">
{{ "createAccount" | i18n }}
</button>
</div>
@@ -113,7 +113,7 @@
<div class="buttons-row">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<b [hidden]="form.loading"
><i class="bwi bwi-sign-in" aria-hidden="true"></i>
><i class="bwi bwi-sign-in" aria-hidden="true"></i>
{{ "loginWithMasterPassword" | i18n }}</b
>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>

View File

@@ -11,7 +11,6 @@ import { ModalService } from "@bitwarden/angular/services/modal.service";
import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
RegisterRouteService,
} from "@bitwarden/auth/common";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
@@ -78,7 +77,6 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDe
loginEmailService: LoginEmailServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction,
webAuthnLoginService: WebAuthnLoginServiceAbstraction,
registerRouteService: RegisterRouteService,
toastService: ToastService,
private configService: ConfigService,
) {
@@ -101,7 +99,6 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDe
loginEmailService,
ssoLoginService,
webAuthnLoginService,
registerRouteService,
toastService,
);
this.onSuccessfulLogin = () => {
@@ -210,7 +207,7 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDe
return;
}
await super.submit();
await super.submitLogin();
if (this.captchaSiteKey) {
const content = document.getElementById("content") as HTMLDivElement;
content.setAttribute("style", "width:335px");
@@ -228,7 +225,7 @@ export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDe
}
// Save off email for SSO
await this.ssoLoginService.setSsoEmail(this.formGroup.value.email);
await this.ssoLoginService.setSsoEmail(this.loggedEmail);
// Generate necessary sso params
const passwordOptions: any = {

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() {}
}

View File

@@ -1,26 +1,23 @@
// 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 { ApiService } from "@bitwarden/common/abstractions/api.service";
import { ProviderUserAcceptRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-accept.request";
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";
import { BaseAcceptComponent } from "@bitwarden/web-vault/app/common/base.accept.component";
import { ToastService } from "@bitwarden/components";
@Component({
selector: "app-accept-provider",
templateUrl: "accept-provider.component.html",
})
export class AcceptProviderComponent extends BaseAcceptComponent {
providerName: string;
providerId: string;
providerUserId: string;
providerInviteToken: string;
providerName!: string;
providerId!: string;
providerUserId!: string;
providerInviteToken!: string;
failedMessage = "providerInviteAcceptFailed";
@@ -32,10 +29,10 @@ export class AcceptProviderComponent extends BaseAcceptComponent {
route: ActivatedRoute,
authService: AuthService,
private apiService: ApiService,
private toastService: ToastService,
platformUtilService: PlatformUtilsService,
registerRouteService: RegisterRouteService,
) {
super(router, platformUtilService, i18nService, route, authService, registerRouteService);
super(router, platformUtilService, i18nService, route, authService);
}
async authedHandler(qParams: Params) {
@@ -47,12 +44,13 @@ export class AcceptProviderComponent extends BaseAcceptComponent {
qParams.providerUserId,
request,
);
this.platformUtilService.showToast(
"success",
this.i18nService.t("inviteAccepted"),
this.i18nService.t("providerInviteAcceptedDesc"),
{ timeout: 10000 },
);
this.toastService.showToast({
variant: "success",
title: this.i18nService.t("inviteAccepted"),
message: this.i18nService.t("providerInviteAcceptedDesc"),
});
this.router.navigate(["/vault"]);
}
@@ -64,25 +62,12 @@ export class AcceptProviderComponent 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,
providerUserId: this.providerUserId,
providerInviteToken: this.providerInviteToken,
};
}
await this.router.navigate([registerRoute], {
queryParams: queryParams,
},
});
}
}

View File

@@ -1,5 +1,3 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Directive, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { ActivatedRoute, NavigationSkipped, Router } from "@angular/router";
@@ -10,11 +8,9 @@ import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
PasswordLoginCredentials,
RegisterRouteService,
} from "@bitwarden/auth/common";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
@@ -38,17 +34,17 @@ import { CaptchaProtectedComponent } from "./captcha-protected.component";
@Directive()
export class LoginComponentV1 extends CaptchaProtectedComponent implements OnInit, OnDestroy {
@ViewChild("masterPasswordInput", { static: true }) masterPasswordInput: ElementRef;
@ViewChild("masterPasswordInput", { static: true }) masterPasswordInput!: ElementRef;
showPassword = false;
formPromise: Promise<AuthResult>;
formPromise!: Promise<AuthResult>;
onSuccessfulLogin: () => Promise<any>;
onSuccessfulLoginNavigate: (userId: UserId) => Promise<any>;
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
onSuccessfulLogin!: () => Promise<any>;
onSuccessfulLoginNavigate!: (userId: UserId) => Promise<any>;
onSuccessfulLoginTwoFactorNavigate!: () => Promise<any>;
onSuccessfulLoginForceResetNavigate!: () => Promise<any>;
showLoginWithDevice: boolean;
showLoginWithDevice!: boolean;
validatedEmail = false;
paramEmailSet = false;
@@ -56,7 +52,7 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
return this.formGroup.controls.email;
}
formGroup = this.formBuilder.group({
formGroup = this.formBuilder.nonNullable.group({
email: ["", [Validators.required, Validators.email]],
masterPassword: [
"",
@@ -67,16 +63,10 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
protected twoFactorRoute = "2fa";
protected successRoute = "vault";
// TODO: remove when email verification flag is removed
protected registerRoute$ = this.registerRouteService.registerRoute$();
protected forcePasswordResetRoute = "update-temp-password";
protected destroy$ = new Subject<void>();
get loggedEmail() {
return this.formGroup.value.email;
}
constructor(
protected devicesApiService: DevicesApiServiceAbstraction,
protected appIdService: AppIdService,
@@ -95,8 +85,6 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
protected route: ActivatedRoute,
protected loginEmailService: LoginEmailServiceAbstraction,
protected ssoLoginService: SsoLoginServiceAbstraction,
protected webAuthnLoginService: WebAuthnLoginServiceAbstraction,
protected registerRouteService: RegisterRouteService,
protected toastService: ToastService,
) {
super(environmentService, i18nService, platformUtilsService, toastService);
@@ -145,9 +133,7 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
this.destroy$.complete();
}
async submit(showToast = true) {
const data = this.formGroup.value;
async submitLogin(showToast = true) {
await this.setupCaptcha();
this.formGroup.markAllAsTouched();
@@ -170,10 +156,10 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
try {
const credentials = new PasswordLoginCredentials(
data.email,
data.masterPassword,
this.formGroup.controls.email.value,
this.formGroup.controls.masterPassword.value,
this.captchaToken,
null,
undefined,
);
this.formPromise = this.loginStrategyService.logIn(credentials);
@@ -231,12 +217,16 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
togglePassword() {
this.showPassword = !this.showPassword;
if (this.ngZone.isStable) {
document.getElementById("masterPassword").focus();
const masterPasswordElement = document.getElementById("masterPassword");
if (masterPasswordElement) {
if (this.ngZone.isStable) {
masterPasswordElement.focus();
} else {
this.ngZone.onStable.pipe(take(1)).subscribe(() => masterPasswordElement.focus());
}
} else {
this.ngZone.onStable
.pipe(take(1))
.subscribe(() => document.getElementById("masterPassword").focus());
this.logService.warning("Element with ID 'masterPassword' not found.");
}
}
@@ -254,7 +244,7 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
async launchSsoBrowser(clientId: string, ssoRedirectUri: string) {
// Save off email for SSO
await this.ssoLoginService.setSsoEmail(this.formGroup.value.email);
await this.ssoLoginService.setSsoEmail(this.formGroup.controls.email.value);
// Generate necessary sso params
const passwordOptions: any = {
@@ -295,12 +285,20 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
}
async validateEmail() {
const email = this.formGroup.get("email");
if (!email) {
this.logService.warning(
`No email in form group when attempting to ${this.validateEmail.name}`,
);
return;
}
this.formGroup.controls.email.markAsTouched();
const emailValid = this.formGroup.get("email").valid;
const emailValid = email.valid;
if (emailValid) {
this.toggleValidateEmail(true);
await this.getLoginWithDevice(this.loggedEmail);
await this.getLoginWithDevice(this.formGroup.controls["email"].value);
}
}
@@ -347,8 +345,8 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
}
protected async saveEmailSettings() {
this.loginEmailService.setLoginEmail(this.formGroup.value.email);
this.loginEmailService.setRememberEmail(this.formGroup.value.rememberEmail);
this.loginEmailService.setLoginEmail(this.formGroup.controls["email"].value);
this.loginEmailService.setRememberEmail(this.formGroup.controls["rememberEmail"].value);
await this.loginEmailService.saveEmailSettings();
}
@@ -366,8 +364,8 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
return true;
}
private getErrorToastMessage() {
const error: AllValidationErrors = this.formValidationErrorService
private getErrorToastMessage(): string {
const error: AllValidationErrors | undefined = this.formValidationErrorService
.getFormValidationErrors(this.formGroup.controls)
.shift();
@@ -380,9 +378,12 @@ export class LoginComponentV1 extends CaptchaProtectedComponent implements OnIni
default:
return this.i18nService.t(this.errorTag(error));
}
} else {
this.logService.warning(
"formValidationErrorService produced no errors even when the form is invalid.",
);
return this.i18nService.t("errorOccurred");
}
return;
}
private errorTag(error: AllValidationErrors): string {

View File

@@ -339,17 +339,9 @@ export class SsoComponent implements OnInit {
}
private async handleChangePasswordRequired(orgIdentifier: string) {
const emailVerification = await this.configService.getFeatureFlag(
FeatureFlag.EmailVerification,
);
if (emailVerification) {
this.changePasswordRoute = "set-password-jit";
}
await this.navigateViaCallbackOrRoute(
this.onSuccessfulLoginChangePasswordNavigate,
[this.changePasswordRoute],
["set-password-jit"],
{
queryParams: {
identifier: orgIdentifier,

View File

@@ -481,16 +481,7 @@ export class SsoComponent implements OnInit {
}
private async handleChangePasswordRequired(orgIdentifier: string) {
const emailVerification = await this.configService.getFeatureFlag(
FeatureFlag.EmailVerification,
);
let route = "set-password";
if (emailVerification) {
route = "set-password-jit";
}
await this.router.navigate([route], {
await this.router.navigate(["set-password-jit"], {
queryParams: {
identifier: orgIdentifier,
},

View File

@@ -45,7 +45,7 @@ export class ToastComponent {
message: string | string[];
/** An optional title to display over the message. */
@Input() title: string;
@Input() title: string | null;
/**
* The percent width of the progress bar, from 0-100

View File

@@ -20,6 +20,15 @@ export type ToastOptions = {
export class ToastService {
constructor(private toastrService: ToastrService) {}
/**
* This will present the toast to a user.
*
* Note: The toast will be displayed for a minimum of 5 seconds if no timeout is provided in
* the options.
*
* @param options Options for toasts. If no timeout is specified an appropriate duration will be
* calculated on the size of the message being displayed.
*/
showToast(options: ToastOptions): void {
const toastrConfig: Partial<IndividualConfig> = {
payload: {