1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 07:43:35 +00:00

refactor(auth): [PM-9678] remove deprecated login components

Removes the V1 Login components and related UnauthenticatedExtensionUIRefresh
feature flag functions. Part of the authentication UI modernization efforts.

Closes PM-9678
This commit is contained in:
Alec Rippberger
2025-03-12 14:36:05 -05:00
committed by GitHub
parent e268055dc1
commit 4308cd8a9f
15 changed files with 12 additions and 1693 deletions

View File

@@ -1,36 +0,0 @@
<app-header [noTheme]="true"></app-header>
<div class="center-content">
<div class="content login-page">
<div class="logo-image"></div>
<p class="lead text-center">{{ "loginOrCreateNewAccount" | i18n }}</p>
<form #form [formGroup]="formGroup" (ngSubmit)="submit()">
<div class="box">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input id="email" type="email" formControlName="email" appInputVerbatim="false" />
</div>
<environment-selector></environment-selector>
<div class="remember-email-check">
<input
id="rememberEmail"
type="checkbox"
name="rememberEmail"
formControlName="rememberEmail"
/>
<label for="rememberEmail">{{ "rememberEmail" | i18n }}</label>
</div>
</div>
</div>
<div class="box">
<button type="submit" class="btn primary block">
<b>{{ "continue" | i18n }}</b>
</button>
</div>
</form>
<p class="createAccountLink">
{{ "newAroundHere" | i18n }}
<a routerLink="/signup" (click)="setLoginEmailValues()">{{ "createAccount" | i18n }}</a>
</p>
</div>
</div>

View File

@@ -1,130 +0,0 @@
// 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 } 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";
@Component({
selector: "app-home",
templateUrl: "home.component.html",
})
export class HomeComponent implements OnInit, OnDestroy {
@ViewChild(EnvironmentSelectorComponent, { static: true })
environmentSelector!: EnvironmentSelectorComponent;
private destroyed$: Subject<void> = new Subject();
loginInitiated = false;
formGroup = this.formBuilder.group({
email: ["", [Validators.required, Validators.email]],
rememberEmail: [false],
});
constructor(
protected platformUtilsService: PlatformUtilsService,
private formBuilder: FormBuilder,
private router: Router,
private i18nService: I18nService,
private loginEmailService: LoginEmailServiceAbstraction,
private accountSwitcherService: AccountSwitcherService,
private toastService: ToastService,
private configService: ConfigService,
private route: ActivatedRoute,
) {}
async ngOnInit(): Promise<void> {
this.listenForUnauthUiRefreshFlagChanges();
const email = await firstValueFrom(this.loginEmailService.loginEmail$);
const rememberEmail = this.loginEmailService.getRememberEmail();
if (email != null) {
this.formGroup.patchValue({ email, rememberEmail });
} else {
const storedEmail = await firstValueFrom(this.loginEmailService.storedEmail$);
if (storedEmail != null) {
this.formGroup.patchValue({ email: storedEmail, rememberEmail: true });
}
}
this.environmentSelector.onOpenSelfHostedSettings
.pipe(
switchMap(async () => {
await this.setLoginEmailValues();
await this.router.navigate(["environment"]);
}),
takeUntil(this.destroyed$),
)
.subscribe();
}
ngOnDestroy(): void {
this.destroyed$.next();
this.destroyed$.complete();
}
private listenForUnauthUiRefreshFlagChanges() {
this.configService
.getFeatureFlag$(FeatureFlag.UnauthenticatedExtensionUIRefresh)
.pipe(
tap(async (flag) => {
// If the flag is turned ON, we must force a reload to ensure the correct UI is shown
if (flag) {
const qParams = await firstValueFrom(this.route.queryParams);
const uniqueQueryParams = {
...qParams,
// adding a unique timestamp to the query params to force a reload
t: new Date().getTime().toString(),
};
await this.router.navigate(["/login"], {
queryParams: uniqueQueryParams,
});
}
}),
takeUntil(this.destroyed$),
)
.subscribe();
}
get availableAccounts$() {
return this.accountSwitcherService.availableAccounts$;
}
async submit() {
this.formGroup.markAllAsTouched();
if (this.formGroup.invalid) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccured"),
message: this.i18nService.t("invalidEmail"),
});
return;
}
await this.setLoginEmailValues();
await this.router.navigate(["login"], {
queryParams: { email: this.formGroup.controls.email.value },
});
}
async setLoginEmailValues() {
// Note: Browser saves email settings here instead of the login component
this.loginEmailService.setRememberEmail(this.formGroup.controls.rememberEmail.value);
await this.loginEmailService.setLoginEmail(this.formGroup.controls.email.value);
await this.loginEmailService.saveEmailSettings();
}
}

View File

@@ -1,81 +0,0 @@
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" [formGroup]="formGroup">
<header>
<h1 class="login-center">
<span class="title">{{ "logIn" | i18n }}</span>
</h1>
</header>
<main tabindex="-1">
<div class="box">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<input id="email" type="text" formControlName="email" [hidden]="true" />
<input
id="rememberEmail"
type="checkbox"
formControlName="rememberEmail"
[hidden]="true"
/>
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
class="monospaced"
formControlName="masterPassword"
appInputVerbatim
appAutofocus
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
(click)="togglePassword()"
[attr.aria-pressed]="showPassword"
>
<i
class="bwi bwi-lg"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
aria-hidden="true"
></i>
</button>
</div>
</div>
<div class="box-content-row" [hidden]="!showCaptcha()">
<iframe
id="hcaptcha_iframe"
height="80"
sandbox="allow-scripts allow-same-origin"
></iframe>
</div>
</div>
<div class="box-footer">
<button type="button" class="btn link" routerLink="/hint" (click)="saveEmailSettings()">
<b>{{ "getMasterPasswordHint" | i18n }}</b>
</button>
</div>
</div>
<div class="content login-buttons">
<button type="submit" class="btn primary block" [disabled]="form.loading">
<span [hidden]="form.loading"
><b>{{ "logInWithMasterPassword" | i18n }}</b></span
>
<i class="bwi bwi-spinner bwi-lg bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
<div class="tw-mb-3" *ngIf="showLoginWithDevice">
<button type="button" class="btn block" (click)="startAuthRequestLogin()">
<span> <i class="bwi bwi-mobile"></i> {{ "loginWithDevice" | i18n }} </span>
</button>
</div>
<button type="button" (click)="launchSsoBrowser()" class="btn block">
<i class="bwi bwi-provider" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
</button>
<div class="small">
<p class="no-margin">{{ "loggingInAs" | i18n }} {{ loggedEmail }}</p>
<a routerLink="/home">{{ "notYou" | i18n }}</a>
</div>
</div>
</main>
</form>

View File

@@ -1,142 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, NgZone, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { firstValueFrom } from "rxjs";
import { LoginComponentV1 as BaseLoginComponent } from "@bitwarden/angular/auth/components/login-v1.component";
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
} 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 { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
@Component({
selector: "app-login",
templateUrl: "login-v1.component.html",
})
export class LoginComponentV1 extends BaseLoginComponent implements OnInit {
constructor(
devicesApiService: DevicesApiServiceAbstraction,
appIdService: AppIdService,
loginStrategyService: LoginStrategyServiceAbstraction,
router: Router,
protected platformUtilsService: PlatformUtilsService,
protected i18nService: I18nService,
protected stateService: StateService,
protected environmentService: EnvironmentService,
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
protected cryptoFunctionService: CryptoFunctionService,
syncService: SyncService,
logService: LogService,
ngZone: NgZone,
formBuilder: FormBuilder,
formValidationErrorService: FormValidationErrorsService,
route: ActivatedRoute,
loginEmailService: LoginEmailServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction,
toastService: ToastService,
) {
super(
devicesApiService,
appIdService,
loginStrategyService,
router,
platformUtilsService,
i18nService,
stateService,
environmentService,
passwordGenerationService,
cryptoFunctionService,
logService,
ngZone,
formBuilder,
formValidationErrorService,
route,
loginEmailService,
ssoLoginService,
toastService,
);
this.onSuccessfulLogin = async () => {
await syncService.fullSync(true);
};
this.successRoute = "/tabs/vault";
}
async ngOnInit(): Promise<void> {
await super.ngOnInit();
await this.validateEmail();
}
settings() {
// 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(["environment"]);
}
async launchSsoBrowser() {
// Save off email for SSO
await this.ssoLoginService.setSsoEmail(this.formGroup.value.email);
// Generate necessary sso params
const passwordOptions: any = {
type: "password",
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
special: false,
};
const state =
(await this.passwordGenerationService.generatePassword(passwordOptions)) +
":clientId=browser";
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256");
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
await this.ssoLoginService.setCodeVerifier(codeVerifier);
await this.ssoLoginService.setSsoState(state);
const env = await firstValueFrom(this.environmentService.environment$);
let url = env.getWebVaultUrl();
if (url == null) {
url = "https://vault.bitwarden.com";
}
const redirectUri = url + "/sso-connector.html";
// Launch browser
this.platformUtilsService.launchUri(
url +
"/#/sso?clientId=browser" +
"&redirectUri=" +
encodeURIComponent(redirectUri) +
"&state=" +
state +
"&codeChallenge=" +
codeChallenge +
"&email=" +
encodeURIComponent(this.formGroup.controls.email.value),
);
}
async saveEmailSettings() {
// values should be saved on home component
return;
}
}

View File

@@ -22,9 +22,7 @@ import { CurrentAccountComponent } from "../auth/popup/account-switching/current
import { EnvironmentComponent } from "../auth/popup/environment.component";
import { ExtensionAnonLayoutWrapperComponent } from "../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component";
import { HintComponent } from "../auth/popup/hint.component";
import { HomeComponent } from "../auth/popup/home.component";
import { LoginDecryptionOptionsComponentV1 } from "../auth/popup/login-decryption-options/login-decryption-options-v1.component";
import { LoginComponentV1 } from "../auth/popup/login-v1.component";
import { LoginViaAuthRequestComponentV1 } from "../auth/popup/login-via-auth-request-v1.component";
import { RemovePasswordComponent } from "../auth/popup/remove-password.component";
import { SetPasswordComponent } from "../auth/popup/set-password.component";
@@ -98,9 +96,7 @@ import "../platform/popup/locales";
ColorPasswordCountPipe,
EnvironmentComponent,
HintComponent,
HomeComponent,
LoginViaAuthRequestComponentV1,
LoginComponentV1,
LoginDecryptionOptionsComponentV1,
SetPasswordComponent,
SsoComponentV1,

View File

@@ -1,157 +0,0 @@
<div id="login-page" class="page-top-padding">
<form
id="login-page"
#form
(ngSubmit)="submit()"
[appApiAction]="formPromise"
[formGroup]="formGroup"
attr.aria-hidden="{{ showingModal }}"
>
<div id="content" class="content" style="padding-top: 50px">
<a (click)="invalidateEmail()" class="tw-cursor-pointer">
<img class="logo-image" alt="Bitwarden" />
</a>
<p class="lead">{{ "loginOrCreateNewAccount" | i18n }}</p>
<!-- start email -->
<ng-container *ngIf="!validatedEmail; else loginPage">
<div class="box last">
<div class="box-content">
<div class="box-content-row" appBoxRow>
<label for="email">{{ "emailAddress" | i18n }}</label>
<input
id="email"
type="email"
formControlName="email"
appInputVerbatim="false"
(keyup.enter)="continue()"
/>
</div>
</div>
<environment-selector #environmentSelector (onOpenSelfHostedSettings)="settings()">
</environment-selector>
</div>
<div class="checkbox remember-email">
<label for="rememberEmail">
<input
id="rememberEmail"
type="checkbox"
name="rememberEmail"
formControlName="rememberEmail"
/>
{{ "rememberEmail" | i18n }}
</label>
</div>
<div class="buttons with-rows">
<div class="buttons-row">
<button type="button" class="btn primary block" (click)="continue()">
{{ "continue" | i18n }}
</button>
</div>
</div>
<div class="sub-options">
<p class="no-margin">{{ "newAroundHere" | i18n }}</p>
<button type="button" class="text text-primary" routerLink="/signup">
{{ "createAccount" | i18n }}
</button>
</div>
</ng-container>
<ng-template [formGroup]="formGroup" #loginPage>
<div class="box last">
<div class="box-content">
<div class="box-content-row box-content-row-flex" appBoxRow>
<div class="row-main">
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
<input
id="masterPassword"
type="{{ showPassword ? 'text' : 'password' }}"
class="monospaced"
formControlName="masterPassword"
appInputVerbatim
/>
</div>
<div class="action-buttons">
<button
type="button"
class="row-btn"
appStopClick
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
[attr.aria-pressed]="showPassword"
(click)="togglePassword()"
>
<i
class="bwi bwi-lg"
aria-hidden="true"
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
></i>
</button>
</div>
</div>
</div>
</div>
<div class="box last" [hidden]="!showCaptcha()">
<div class="box-content">
<iframe
id="hcaptcha_iframe"
style="margin-top: 20px"
sandbox="allow-scripts allow-same-origin"
></iframe>
<div class="box-content-row">
<button
class="btn block"
type="button"
routerLink="/accessibility-cookie"
(click)="saveEmailSettings()"
>
<i class="bwi bwi-universal-access" aria-hidden="true"></i>
{{ "loadAccessibilityCookie" | i18n }}
</button>
</div>
</div>
</div>
<div class="buttons with-rows">
<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>
{{ "loginWithMasterPassword" | i18n }}</b
>
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
</button>
</div>
<div class="buttons-row" *ngIf="showLoginWithDevice">
<button type="button" class="btn block" (click)="startAuthRequestLogin()">
<i class="bwi bwi-mobile" aria-hidden="true"></i>
{{ "logInWithAnotherDevice" | i18n }}
</button>
</div>
<div class="buttons-row">
<button
type="button"
(click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')"
class="btn block"
>
<i class="bwi bwi-provider" aria-hidden="true"></i>
{{ "enterpriseSingleSignOn" | i18n }}
</button>
</div>
</div>
<div class="sub-options">
<button
type="button"
class="text text-primary password-hint-btn"
routerLink="/hint"
(click)="saveEmailSettings()"
>
{{ "getMasterPasswordHint" | i18n }}
</button>
<div>
<p class="no-margin">{{ "loggingInAs" | i18n }} {{ loggedEmail }}</p>
<a [routerLink]="[]" (click)="toggleValidateEmail(false)">{{ "notYou" | i18n }}</a>
</div>
</div>
</ng-template>
</div>
</form>
</div>
<ng-template #environment></ng-template>

View File

@@ -1,266 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, NgZone, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { Subject, firstValueFrom, takeUntil, tap } from "rxjs";
import { LoginComponentV1 as BaseLoginComponent } from "@bitwarden/angular/auth/components/login-v1.component";
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
import { ModalService } from "@bitwarden/angular/services/modal.service";
import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
} 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 { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { EnvironmentComponent } from "../environment.component";
const BroadcasterSubscriptionId = "LoginComponent";
@Component({
selector: "app-login",
templateUrl: "login-v1.component.html",
})
export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDestroy {
@ViewChild("environment", { read: ViewContainerRef, static: true })
environmentModal: ViewContainerRef;
protected componentDestroyed$: Subject<void> = new Subject();
webVaultHostname = "";
showingModal = false;
private deferFocus: boolean = null;
get loggedEmail() {
return this.formGroup.value.email;
}
constructor(
devicesApiService: DevicesApiServiceAbstraction,
appIdService: AppIdService,
loginStrategyService: LoginStrategyServiceAbstraction,
router: Router,
i18nService: I18nService,
syncService: SyncService,
private modalService: ModalService,
platformUtilsService: PlatformUtilsService,
stateService: StateService,
environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationServiceAbstraction,
cryptoFunctionService: CryptoFunctionService,
private broadcasterService: BroadcasterService,
ngZone: NgZone,
private messagingService: MessagingService,
logService: LogService,
formBuilder: FormBuilder,
formValidationErrorService: FormValidationErrorsService,
route: ActivatedRoute,
loginEmailService: LoginEmailServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction,
toastService: ToastService,
private configService: ConfigService,
) {
super(
devicesApiService,
appIdService,
loginStrategyService,
router,
platformUtilsService,
i18nService,
stateService,
environmentService,
passwordGenerationService,
cryptoFunctionService,
logService,
ngZone,
formBuilder,
formValidationErrorService,
route,
loginEmailService,
ssoLoginService,
toastService,
);
this.onSuccessfulLogin = () => {
return syncService.fullSync(true);
};
}
async ngOnInit() {
this.listenForUnauthUiRefreshFlagChanges();
await super.ngOnInit();
await this.getLoginWithDevice(this.loggedEmail);
this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => {
this.ngZone.run(() => {
switch (message.command) {
case "windowHidden":
this.onWindowHidden();
break;
case "windowIsFocused":
if (this.deferFocus === null) {
this.deferFocus = !message.windowIsFocused;
if (!this.deferFocus) {
this.focusInput();
}
} else if (this.deferFocus && message.windowIsFocused) {
this.focusInput();
this.deferFocus = false;
}
break;
default:
}
});
});
this.messagingService.send("getWindowIsFocused");
}
ngOnDestroy() {
this.broadcasterService.unsubscribe(BroadcasterSubscriptionId);
this.componentDestroyed$.next();
this.componentDestroyed$.complete();
}
private listenForUnauthUiRefreshFlagChanges() {
this.configService
.getFeatureFlag$(FeatureFlag.UnauthenticatedExtensionUIRefresh)
.pipe(
tap(async (flag) => {
if (flag) {
const qParams = await firstValueFrom(this.route.queryParams);
const uniqueQueryParams = {
...qParams,
// adding a unique timestamp to the query params to force a reload
t: new Date().getTime().toString(),
};
await this.router.navigate(["/"], {
queryParams: uniqueQueryParams,
});
}
}),
takeUntil(this.componentDestroyed$),
)
.subscribe();
}
async settings() {
const [modal, childComponent] = await this.modalService.openViewRef(
EnvironmentComponent,
this.environmentModal,
);
modal.onShown.pipe(takeUntil(this.componentDestroyed$)).subscribe(() => {
this.showingModal = true;
});
modal.onClosed.pipe(takeUntil(this.componentDestroyed$)).subscribe(() => {
this.showingModal = false;
});
// eslint-disable-next-line rxjs/no-async-subscribe
childComponent.onSaved.pipe(takeUntil(this.componentDestroyed$)).subscribe(async () => {
modal.close();
await this.getLoginWithDevice(this.loggedEmail);
});
}
onWindowHidden() {
this.showPassword = false;
}
async continue() {
await super.validateEmail();
if (!this.formGroup.controls.email.valid) {
this.toastService.showToast({
variant: "error",
title: this.i18nService.t("errorOccured"),
message: this.i18nService.t("invalidEmail"),
});
return;
}
this.focusInput();
}
async submit() {
if (!this.validatedEmail) {
return;
}
await super.submit();
if (this.captchaSiteKey) {
const content = document.getElementById("content") as HTMLDivElement;
content.setAttribute("style", "width:335px");
}
}
private focusInput() {
const email = this.loggedEmail;
document.getElementById(email == null || email === "" ? "email" : "masterPassword")?.focus();
}
async launchSsoBrowser(clientId: string, ssoRedirectUri: string) {
if (!ipc.platform.isAppImage && !ipc.platform.isSnapStore && !ipc.platform.isDev) {
return super.launchSsoBrowser(clientId, ssoRedirectUri);
}
const email = this.formGroup.controls.email.value;
// Save off email for SSO
await this.ssoLoginService.setSsoEmail(email);
// Generate necessary sso params
const passwordOptions: any = {
type: "password",
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
special: false,
};
const state = await this.passwordGenerationService.generatePassword(passwordOptions);
const ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256");
const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
// Save sso params
await this.ssoLoginService.setSsoState(state);
await this.ssoLoginService.setCodeVerifier(ssoCodeVerifier);
try {
await ipc.platform.localhostCallbackService.openSsoPrompt(codeChallenge, state, email);
// FIXME: Remove when updating file. Eslint update
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (err) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccured"),
this.i18nService.t("ssoError"),
);
}
}
/**
* Force the validatedEmail flag to false, which will show the login page.
*/
invalidateEmail() {
this.validatedEmail = false;
}
}

View File

@@ -6,17 +6,15 @@ import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components
import { SharedModule } from "../../app/shared/shared.module";
import { LoginDecryptionOptionsComponentV1 } from "./login-decryption-options/login-decryption-options-v1.component";
import { LoginComponentV1 } from "./login-v1.component";
import { LoginViaAuthRequestComponentV1 } from "./login-via-auth-request-v1.component";
@NgModule({
imports: [SharedModule, RouterModule],
declarations: [
LoginComponentV1,
LoginViaAuthRequestComponentV1,
EnvironmentSelectorComponent,
LoginDecryptionOptionsComponentV1,
],
exports: [LoginComponentV1, LoginViaAuthRequestComponentV1],
exports: [LoginViaAuthRequestComponentV1],
})
export class LoginModule {}

View File

@@ -1,129 +0,0 @@
<form
[bitSubmit]="submitForm.bind(null, false)"
[appApiAction]="formPromise"
[formGroup]="formGroup"
>
<ng-container *ngIf="!validatedEmail">
<div class="tw-mb-3">
<bit-form-field>
<bit-label>{{ "emailAddress" | i18n }}</bit-label>
<input bitInput type="email" formControlName="email" appAutofocus />
</bit-form-field>
</div>
<div class="tw-mb-3 tw-flex tw-items-start">
<bit-form-control class="tw-mb-0">
<input type="checkbox" bitCheckbox formControlName="rememberEmail" />
<bit-label>{{ "rememberEmail" | i18n }}</bit-label>
</bit-form-control>
</div>
<div class="tw-mb-3">
<button
bitButton
type="submit"
buttonType="primary"
class="tw-w-full"
(click)="validateEmail()"
>
<span> {{ "continue" | i18n }} </span>
</button>
</div>
<div class="tw-mb-3 tw-flex tw-flex-col tw-items-center tw-justify-center">
<p class="tw-mb-3">{{ "or" | i18n }}</p>
<a
bitLink
block
linkType="primary"
routerLink="/login-with-passkey"
(mousedown)="$event.preventDefault()"
>
<span><i class="bwi bwi-passkey"></i> {{ "logInWithPasskey" | i18n }}</span>
</a>
</div>
<hr />
<p class="tw-m-0 tw-text-sm">
{{ "newAroundHere" | i18n }}
<!-- Two notes:
(1) We check the value and validity of email so we don't send an invalid email to autofill
on load of register for both enter and mouse based navigation
(2) We use mousedown to trigger navigation so that the onBlur form validation does not fire
and move the create account link down the page on click which causes the user to miss actually
clicking on the link. Mousedown fires before onBlur.
-->
<a
bitLink
routerLink="/signup"
[queryParams]="emailFormControl.valid ? { email: emailFormControl.value } : {}"
(mousedown)="goToRegister()"
>
{{ "createAccount" | i18n }}
</a>
</p>
</ng-container>
<div [ngClass]="{ 'tw-hidden': !validatedEmail }">
<div class="tw-mb-6 tw-h-28">
<bit-form-field class="!tw-mb-1">
<bit-label>{{ "masterPass" | i18n }}</bit-label>
<input type="password" bitInput #masterPasswordInput formControlName="masterPassword" />
<button type="button" bitSuffix bitIconButton bitPasswordInputToggle></button>
</bit-form-field>
<a
bitLink
class="tw-mt-2"
routerLink="/hint"
(mousedown)="goToHint()"
(click)="saveEmailSettings()"
>{{ "getMasterPasswordHint" | i18n }}</a
>
</div>
<div [hidden]="!showCaptcha()">
<iframe id="hcaptcha_iframe" height="80" sandbox="allow-scripts allow-same-origin"></iframe>
</div>
<div class="tw-mb-3 tw-flex tw-space-x-4">
<button bitButton buttonType="primary" bitFormButton type="submit" [block]="true">
<span> {{ "loginWithMasterPassword" | i18n }} </span>
</button>
</div>
<div class="tw-mb-3" *ngIf="showLoginWithDevice">
<button
bitButton
type="button"
[block]="true"
buttonType="secondary"
(click)="startAuthRequestLogin()"
>
<span> <i class="bwi bwi-mobile"></i> {{ "loginWithDevice" | i18n }} </span>
</button>
</div>
<div class="tw-mb-3">
<a
routerLink="/sso"
[queryParams]="{ email: formGroup.value.email }"
(click)="saveEmailSettings()"
bitButton
buttonType="secondary"
class="tw-w-full"
>
<i class="bwi bwi-provider tw-mr-2"></i>
{{ "enterpriseSingleSignOn" | i18n }}
</a>
</div>
<hr />
<div class="tw-m-0 tw-text-sm">
<p class="tw-mb-1">{{ "loggingInAs" | i18n }} {{ loggedEmail }}</p>
<a bitLink [routerLink]="[]" (click)="toggleValidateEmail(false)">{{ "notYou" | i18n }}</a>
</div>
</div>
</form>

View File

@@ -1,224 +0,0 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { Component, NgZone, OnInit } from "@angular/core";
import { FormBuilder } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { takeUntil } from "rxjs";
import { first } from "rxjs/operators";
import { LoginComponentV1 as BaseLoginComponent } from "@bitwarden/angular/auth/components/login-v1.component";
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
} 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";
import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { UserId } from "@bitwarden/common/types/guid";
import { ToastService } from "@bitwarden/components";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy";
import { RouterService } from "../../core";
import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service";
import { OrganizationInvite } from "../organization-invite/organization-invite";
@Component({
selector: "app-login",
templateUrl: "login-v1.component.html",
})
export class LoginComponentV1 extends BaseLoginComponent implements OnInit {
showResetPasswordAutoEnrollWarning = false;
enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions;
policies: Policy[];
constructor(
private acceptOrganizationInviteService: AcceptOrganizationInviteService,
devicesApiService: DevicesApiServiceAbstraction,
appIdService: AppIdService,
loginStrategyService: LoginStrategyServiceAbstraction,
router: Router,
i18nService: I18nService,
route: ActivatedRoute,
platformUtilsService: PlatformUtilsService,
environmentService: EnvironmentService,
passwordGenerationService: PasswordGenerationServiceAbstraction,
private passwordStrengthService: PasswordStrengthServiceAbstraction,
cryptoFunctionService: CryptoFunctionService,
private policyApiService: PolicyApiServiceAbstraction,
private policyService: InternalPolicyService,
logService: LogService,
ngZone: NgZone,
protected stateService: StateService,
private routerService: RouterService,
formBuilder: FormBuilder,
formValidationErrorService: FormValidationErrorsService,
loginEmailService: LoginEmailServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction,
toastService: ToastService,
) {
super(
devicesApiService,
appIdService,
loginStrategyService,
router,
platformUtilsService,
i18nService,
stateService,
environmentService,
passwordGenerationService,
cryptoFunctionService,
logService,
ngZone,
formBuilder,
formValidationErrorService,
route,
loginEmailService,
ssoLoginService,
toastService,
);
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
}
submitForm = async (showToast = true) => {
return await this.submitFormHelper(showToast);
};
private async submitFormHelper(showToast: boolean) {
await super.submit(showToast);
}
async ngOnInit() {
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
// If there is a query parameter called 'org', set previousUrl to `/create-organization?org=paramValue`
if (qParams.org != null) {
const route = this.router.createUrlTree(["create-organization"], {
queryParams: { plan: qParams.org },
});
this.routerService.setPreviousUrl(route.toString());
}
/**
* If there is a query parameter called 'sponsorshipToken', that means they are coming
* from an email for sponsoring a families organization. If so, then set the prevousUrl
* to `/setup/families-for-enterprise?token=paramValue`
*/
if (qParams.sponsorshipToken != null) {
const route = this.router.createUrlTree(["setup/families-for-enterprise"], {
queryParams: { token: qParams.sponsorshipToken },
});
this.routerService.setPreviousUrl(route.toString());
}
await super.ngOnInit();
});
// If there's an existing org invite, use it to get the password policies
const orgInvite = await this.acceptOrganizationInviteService.getOrganizationInvite();
if (orgInvite != null) {
await this.initPasswordPolicies(orgInvite);
}
}
async goAfterLogIn(userId: UserId) {
const masterPassword = this.formGroup.value.masterPassword;
// Check master password against policy
if (this.enforcedPasswordPolicyOptions != null) {
const strengthResult = this.passwordStrengthService.getPasswordStrength(
masterPassword,
this.formGroup.value.email,
);
const masterPasswordScore = strengthResult == null ? null : strengthResult.score;
// If invalid, save policies and require update
if (
!this.policyService.evaluateMasterPassword(
masterPasswordScore,
masterPassword,
this.enforcedPasswordPolicyOptions,
)
) {
const policiesData: { [id: string]: PolicyData } = {};
this.policies.map((p) => (policiesData[p.id] = PolicyData.fromPolicy(p)));
await this.policyService.replace(policiesData, userId);
await this.router.navigate(["update-password"]);
return;
}
}
this.loginEmailService.clearValues();
await this.router.navigate([this.successRoute]);
}
async goToHint() {
await this.saveEmailSettings();
await this.router.navigateByUrl("/hint");
}
async goToRegister() {
if (this.emailFormControl.valid) {
await this.router.navigate(["/signup"], {
queryParams: { email: this.emailFormControl.value },
});
return;
}
await this.router.navigate(["/signup"]);
}
protected override async handleMigrateEncryptionKey(result: AuthResult): Promise<boolean> {
if (!result.requiresEncryptionKeyMigration) {
return false;
}
await this.router.navigate(["migrate-legacy-encryption"]);
return true;
}
private async initPasswordPolicies(invite: OrganizationInvite): Promise<void> {
try {
this.policies = await this.policyApiService.getPoliciesByToken(
invite.organizationId,
invite.token,
invite.email,
invite.organizationUserId,
);
} catch (e) {
this.logService.error(e);
}
if (this.policies == null) {
return;
}
const resetPasswordPolicy = this.policyService.getResetPasswordPolicyOptions(
this.policies,
invite.organizationId,
);
// Set to true if policy enabled and auto-enroll enabled
this.showResetPasswordAutoEnrollWarning =
resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled;
this.policyService
.masterPasswordPolicyOptions$(this.policies)
.pipe(takeUntil(this.destroy$))
.subscribe((enforcedPasswordPolicyOptions) => {
this.enforcedPasswordPolicyOptions = enforcedPasswordPolicyOptions;
});
}
}

View File

@@ -5,20 +5,17 @@ import { CheckboxModule } from "@bitwarden/components";
import { SharedModule } from "../../../app/shared";
import { LoginDecryptionOptionsComponentV1 } from "./login-decryption-options/login-decryption-options-v1.component";
import { LoginComponentV1 } from "./login-v1.component";
import { LoginViaAuthRequestComponentV1 } from "./login-via-auth-request-v1.component";
import { LoginViaWebAuthnComponent } from "./login-via-webauthn/login-via-webauthn.component";
@NgModule({
imports: [SharedModule, CheckboxModule],
declarations: [
LoginComponentV1,
LoginViaAuthRequestComponentV1,
LoginDecryptionOptionsComponentV1,
LoginViaWebAuthnComponent,
],
exports: [
LoginComponentV1,
LoginViaAuthRequestComponentV1,
LoginDecryptionOptionsComponentV1,
LoginViaWebAuthnComponent,