mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +00:00
Two-Step Login (#3852)
* [SG-163] Two step login flow web (#3648) * two step login flow * moved code from old branch and reafctored * fixed review comments * [SG-164] Two Step Login Flow - Browser (#3793) * Add new messages * Remove SSO button from home component * Change create account button to text * Add top padding to create account link * Add email input to HomeComponent * Add continue button to email input * Add form to home component * Retreive email from state service * Redirect to login after submit * Add error message for invalid email * Remove email input from login component * Remove loggingInTo from under MP input * Style the MP hint link * Add self hosted domain to email form * Made the mp hint link bold * Add the new login button * Style app-private-mode-warning in its component * Bitwarden -> Login text change * Remove the old login button * Cancel -> Close text change * Add avatar to login header * Login -> LoginWithMasterPassword text change * Add SSO button to login screen * Add not you button * Allow all clients to use the email query param on the login component * Introduct HomeGuard * Clear remembered email when clicking Not You * Make remember email opt-in * Use formGroup.patchValue instead of directly patching individual controls * [SG-165] Desktop login flow changes (#3814) * two step login flow * moved code from old branch and reafctored * fixed review comments * Make toggleValidateEmail in base class public * Add desktop login messages * Desktop login flow changes * Fix known device api error * Only submit if email has been validated * Clear remembered email when switching accounts * Fix merge issue * Add 'login with another device' button * Remove 'log in with another device' button for now * Pin login pag content to top instead of center justified * Leave email if 'Not you?' is clicked * Continue when enter is hit on email input Co-authored-by: gbubemismith <gsmithwalter@gmail.com> * [SG-750] and [SG-751] Web two step login bug fixes (#3843) * Continue when enter is hit on email input * Mark email input as touched on 'continue' so field is validated * disable login with device on self-hosted (#3895) * [SG-753] Keep email after hint component is launched in browser (#3883) * Keep email after hint component is launched in browser * Use query params instead of state for consistency * Send email and rememberEmail to home component on navigation (#3897) * removed avatar and close button from the password screen (#3901) * [SG-781] Remove extra login page and remove rememberEmail code (#3902) * Remove browser home guard * Always remember email for browser * Remove login landing page button * [SG-782] Add login service to streamline login form data persistence (#3911) * Add login service and abstraction * Inject login service into apps * Inject and use new service in login component * Use service in hint component to prefill email * Add method in LoginService to clear service values * Add LoginService to two-factor component to clear values * make login.service variables private Co-authored-by: Gbubemi Smith <gsmith@bitwarden.com> Co-authored-by: Addison Beck <addisonbeck1@gmail.com> Co-authored-by: Robyn MacCallum <robyntmaccallum@gmail.com> Co-authored-by: gbubemismith <gsmithwalter@gmail.com>
This commit is contained in:
@@ -1,12 +1,15 @@
|
||||
import { Directive, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { LoginService } from "@bitwarden/common/abstractions/login.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { PasswordHintRequest } from "@bitwarden/common/models/request/password-hint.request";
|
||||
|
||||
export class HintComponent {
|
||||
@Directive()
|
||||
export class HintComponent implements OnInit {
|
||||
email = "";
|
||||
formPromise: Promise<any>;
|
||||
|
||||
@@ -18,9 +21,14 @@ export class HintComponent {
|
||||
protected i18nService: I18nService,
|
||||
protected apiService: ApiService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
private logService: LogService
|
||||
private logService: LogService,
|
||||
private loginService: LoginService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.email = this.loginService.getEmail() ?? "";
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.email == null || this.email === "") {
|
||||
this.platformUtilsService.showToast(
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Directive, NgZone, OnInit } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { take } from "rxjs/operators";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AppIdService } from "@bitwarden/common/abstractions/appId.service";
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
@@ -12,6 +14,7 @@ import {
|
||||
} from "@bitwarden/common/abstractions/formValidationErrors.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { LoginService } from "@bitwarden/common/abstractions/login.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
@@ -29,20 +32,30 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
onSuccessfulLoginNavigate: () => Promise<any>;
|
||||
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
|
||||
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
|
||||
selfHosted = false;
|
||||
private selfHosted = false;
|
||||
showLoginWithDevice: boolean;
|
||||
validatedEmail = false;
|
||||
paramEmailSet = false;
|
||||
|
||||
formGroup = this.formBuilder.group({
|
||||
email: ["", [Validators.required, Validators.email]],
|
||||
masterPassword: ["", [Validators.required, Validators.minLength(8)]],
|
||||
rememberEmail: [true],
|
||||
rememberEmail: [false],
|
||||
});
|
||||
|
||||
protected twoFactorRoute = "2fa";
|
||||
protected successRoute = "vault";
|
||||
protected forcePasswordResetRoute = "update-temp-password";
|
||||
protected alwaysRememberEmail = false;
|
||||
protected skipRememberEmail = false;
|
||||
|
||||
get loggedEmail() {
|
||||
return this.formGroup.value.email;
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected apiService: ApiService,
|
||||
protected appIdService: AppIdService,
|
||||
protected authService: AuthService,
|
||||
protected router: Router,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
@@ -54,7 +67,9 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
protected logService: LogService,
|
||||
protected ngZone: NgZone,
|
||||
protected formBuilder: FormBuilder,
|
||||
protected formValidationErrorService: FormValidationErrorsService
|
||||
protected formValidationErrorService: FormValidationErrorsService,
|
||||
protected route: ActivatedRoute,
|
||||
protected loginService: LoginService
|
||||
) {
|
||||
super(environmentService, i18nService, platformUtilsService);
|
||||
this.selfHosted = platformUtilsService.isSelfHost();
|
||||
@@ -65,19 +80,35 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
let email = this.formGroup.value.email;
|
||||
this.route?.queryParams.subscribe((params) => {
|
||||
if (params != null) {
|
||||
const queryParamsEmail = params["email"];
|
||||
if (queryParamsEmail != null && queryParamsEmail.indexOf("@") > -1) {
|
||||
this.formGroup.get("email").setValue(queryParamsEmail);
|
||||
this.paramEmailSet = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
let email = this.loginService.getEmail();
|
||||
|
||||
if (email == null || email === "") {
|
||||
email = await this.stateService.getRememberedEmail();
|
||||
this.formGroup.get("email")?.setValue(email);
|
||||
}
|
||||
|
||||
if (email == null) {
|
||||
this.formGroup.get("email")?.setValue("");
|
||||
}
|
||||
if (!this.paramEmailSet) {
|
||||
this.formGroup.get("email")?.setValue(email ?? "");
|
||||
}
|
||||
if (!this.alwaysRememberEmail) {
|
||||
const rememberEmail = (await this.stateService.getRememberedEmail()) != null;
|
||||
let rememberEmail = this.loginService.getRememberEmail();
|
||||
if (rememberEmail == null) {
|
||||
rememberEmail = (await this.stateService.getRememberedEmail()) != null;
|
||||
}
|
||||
this.formGroup.get("rememberEmail")?.setValue(rememberEmail);
|
||||
}
|
||||
|
||||
if (email) {
|
||||
this.validateEmail();
|
||||
}
|
||||
}
|
||||
|
||||
async submit(showToast = true) {
|
||||
@@ -108,6 +139,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
);
|
||||
this.formPromise = this.authService.logIn(credentials);
|
||||
const response = await this.formPromise;
|
||||
this.setFormValues();
|
||||
if (data.rememberEmail || this.alwaysRememberEmail) {
|
||||
await this.stateService.setRememberedEmail(data.email);
|
||||
} else {
|
||||
@@ -130,6 +162,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
} else {
|
||||
const disableFavicon = await this.stateService.getDisableFavicon();
|
||||
await this.stateService.setDisableFavicon(!!disableFavicon);
|
||||
this.loginService.clearValues();
|
||||
if (this.onSuccessfulLogin != null) {
|
||||
this.onSuccessfulLogin();
|
||||
}
|
||||
@@ -191,6 +224,25 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
);
|
||||
}
|
||||
|
||||
async validateEmail() {
|
||||
this.formGroup.controls.email.markAsTouched();
|
||||
const emailInvalid = this.formGroup.get("email").invalid;
|
||||
if (!emailInvalid) {
|
||||
this.toggleValidateEmail(true);
|
||||
await this.getLoginWithDevice(this.loggedEmail);
|
||||
}
|
||||
}
|
||||
|
||||
toggleValidateEmail(value: boolean) {
|
||||
this.validatedEmail = value;
|
||||
this.formGroup.controls.masterPassword.reset();
|
||||
}
|
||||
|
||||
setFormValues() {
|
||||
this.loginService.setEmail(this.formGroup.value.email);
|
||||
this.loginService.setRememberEmail(this.formGroup.value.rememberEmail);
|
||||
}
|
||||
|
||||
private getErrorToastMessage() {
|
||||
const error: AllValidationErrors = this.formValidationErrorService
|
||||
.getFormValidationErrors(this.formGroup.controls)
|
||||
@@ -213,8 +265,19 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
return `${error.controlName}${name}`;
|
||||
}
|
||||
|
||||
private async getLoginWithDevice(email: string) {
|
||||
try {
|
||||
const deviceIdentifier = await this.appIdService.getAppId();
|
||||
const res = await this.apiService.getKnownDevice(email, deviceIdentifier);
|
||||
//ensure the application is not self-hosted
|
||||
this.showLoginWithDevice = res && !this.selfHosted;
|
||||
} catch (e) {
|
||||
this.showLoginWithDevice = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected focusInput() {
|
||||
const email = this.formGroup.value.email;
|
||||
const email = this.loggedEmail;
|
||||
document.getElementById(email == null || email === "" ? "email" : "masterPassword").focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { LoginService } from "@bitwarden/common/abstractions/login.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service";
|
||||
@@ -59,7 +60,8 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||
protected route: ActivatedRoute,
|
||||
protected logService: LogService,
|
||||
protected twoFactorService: TwoFactorService,
|
||||
protected appIdService: AppIdService
|
||||
protected appIdService: AppIdService,
|
||||
protected loginService: LoginService
|
||||
) {
|
||||
super(environmentService, i18nService, platformUtilsService);
|
||||
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
|
||||
@@ -204,6 +206,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||
return;
|
||||
}
|
||||
if (this.onSuccessfulLogin != null) {
|
||||
this.loginService.clearValues();
|
||||
this.onSuccessfulLogin();
|
||||
}
|
||||
if (response.resetMasterPassword) {
|
||||
@@ -213,8 +216,10 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
|
||||
this.successRoute = "update-temp-password";
|
||||
}
|
||||
if (this.onSuccessfulLoginNavigate != null) {
|
||||
this.loginService.clearValues();
|
||||
this.onSuccessfulLoginNavigate();
|
||||
} else {
|
||||
this.loginService.clearValues();
|
||||
this.router.navigate([this.successRoute], {
|
||||
queryParams: {
|
||||
identifier: this.identifier,
|
||||
|
||||
Reference in New Issue
Block a user