1
0
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:
Todd Martin
2022-10-28 14:54:55 -04:00
committed by GitHub
parent aa256b8a70
commit 2cd65939d5
38 changed files with 703 additions and 269 deletions

View File

@@ -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(

View File

@@ -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();
}
}

View File

@@ -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,