mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 05:43:41 +00:00
[SG-168] Passwordless login web MVP (#3424)
* passwordless login page redesign * passwordless login page redesign * restyled login form to use tailwind * restyled login form to use tailwind * moved texts on login device template to locales * made reactive form changes for clients * added request model * made more changes * added implmentation to auth request api * fixed refrencing issue * renamed model property * Added resend notification functionality * Added new file * login with device first draft * login with device first draft * login with device first draft * login with device first draft * connection to anonymous hub * connection to anonymous hub * refactored confirm login response * removed comment * cleaned up login * changed uptyped form builder * changed uptyped form builder * [SG-168] Update login strategy with passwordless login credentials. * [SG-168] Removed logs. Changed inputs for passwordless logic strategy. Removed tokenRequestPasswordless it is using the same as password. * code cleanup * code cleanup * removed login with device from self hosted * fixed PR comments * added module for login * fixed post request bug * added feature flag * added feature flag * added feature flag Co-authored-by: André Bispo <abispo@bitwarden.com>
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
import { Directive, Input, NgZone, OnInit } from "@angular/core";
|
||||
import { Directive, NgZone, OnInit } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
import { take } from "rxjs/operators";
|
||||
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import {
|
||||
AllValidationErrors,
|
||||
FormValidationErrorsService,
|
||||
} from "@bitwarden/common/abstractions/formValidationErrors.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
@@ -18,16 +23,19 @@ import { CaptchaProtectedComponent } from "./captchaProtected.component";
|
||||
|
||||
@Directive()
|
||||
export class LoginComponent extends CaptchaProtectedComponent implements OnInit {
|
||||
@Input() email = "";
|
||||
@Input() rememberEmail = true;
|
||||
|
||||
masterPassword = "";
|
||||
showPassword = false;
|
||||
formPromise: Promise<AuthResult>;
|
||||
onSuccessfulLogin: () => Promise<any>;
|
||||
onSuccessfulLoginNavigate: () => Promise<any>;
|
||||
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
|
||||
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
|
||||
selfHosted = false;
|
||||
|
||||
formGroup = this.formBuilder.group({
|
||||
email: ["", [Validators.required, Validators.email]],
|
||||
masterPassword: ["", [Validators.required, Validators.minLength(8)]],
|
||||
rememberEmail: [true],
|
||||
});
|
||||
|
||||
protected twoFactorRoute = "2fa";
|
||||
protected successRoute = "vault";
|
||||
@@ -44,9 +52,12 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
protected passwordGenerationService: PasswordGenerationService,
|
||||
protected cryptoFunctionService: CryptoFunctionService,
|
||||
protected logService: LogService,
|
||||
protected ngZone: NgZone
|
||||
protected ngZone: NgZone,
|
||||
protected formBuilder: FormBuilder,
|
||||
protected formValidationErrorService: FormValidationErrorsService
|
||||
) {
|
||||
super(environmentService, i18nService, platformUtilsService);
|
||||
this.selfHosted = platformUtilsService.isSelfHost();
|
||||
}
|
||||
|
||||
get selfHostedDomain() {
|
||||
@@ -54,59 +65,53 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
if (this.email == null || this.email === "") {
|
||||
this.email = await this.stateService.getRememberedEmail();
|
||||
if (this.email == null) {
|
||||
this.email = "";
|
||||
let email = this.formGroup.get("email")?.value;
|
||||
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.alwaysRememberEmail) {
|
||||
this.rememberEmail = (await this.stateService.getRememberedEmail()) != null;
|
||||
}
|
||||
if (Utils.isBrowser && !Utils.isNode) {
|
||||
this.focusInput();
|
||||
const rememberEmail = (await this.stateService.getRememberedEmail()) != null;
|
||||
this.formGroup.get("rememberEmail")?.setValue(rememberEmail);
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
async submit(showToast = true) {
|
||||
const email = this.formGroup.get("email")?.value;
|
||||
const masterPassword = this.formGroup.get("masterPassword")?.value;
|
||||
const rememberEmail = this.formGroup.get("rememberEmail")?.value;
|
||||
|
||||
await this.setupCaptcha();
|
||||
|
||||
if (this.email == null || this.email === "") {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("emailRequired")
|
||||
);
|
||||
this.formGroup.markAllAsTouched();
|
||||
|
||||
//web
|
||||
if (this.formGroup.invalid && !showToast) {
|
||||
return;
|
||||
}
|
||||
if (this.email.indexOf("@") === -1) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("invalidEmail")
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (this.masterPassword == null || this.masterPassword === "") {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("masterPasswordRequired")
|
||||
);
|
||||
|
||||
//desktop, browser; This should be removed once all clients use reactive forms
|
||||
if (this.formGroup.invalid && showToast) {
|
||||
const errorText = this.getErrorToastMessage();
|
||||
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errorText);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const credentials = new PasswordLogInCredentials(
|
||||
this.email,
|
||||
this.masterPassword,
|
||||
email,
|
||||
masterPassword,
|
||||
this.captchaToken,
|
||||
null
|
||||
);
|
||||
this.formPromise = this.authService.logIn(credentials);
|
||||
const response = await this.formPromise;
|
||||
if (this.rememberEmail || this.alwaysRememberEmail) {
|
||||
await this.stateService.setRememberedEmail(this.email);
|
||||
if (rememberEmail || this.alwaysRememberEmail) {
|
||||
await this.stateService.setRememberedEmail(email);
|
||||
} else {
|
||||
await this.stateService.setRememberedEmail(null);
|
||||
}
|
||||
@@ -188,9 +193,30 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit
|
||||
);
|
||||
}
|
||||
|
||||
private getErrorToastMessage() {
|
||||
const error: AllValidationErrors = this.formValidationErrorService
|
||||
.getFormValidationErrors(this.formGroup.controls)
|
||||
.shift();
|
||||
|
||||
if (error) {
|
||||
switch (error.errorName) {
|
||||
case "email":
|
||||
return this.i18nService.t("invalidEmail");
|
||||
default:
|
||||
return this.i18nService.t(this.errorTag(error));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private errorTag(error: AllValidationErrors): string {
|
||||
const name = error.errorName.charAt(0).toUpperCase() + error.errorName.slice(1);
|
||||
return `${error.controlName}${name}`;
|
||||
}
|
||||
|
||||
protected focusInput() {
|
||||
document
|
||||
.getElementById(this.email == null || this.email === "" ? "email" : "masterPassword")
|
||||
.focus();
|
||||
const email = this.formGroup.get("email")?.value;
|
||||
document.getElementById(email == null || email === "" ? "email" : "masterPassword").focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { AbstractThemingService } from "@bitwarden/angular/services/theming/them
|
||||
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
||||
import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/abstractions/account/account-api.service.abstraction";
|
||||
import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/abstractions/account/account.service.abstraction";
|
||||
import { AnonymousHubService as AnonymousHubServiceAbstraction } from "@bitwarden/common/abstractions/anonymousHub.service";
|
||||
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AppIdService as AppIdServiceAbstraction } from "@bitwarden/common/abstractions/appId.service";
|
||||
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
|
||||
@@ -62,6 +63,7 @@ import { Account } from "@bitwarden/common/models/domain/account";
|
||||
import { GlobalState } from "@bitwarden/common/models/domain/globalState";
|
||||
import { AccountApiService } from "@bitwarden/common/services/account/account-api.service";
|
||||
import { AccountService } from "@bitwarden/common/services/account/account.service";
|
||||
import { AnonymousHubService } from "@bitwarden/common/services/anonymousHub.service";
|
||||
import { ApiService } from "@bitwarden/common/services/api.service";
|
||||
import { AppIdService } from "@bitwarden/common/services/appId.service";
|
||||
import { AuditService } from "@bitwarden/common/services/audit.service";
|
||||
@@ -544,6 +546,11 @@ import { ValidationService } from "./validation.service";
|
||||
useClass: ConfigApiService,
|
||||
deps: [ApiServiceAbstraction],
|
||||
},
|
||||
{
|
||||
provide: AnonymousHubServiceAbstraction,
|
||||
useClass: AnonymousHubService,
|
||||
deps: [EnvironmentServiceAbstraction, AuthServiceAbstraction, LogService],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class JslibServicesModule {}
|
||||
|
||||
Reference in New Issue
Block a user