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:
parent
aa256b8a70
commit
2cd65939d5
|
@ -2028,5 +2028,20 @@
|
|||
"example": "Jun 15, 2015"
|
||||
}
|
||||
}
|
||||
},
|
||||
"loginWithMasterPassword": {
|
||||
"message": "Log in with master password"
|
||||
},
|
||||
"loggingInAs": {
|
||||
"message": "Logging in as"
|
||||
},
|
||||
"notYou": {
|
||||
"message": "Not you?"
|
||||
},
|
||||
"newAroundHere": {
|
||||
"message": "New around here?"
|
||||
},
|
||||
"rememberEmail": {
|
||||
"message": "Remember email"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise">
|
||||
<header>
|
||||
<div class="left">
|
||||
<button type="button" routerLink="/login">{{ "cancel" | i18n }}</button>
|
||||
<button type="button" routerLink="/login">
|
||||
{{ "cancel" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<h1 class="center">
|
||||
<span class="title">{{ "passwordHint" | i18n }}</span>
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { Component } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { HintComponent as BaseHintComponent } from "@bitwarden/angular/components/hint.component";
|
||||
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";
|
||||
|
||||
@Component({
|
||||
|
@ -17,8 +18,14 @@ export class HintComponent extends BaseHintComponent {
|
|||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
apiService: ApiService,
|
||||
logService: LogService
|
||||
logService: LogService,
|
||||
private route: ActivatedRoute,
|
||||
loginService: LoginService
|
||||
) {
|
||||
super(router, i18nService, apiService, platformUtilsService, logService);
|
||||
super(router, i18nService, apiService, platformUtilsService, logService, loginService);
|
||||
|
||||
super.onSuccessfulSubmit = async () => {
|
||||
this.router.navigate([this.successRoute]);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,15 +2,28 @@
|
|||
<div class="content">
|
||||
<div class="logo-image"></div>
|
||||
<p class="lead text-center">{{ "loginOrCreateNewAccount" | i18n }}</p>
|
||||
<button type="button" class="btn primary block" routerLink="/login">
|
||||
<b>{{ "login" | i18n }}</b>
|
||||
</button>
|
||||
<button type="button" (click)="launchSsoBrowser()" class="btn block">
|
||||
<i class="bwi bwi-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
|
||||
</button>
|
||||
<button type="button" class="btn block" routerLink="/register">
|
||||
{{ "createAccount" | i18n }}
|
||||
</button>
|
||||
<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>
|
||||
</div>
|
||||
<div class="box-footer no-margin" *ngIf="selfHostedDomain">
|
||||
{{ "loggingInTo" | i18n: selfHostedDomain }}
|
||||
</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="/register">{{ "createAccount" | i18n }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" routerLink="/environment" class="settings-icon">
|
||||
|
|
|
@ -1,63 +1,56 @@
|
|||
import { Component } from "@angular/core";
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
|
||||
@Component({
|
||||
selector: "app-home",
|
||||
templateUrl: "home.component.html",
|
||||
})
|
||||
export class HomeComponent {
|
||||
export class HomeComponent implements OnInit {
|
||||
loginInitiated = false;
|
||||
|
||||
formGroup = this.formBuilder.group({
|
||||
email: ["", [Validators.required, Validators.email]],
|
||||
});
|
||||
|
||||
constructor(
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
private passwordGenerationService: PasswordGenerationService,
|
||||
private stateService: StateService,
|
||||
private cryptoFunctionService: CryptoFunctionService,
|
||||
private environmentService: EnvironmentService
|
||||
private formBuilder: FormBuilder,
|
||||
private router: Router,
|
||||
private i18nService: I18nService,
|
||||
private environmentService: EnvironmentService,
|
||||
private route: ActivatedRoute
|
||||
) {}
|
||||
async ngOnInit(): Promise<void> {
|
||||
const rememberedEmail = await this.stateService.getRememberedEmail();
|
||||
if (rememberedEmail != null) {
|
||||
this.formGroup.patchValue({ email: await this.stateService.getRememberedEmail() });
|
||||
}
|
||||
}
|
||||
|
||||
async launchSsoBrowser() {
|
||||
// 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.stateService.setSsoCodeVerifier(codeVerifier);
|
||||
await this.stateService.setSsoState(state);
|
||||
|
||||
let url = this.environmentService.getWebVaultUrl();
|
||||
if (url == null) {
|
||||
url = "https://vault.bitwarden.com";
|
||||
submit() {
|
||||
this.formGroup.markAllAsTouched();
|
||||
if (this.formGroup.invalid) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccured"),
|
||||
this.i18nService.t("invalidEmail")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const redirectUri = url + "/sso-connector.html";
|
||||
this.stateService.setRememberedEmail(this.formGroup.value.email);
|
||||
|
||||
// Launch browser
|
||||
this.platformUtilsService.launchUri(
|
||||
url +
|
||||
"/#/sso?clientId=browser" +
|
||||
"&redirectUri=" +
|
||||
encodeURIComponent(redirectUri) +
|
||||
"&state=" +
|
||||
state +
|
||||
"&codeChallenge=" +
|
||||
codeChallenge
|
||||
);
|
||||
this.router.navigate(["login"], { queryParams: { email: this.formGroup.value.email } });
|
||||
}
|
||||
|
||||
get selfHostedDomain() {
|
||||
return this.environmentService.hasBaseUrl() ? this.environmentService.getWebVaultUrl() : null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,25 +1,12 @@
|
|||
<form #form (ngSubmit)="submit()" [appApiAction]="formPromise" [formGroup]="formGroup">
|
||||
<header>
|
||||
<div class="left">
|
||||
<button type="button" routerLink="/home">{{ "cancel" | i18n }}</button>
|
||||
</div>
|
||||
<h1 class="center">
|
||||
<span class="title">{{ "appName" | i18n }}</span>
|
||||
<h1 class="login-center">
|
||||
<span class="title">{{ "logIn" | i18n }}</span>
|
||||
</h1>
|
||||
<div class="right">
|
||||
<button type="submit" [disabled]="form.loading">
|
||||
<span [hidden]="form.loading">{{ "login" | i18n }}</span>
|
||||
<i class="bwi bwi-spinner bwi-lg bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<main tabindex="-1">
|
||||
<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>
|
||||
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||
<div class="row-main">
|
||||
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||
|
@ -52,13 +39,27 @@
|
|||
<iframe id="hcaptcha_iframe" height="80"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-footer">
|
||||
<button type="button" class="btn link" routerLink="/hint" (click)="setFormValues()">
|
||||
<b>{{ "getMasterPasswordHint" | i18n }}</b>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-center text-muted" *ngIf="selfHostedDomain">
|
||||
{{ "loggingInTo" | i18n: selfHostedDomain }}
|
||||
</p>
|
||||
<p class="text-center">
|
||||
<button type="button" routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</button>
|
||||
</p>
|
||||
<app-private-mode-warning></app-private-mode-warning>
|
||||
<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>
|
||||
<button type="button" (click)="launchSsoBrowser()" class="btn block">
|
||||
<i class="bwi bwi-bank" 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>
|
||||
|
|
|
@ -1,27 +1,33 @@
|
|||
import { Component, NgZone } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/components/login.component";
|
||||
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";
|
||||
import { FormValidationErrorsService } 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";
|
||||
import { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
|
||||
@Component({
|
||||
selector: "app-login",
|
||||
templateUrl: "login.component.html",
|
||||
})
|
||||
export class LoginComponent extends BaseLoginComponent {
|
||||
protected alwaysRememberEmail = true;
|
||||
protected skipRememberEmail = true;
|
||||
|
||||
constructor(
|
||||
apiService: ApiService,
|
||||
appIdService: AppIdService,
|
||||
authService: AuthService,
|
||||
router: Router,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
|
@ -34,9 +40,13 @@ export class LoginComponent extends BaseLoginComponent {
|
|||
logService: LogService,
|
||||
ngZone: NgZone,
|
||||
formBuilder: FormBuilder,
|
||||
formValidationErrorService: FormValidationErrorsService
|
||||
formValidationErrorService: FormValidationErrorsService,
|
||||
route: ActivatedRoute,
|
||||
loginService: LoginService
|
||||
) {
|
||||
super(
|
||||
apiService,
|
||||
appIdService,
|
||||
authService,
|
||||
router,
|
||||
platformUtilsService,
|
||||
|
@ -48,7 +58,9 @@ export class LoginComponent extends BaseLoginComponent {
|
|||
logService,
|
||||
ngZone,
|
||||
formBuilder,
|
||||
formValidationErrorService
|
||||
formValidationErrorService,
|
||||
route,
|
||||
loginService
|
||||
);
|
||||
super.onSuccessfulLogin = async () => {
|
||||
await syncService.fullSync(true);
|
||||
|
@ -59,4 +71,45 @@ export class LoginComponent extends BaseLoginComponent {
|
|||
settings() {
|
||||
this.router.navigate(["environment"]);
|
||||
}
|
||||
|
||||
async launchSsoBrowser() {
|
||||
// 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.stateService.setSsoCodeVerifier(codeVerifier);
|
||||
await this.stateService.setSsoState(state);
|
||||
|
||||
let url = this.environmentService.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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.s
|
|||
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 { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
|
@ -44,7 +45,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
|||
private messagingService: MessagingService,
|
||||
logService: LogService,
|
||||
twoFactorService: TwoFactorService,
|
||||
appIdService: AppIdService
|
||||
appIdService: AppIdService,
|
||||
loginService: LoginService
|
||||
) {
|
||||
super(
|
||||
authService,
|
||||
|
@ -58,9 +60,11 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
|||
route,
|
||||
logService,
|
||||
twoFactorService,
|
||||
appIdService
|
||||
appIdService,
|
||||
loginService
|
||||
);
|
||||
super.onSuccessfulLogin = () => {
|
||||
this.loginService.clearValues();
|
||||
return syncService.fullSync(true);
|
||||
};
|
||||
super.successRoute = "/tabs/vault";
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<app-callout type="warning" *ngIf="showWarning">
|
||||
<app-callout class="app-private-mode-warning" type="warning" *ngIf="showWarning">
|
||||
{{ "privateModeWarning" | i18n }}
|
||||
<a href="https://bitwarden.com/help/article/private-mode/" target="_blank" rel="noopener">{{
|
||||
"learnMore" | i18n
|
||||
|
|
|
@ -174,6 +174,11 @@ header {
|
|||
|
||||
.right {
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
app-avatar {
|
||||
max-height: 30px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
|
@ -183,6 +188,10 @@ header {
|
|||
min-width: 0;
|
||||
}
|
||||
|
||||
.login-center {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
app-pop-out > button,
|
||||
div > button,
|
||||
div > a {
|
||||
|
|
|
@ -83,6 +83,11 @@
|
|||
margin: 5px 10px;
|
||||
font-size: $font-size-small;
|
||||
|
||||
button.btn {
|
||||
font-size: $font-size-small;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@include themify($themes) {
|
||||
color: themed("mutedColor");
|
||||
}
|
||||
|
|
|
@ -440,3 +440,7 @@ app-vault-view .box-footer {
|
|||
html.force_redraw {
|
||||
animation: redraw 1s linear infinite;
|
||||
}
|
||||
|
||||
.rounded-circle {
|
||||
border-radius: 50% !important;
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ app-home {
|
|||
}
|
||||
}
|
||||
|
||||
app-private-mode-warning {
|
||||
.app-private-mode-warning {
|
||||
display: block;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
@ -115,3 +115,11 @@ body.body-full {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.createAccountLink {
|
||||
padding-top: 30px;
|
||||
}
|
||||
|
||||
.login-buttons > button {
|
||||
margin: 15px 0 15px 0;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import { FolderService } from "@bitwarden/common/abstractions/folder/folder.serv
|
|||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
|
||||
import { LogService as LogServiceAbstraction } from "@bitwarden/common/abstractions/log.service";
|
||||
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/abstractions/login.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { NotificationsService } from "@bitwarden/common/abstractions/notifications.service";
|
||||
import { OrganizationService } from "@bitwarden/common/abstractions/organization/organization.service.abstraction";
|
||||
|
@ -48,6 +49,7 @@ import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout
|
|||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
||||
import { AuthService } from "@bitwarden/common/services/auth.service";
|
||||
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
|
||||
import { LoginService } from "@bitwarden/common/services/login.service";
|
||||
import { SearchService } from "@bitwarden/common/services/search.service";
|
||||
|
||||
import MainBackground from "../../background/main.background";
|
||||
|
@ -309,6 +311,10 @@ function getBgService<T>(service: keyof MainBackground) {
|
|||
provide: FileDownloadService,
|
||||
useClass: BrowserFileDownloadService,
|
||||
},
|
||||
{
|
||||
provide: LoginServiceAbstraction,
|
||||
useClass: LoginService,
|
||||
},
|
||||
{
|
||||
provide: AbstractThemingService,
|
||||
useFactory: () => {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { HintComponent as BaseHintComponent } from "@bitwarden/angular/component
|
|||
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";
|
||||
|
||||
@Component({
|
||||
|
@ -17,8 +18,9 @@ export class HintComponent extends BaseHintComponent {
|
|||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
apiService: ApiService,
|
||||
logService: LogService
|
||||
logService: LogService,
|
||||
loginService: LoginService
|
||||
) {
|
||||
super(router, i18nService, apiService, platformUtilsService, logService);
|
||||
super(router, i18nService, apiService, platformUtilsService, logService, loginService);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,78 +22,135 @@
|
|||
<div id="content" class="content">
|
||||
<img class="logo-image" alt="Bitwarden" />
|
||||
<p class="lead">{{ "loginOrCreateNewAccount" | i18n }}</p>
|
||||
<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" />
|
||||
</div>
|
||||
<div class="box-content-row box-content-row-flex" appBoxRow>
|
||||
<div class="row-main">
|
||||
<label for="masterPassword">{{ "masterPass" | i18n }}</label>
|
||||
<!-- 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="masterPassword"
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
class="monospaced"
|
||||
formControlName="masterPassword"
|
||||
appInputVerbatim
|
||||
id="email"
|
||||
type="email"
|
||||
formControlName="email"
|
||||
appInputVerbatim="false"
|
||||
(keyup.enter)="validateEmail()"
|
||||
/>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
</div>
|
||||
<div class="box-footer" *ngIf="selfHostedDomain">
|
||||
{{ "loggingInTo" | i18n: selfHostedDomain }}
|
||||
</div>
|
||||
</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="/register">
|
||||
{{ "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"></iframe>
|
||||
<div class="box-content-row">
|
||||
<button
|
||||
class="btn block"
|
||||
type="button"
|
||||
class="row-btn"
|
||||
appStopClick
|
||||
appA11yTitle="{{ 'toggleVisibility' | i18n }}"
|
||||
[attr.aria-pressed]="showPassword"
|
||||
(click)="togglePassword()"
|
||||
routerLink="/accessibility-cookie"
|
||||
(click)="setFormValues()"
|
||||
>
|
||||
<i
|
||||
class="bwi bwi-lg"
|
||||
aria-hidden="true"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
<i class="bwi bwi-universal-access" aria-hidden="true"></i>
|
||||
{{ "loadAccessibilityCookie" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box last" [hidden]="!showCaptcha()">
|
||||
<div class="box-content">
|
||||
<iframe id="hcaptcha_iframe" style="margin-top: 20px"></iframe>
|
||||
<div class="box-content-row">
|
||||
<button class="btn block" type="button" routerLink="/accessibility-cookie">
|
||||
<i class="bwi bwi-universal-access" aria-hidden="true"></i>
|
||||
{{ "loadAccessibilityCookie" | i18n }}
|
||||
<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">
|
||||
<button
|
||||
type="button"
|
||||
(click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')"
|
||||
class="btn block"
|
||||
>
|
||||
<i class="bwi bwi-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | 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> {{ "logIn" | i18n }}</b
|
||||
>
|
||||
<i class="bwi bwi-spinner bwi-spin" [hidden]="!form.loading" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button type="button" routerLink="/register" class="btn block">
|
||||
<i class="bwi bwi-pencil-square" aria-hidden="true"></i> {{ "createAccount" | i18n }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="buttons-row">
|
||||
<div class="sub-options">
|
||||
<button
|
||||
type="button"
|
||||
(click)="launchSsoBrowser('desktop', 'bitwarden://sso-callback')"
|
||||
class="btn block"
|
||||
class="text text-primary password-hint-btn"
|
||||
routerLink="/hint"
|
||||
(click)="setFormValues()"
|
||||
>
|
||||
<i class="bwi bwi-bank" aria-hidden="true"></i> {{ "enterpriseSingleSignOn" | i18n }}
|
||||
{{ "getMasterPasswordHint" | i18n }}
|
||||
</button>
|
||||
<div>
|
||||
<p class="no-margin">{{ "loggingInAs" | i18n }} {{ loggedEmail }}</p>
|
||||
<a [routerLink]="[]" (click)="toggleValidateEmail(false)">{{ "notYou" | i18n }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sub-options">
|
||||
<button type="button" routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</button>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { Component, NgZone, OnDestroy, ViewChild, ViewContainerRef } from "@angular/core";
|
||||
import { FormBuilder } from "@angular/forms";
|
||||
import { Router } from "@angular/router";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
|
||||
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/components/login.component";
|
||||
import { ModalService } from "@bitwarden/angular/services/modal.service";
|
||||
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 { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
|
@ -11,6 +13,7 @@ import { EnvironmentService } from "@bitwarden/common/abstractions/environment.s
|
|||
import { FormValidationErrorsService } 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 { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
|
@ -29,13 +32,23 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
|
|||
@ViewChild("environment", { read: ViewContainerRef, static: true })
|
||||
environmentModal: ViewContainerRef;
|
||||
|
||||
showingModal = false;
|
||||
webVaultHostname = "";
|
||||
|
||||
protected alwaysRememberEmail = true;
|
||||
showingModal = false;
|
||||
|
||||
private deferFocus: boolean = null;
|
||||
|
||||
get loggedEmail() {
|
||||
return this.formGroup.value.email;
|
||||
}
|
||||
|
||||
get selfHostedDomain() {
|
||||
return this.environmentService.hasBaseUrl() ? this.environmentService.getWebVaultUrl() : null;
|
||||
}
|
||||
|
||||
constructor(
|
||||
apiService: ApiService,
|
||||
appIdService: AppIdService,
|
||||
authService: AuthService,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
|
@ -51,9 +64,13 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
|
|||
private messagingService: MessagingService,
|
||||
logService: LogService,
|
||||
formBuilder: FormBuilder,
|
||||
formValidationErrorService: FormValidationErrorsService
|
||||
formValidationErrorService: FormValidationErrorsService,
|
||||
route: ActivatedRoute,
|
||||
loginService: LoginService
|
||||
) {
|
||||
super(
|
||||
apiService,
|
||||
appIdService,
|
||||
authService,
|
||||
router,
|
||||
platformUtilsService,
|
||||
|
@ -65,7 +82,9 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
|
|||
logService,
|
||||
ngZone,
|
||||
formBuilder,
|
||||
formValidationErrorService
|
||||
formValidationErrorService,
|
||||
route,
|
||||
loginService
|
||||
);
|
||||
super.onSuccessfulLogin = () => {
|
||||
return syncService.fullSync(true);
|
||||
|
@ -127,7 +146,23 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
|
|||
this.showPassword = false;
|
||||
}
|
||||
|
||||
async continue() {
|
||||
await super.validateEmail();
|
||||
if (!this.formGroup.controls.email.valid) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccured"),
|
||||
this.i18nService.t("invalidEmail")
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (!this.validatedEmail) {
|
||||
return;
|
||||
}
|
||||
|
||||
await super.submit();
|
||||
if (this.captchaSiteKey) {
|
||||
const content = document.getElementById("content") as HTMLDivElement;
|
||||
|
|
|
@ -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 { SyncService } from "@bitwarden/common/abstractions/sync/sync.service.abstraction";
|
||||
|
@ -41,7 +42,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
|||
route: ActivatedRoute,
|
||||
logService: LogService,
|
||||
twoFactorService: TwoFactorService,
|
||||
appIdService: AppIdService
|
||||
appIdService: AppIdService,
|
||||
loginService: LoginService
|
||||
) {
|
||||
super(
|
||||
authService,
|
||||
|
@ -55,9 +57,11 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
|||
route,
|
||||
logService,
|
||||
twoFactorService,
|
||||
appIdService
|
||||
appIdService,
|
||||
loginService
|
||||
);
|
||||
super.onSuccessfulLogin = () => {
|
||||
this.loginService.clearValues();
|
||||
return syncService.fullSync(true);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@
|
|||
<ng-container *ngIf="activeAccount?.email != null">
|
||||
<div class="border" *ngIf="numberOfAccounts > 0"></div>
|
||||
<ng-container *ngIf="numberOfAccounts < 4">
|
||||
<button type="button" class="add" routerLink="/login" (click)="addAccount()">
|
||||
<button type="button" class="add" (click)="addAccount()">
|
||||
<i class="bwi bwi-plus" aria-hidden="true"></i> {{ "addAccount" | i18n }}
|
||||
</button>
|
||||
</ng-container>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { animate, state, style, transition, trigger } from "@angular/animations";
|
||||
import { ConnectedPosition } from "@angular/cdk/overlay";
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { concatMap, Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
|
@ -91,6 +92,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
|
|||
private stateService: StateService,
|
||||
private authService: AuthService,
|
||||
private messagingService: MessagingService,
|
||||
private router: Router,
|
||||
private tokenService: TokenService
|
||||
) {}
|
||||
|
||||
|
@ -142,6 +144,8 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
|
|||
async addAccount() {
|
||||
this.close();
|
||||
await this.stateService.setActiveUser(null);
|
||||
await this.stateService.setRememberedEmail(null);
|
||||
this.router.navigate(["/login"]);
|
||||
}
|
||||
|
||||
private async createSwitcherAccounts(baseAccounts: {
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
LogService,
|
||||
LogService as LogServiceAbstraction,
|
||||
} from "@bitwarden/common/abstractions/log.service";
|
||||
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/abstractions/login.service";
|
||||
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/abstractions/passwordReprompt.service";
|
||||
|
@ -35,6 +36,7 @@ import { SystemService as SystemServiceAbstraction } from "@bitwarden/common/abs
|
|||
import { ClientType } from "@bitwarden/common/enums/clientType";
|
||||
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
||||
import { GlobalState } from "@bitwarden/common/models/domain/global-state";
|
||||
import { LoginService } from "@bitwarden/common/services/login.service";
|
||||
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
|
||||
import { SystemService } from "@bitwarden/common/services/system.service";
|
||||
import { ElectronCryptoService } from "@bitwarden/electron/services/electronCrypto.service";
|
||||
|
@ -175,6 +177,10 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
|
|||
EncryptedMessageHandlerService,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: LoginServiceAbstraction,
|
||||
useClass: LoginService,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class ServicesModule {}
|
||||
|
|
|
@ -2021,5 +2021,32 @@
|
|||
},
|
||||
"vault": {
|
||||
"message": "Vault"
|
||||
},
|
||||
"loginWithMasterPassword": {
|
||||
"message": "Log in with master password"
|
||||
},
|
||||
"loggingInAs": {
|
||||
"message": "Logging in as"
|
||||
},
|
||||
"rememberEmail": {
|
||||
"message": "Remember email"
|
||||
},
|
||||
"notYou": {
|
||||
"message": "Not you?"
|
||||
},
|
||||
"newAroundHere": {
|
||||
"message": "New around here?"
|
||||
},
|
||||
"loggingInTo": {
|
||||
"message": "Logging in to $DOMAIN$",
|
||||
"placeholders": {
|
||||
"domain": {
|
||||
"content": "$1",
|
||||
"example": "example.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"logInWithAnotherDevice": {
|
||||
"message": "Log in with another device"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -310,6 +310,11 @@ form,
|
|||
margin-top: 4px;
|
||||
margin-left: -18px;
|
||||
}
|
||||
|
||||
&.remember-email {
|
||||
padding-left: 20px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.radio {
|
||||
|
@ -482,6 +487,10 @@ app-root > #loading,
|
|||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.password-hint-btn {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.set-pin-modal {
|
||||
.box {
|
||||
margin-bottom: 15px;
|
||||
|
|
|
@ -189,6 +189,8 @@
|
|||
|
||||
#login-page {
|
||||
flex-direction: column;
|
||||
justify-content: unset;
|
||||
padding-top: 20px;
|
||||
|
||||
.login-header {
|
||||
align-self: flex-start;
|
||||
|
|
|
@ -5,6 +5,7 @@ import { HintComponent as BaseHintComponent } from "@bitwarden/angular/component
|
|||
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";
|
||||
|
||||
@Component({
|
||||
|
@ -17,8 +18,9 @@ export class HintComponent extends BaseHintComponent {
|
|||
i18nService: I18nService,
|
||||
apiService: ApiService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
logService: LogService
|
||||
logService: LogService,
|
||||
loginService: LoginService
|
||||
) {
|
||||
super(router, i18nService, apiService, platformUtilsService, logService);
|
||||
super(router, i18nService, apiService, platformUtilsService, logService, loginService);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,102 +16,122 @@
|
|||
<div
|
||||
class="tw-mt-3 tw-rounded-md tw-border tw-border-solid tw-border-secondary-300 tw-bg-background tw-p-6"
|
||||
>
|
||||
<bit-callout
|
||||
type="warning"
|
||||
title="{{ 'resetPasswordPolicyAutoEnroll' | i18n }}"
|
||||
*ngIf="showResetPasswordAutoEnrollWarning"
|
||||
>
|
||||
{{ "resetPasswordAutoEnrollInviteWarning" | i18n }}
|
||||
</bit-callout>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "emailAddress" | i18n }}</bit-label>
|
||||
<input id="login_input_email" bitInput type="email" formControlName="email" />
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "masterPass" | i18n }}</bit-label>
|
||||
<input
|
||||
id="login_input_master-password"
|
||||
bitInput
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
formControlName="masterPassword"
|
||||
/>
|
||||
<button type="button" bitSuffix bitButton (click)="togglePassword()">
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="bwi bwi-lg bwi-eye"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
<bit-hint>
|
||||
<a routerLink="/hint">{{ "getMasterPasswordHint" | i18n }}</a>
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3 tw-flex tw-items-start">
|
||||
<div class="tw-flex tw-h-6 tw-items-center">
|
||||
<input
|
||||
id="login_input_remember-email"
|
||||
class="tw-w-4 tw-rounded tw-border"
|
||||
bitInput
|
||||
type="checkbox"
|
||||
formControlName="rememberEmail"
|
||||
/>
|
||||
<ng-container *ngIf="!validatedEmail; else loginPage">
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "emailAddress" | i18n }}</bit-label>
|
||||
<input
|
||||
id="login_input_email"
|
||||
bitInput
|
||||
type="email"
|
||||
formControlName="email"
|
||||
(keyup.enter)="validateEmail()"
|
||||
/>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
<bit-label class="ml-2">
|
||||
{{ "rememberEmail" | i18n }}
|
||||
</bit-label>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<div class="tw-mb-3 tw-flex tw-items-start">
|
||||
<div class="tw-flex tw-h-6 tw-items-center">
|
||||
<input
|
||||
id="login_input_remember-email"
|
||||
class="tw-w-4 tw-rounded tw-border"
|
||||
bitInput
|
||||
type="checkbox"
|
||||
formControlName="rememberEmail"
|
||||
/>
|
||||
</div>
|
||||
<bit-label class="ml-2">
|
||||
{{ "rememberEmail" | i18n }}
|
||||
</bit-label>
|
||||
</div>
|
||||
|
||||
<div [hidden]="!showCaptcha()">
|
||||
<iframe id="hcaptcha_iframe" height="80"></iframe>
|
||||
</div>
|
||||
<div class="tw-mb-3">
|
||||
<button
|
||||
bitButton
|
||||
type="button"
|
||||
buttonType="primary"
|
||||
class="tw-w-full"
|
||||
[disabled]="form.loading"
|
||||
(click)="validateEmail()"
|
||||
>
|
||||
<span> {{ "continue" | i18n }} </span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3 tw-flex tw-space-x-4">
|
||||
<button
|
||||
bitButton
|
||||
buttonType="primary"
|
||||
type="submit"
|
||||
[block]="true"
|
||||
[loading]="form.loading"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<span> <i class="bwi bwi-sign-in"></i> {{ "logIn" | i18n }} </span>
|
||||
</button>
|
||||
<hr />
|
||||
|
||||
<a bitButton buttonType="secondary" routerLink="/register" [block]="true">
|
||||
<i class="bwi bwi-pencil-square"></i>
|
||||
{{ "createAccount" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3" *ngIf="!selfHosted && showPasswordless">
|
||||
<button
|
||||
bitButton
|
||||
type="button"
|
||||
buttonType="secondary"
|
||||
class="tw-w-full"
|
||||
(click)="startPasswordlessLogin()"
|
||||
[disabled]="form.loading"
|
||||
>
|
||||
<span> <i class="bwi bwi-mobile"></i> {{ "loginWithDevice" | i18n }} </span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<a routerLink="/sso" bitButton buttonType="secondary" class="tw-w-full">
|
||||
<i class="bwi bwi-provider tw-mr-2"></i>
|
||||
{{ "enterpriseSingleSignOn" | i18n }}
|
||||
</a>
|
||||
</div>
|
||||
<p class="tw-m-0 tw-text-sm">
|
||||
{{ "newAroundHere" | i18n }}
|
||||
<a routerLink="/register">{{ "createAccount" | i18n }}</a>
|
||||
</p>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<ng-template [formGroup]="formGroup" #loginPage>
|
||||
<div class="tw-mb-3">
|
||||
<bit-form-field>
|
||||
<bit-label>{{ "masterPass" | i18n }}</bit-label>
|
||||
<input
|
||||
id="login_input_master-password"
|
||||
bitInput
|
||||
type="{{ showPassword ? 'text' : 'password' }}"
|
||||
formControlName="masterPassword"
|
||||
/>
|
||||
<button type="button" bitSuffix bitButton (click)="togglePassword()">
|
||||
<i
|
||||
aria-hidden="true"
|
||||
class="bwi bwi-lg bwi-eye"
|
||||
[ngClass]="{ 'bwi-eye': !showPassword, 'bwi-eye-slash': showPassword }"
|
||||
></i>
|
||||
</button>
|
||||
<bit-hint>
|
||||
<a routerLink="/hint" (click)="setFormValues()">{{ "getMasterPasswordHint" | i18n }}</a>
|
||||
</bit-hint>
|
||||
</bit-form-field>
|
||||
</div>
|
||||
|
||||
<div [hidden]="!showCaptcha()">
|
||||
<iframe id="hcaptcha_iframe" height="80"></iframe>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3 tw-flex tw-space-x-4">
|
||||
<button bitButton buttonType="primary" type="submit" [block]="true" [loading]="form.loading">
|
||||
<span> {{ "loginWithMasterPassword" | i18n }} </span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3" *ngIf="showLoginWithDevice && showPasswordless">
|
||||
<button
|
||||
bitButton
|
||||
type="button"
|
||||
[block]="true"
|
||||
buttonType="secondary"
|
||||
(click)="startPasswordlessLogin()"
|
||||
>
|
||||
<span> <i class="bwi bwi-mobile"></i> {{ "loginWithDevice" | i18n }} </span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="tw-mb-3">
|
||||
<a
|
||||
routerLink="/sso"
|
||||
(click)="setFormValues()"
|
||||
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 [routerLink]="[]" (click)="toggleValidateEmail(false)">{{ "notYou" | i18n }}</a>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
|
|
@ -6,12 +6,14 @@ import { first } from "rxjs/operators";
|
|||
|
||||
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/components/login.component";
|
||||
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";
|
||||
import { FormValidationErrorsService } 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 { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
|
@ -39,15 +41,16 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest
|
|||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
apiService: ApiService,
|
||||
appIdService: AppIdService,
|
||||
authService: AuthService,
|
||||
router: Router,
|
||||
i18nService: I18nService,
|
||||
private route: ActivatedRoute,
|
||||
route: ActivatedRoute,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
environmentService: EnvironmentService,
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
cryptoFunctionService: CryptoFunctionService,
|
||||
private apiService: ApiService,
|
||||
private policyApiService: PolicyApiServiceAbstraction,
|
||||
private policyService: InternalPolicyService,
|
||||
logService: LogService,
|
||||
|
@ -56,9 +59,12 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest
|
|||
private messagingService: MessagingService,
|
||||
private routerService: RouterService,
|
||||
formBuilder: FormBuilder,
|
||||
formValidationErrorService: FormValidationErrorsService
|
||||
formValidationErrorService: FormValidationErrorsService,
|
||||
loginService: LoginService
|
||||
) {
|
||||
super(
|
||||
apiService,
|
||||
appIdService,
|
||||
authService,
|
||||
router,
|
||||
platformUtilsService,
|
||||
|
@ -70,7 +76,9 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest
|
|||
logService,
|
||||
ngZone,
|
||||
formBuilder,
|
||||
formValidationErrorService
|
||||
formValidationErrorService,
|
||||
route,
|
||||
loginService
|
||||
);
|
||||
this.onSuccessfulLogin = async () => {
|
||||
this.messagingService.send("setFullWidth");
|
||||
|
@ -82,9 +90,6 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest
|
|||
async ngOnInit() {
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
if (qParams.email != null && qParams.email.indexOf("@") > -1) {
|
||||
this.formGroup.get("email")?.setValue(qParams.email);
|
||||
}
|
||||
if (qParams.premium != null) {
|
||||
this.routerService.setPreviousUrl("/settings/premium");
|
||||
} else if (qParams.org != null) {
|
||||
|
@ -102,8 +107,6 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest
|
|||
this.routerService.setPreviousUrl(route.toString());
|
||||
}
|
||||
await super.ngOnInit();
|
||||
const rememberEmail = await this.stateService.getRememberEmail();
|
||||
this.formGroup.get("rememberEmail")?.setValue(rememberEmail);
|
||||
});
|
||||
|
||||
const invite = await this.stateService.getOrganizationInvitation();
|
||||
|
@ -176,6 +179,7 @@ export class LoginComponent extends BaseLoginComponent implements OnInit, OnDest
|
|||
if (previousUrl) {
|
||||
this.router.navigateByUrl(previousUrl);
|
||||
} else {
|
||||
this.loginService.clearValues();
|
||||
this.router.navigate([this.successRoute]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
@ -40,7 +41,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
|||
logService: LogService,
|
||||
twoFactorService: TwoFactorService,
|
||||
appIdService: AppIdService,
|
||||
private routerService: RouterService
|
||||
private routerService: RouterService,
|
||||
loginService: LoginService
|
||||
) {
|
||||
super(
|
||||
authService,
|
||||
|
@ -54,7 +56,8 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
|||
route,
|
||||
logService,
|
||||
twoFactorService,
|
||||
appIdService
|
||||
appIdService,
|
||||
loginService
|
||||
);
|
||||
this.onSuccessfulLoginNavigate = this.goAfterLogIn;
|
||||
}
|
||||
|
@ -79,6 +82,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
|
|||
}
|
||||
|
||||
async goAfterLogIn() {
|
||||
this.loginService.clearValues();
|
||||
const previousUrl = this.routerService.getPreviousUrl();
|
||||
if (previousUrl) {
|
||||
this.router.navigateByUrl(previousUrl);
|
||||
|
|
|
@ -13,6 +13,7 @@ import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.
|
|||
import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service";
|
||||
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
|
||||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/abstractions/login.service";
|
||||
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/abstractions/passwordReprompt.service";
|
||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
|
@ -20,6 +21,7 @@ import { StateService as BaseStateServiceAbstraction } from "@bitwarden/common/a
|
|||
import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/abstractions/stateMigration.service";
|
||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||
import { StateFactory } from "@bitwarden/common/factories/stateFactory";
|
||||
import { LoginService } from "@bitwarden/common/services/login.service";
|
||||
import { MemoryStorageService } from "@bitwarden/common/services/memoryStorage.service";
|
||||
|
||||
import { BroadcasterMessagingService } from "./broadcaster-messaging.service";
|
||||
|
@ -98,6 +100,10 @@ import { WebPlatformUtilsService } from "./web-platform-utils.service";
|
|||
provide: FileDownloadService,
|
||||
useClass: WebFileDownloadService,
|
||||
},
|
||||
{
|
||||
provide: LoginServiceAbstraction,
|
||||
useClass: LoginService,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class CoreModule {
|
||||
|
|
|
@ -569,12 +569,15 @@
|
|||
"loginOrCreateNewAccount": {
|
||||
"message": "Log in or create a new account to access your secure vault."
|
||||
},
|
||||
"loginWithDevice" : {
|
||||
"loginWithDevice": {
|
||||
"message": "Log in with device"
|
||||
},
|
||||
"loginWithDeviceEnabledInfo": {
|
||||
"message": "Log in with device must be set up in the settings of the Bitwarden mobile app. Need another option?"
|
||||
},
|
||||
"loginWithMasterPassword": {
|
||||
"message": "Log in with master password"
|
||||
},
|
||||
"createAccount": {
|
||||
"message": "Create account"
|
||||
},
|
||||
|
@ -717,7 +720,7 @@
|
|||
"noOrganizationsList": {
|
||||
"message": "You do not belong to any organizations. Organizations allow you to securely share items with other users."
|
||||
},
|
||||
"notificationSentDevice":{
|
||||
"notificationSentDevice": {
|
||||
"message": "A notification has been sent to your device."
|
||||
},
|
||||
"versionNumber": {
|
||||
|
@ -5394,6 +5397,12 @@
|
|||
"numberOfUsers": {
|
||||
"message": "Number of users"
|
||||
},
|
||||
"loggingInAs": {
|
||||
"message": "Logging in as"
|
||||
},
|
||||
"notYou": {
|
||||
"message": "Not you?"
|
||||
},
|
||||
"multiSelectPlaceholder": {
|
||||
"message": "-- Type to Filter --"
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -31,6 +31,7 @@ import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction }
|
|||
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/abstractions/keyConnector.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/abstractions/login.service";
|
||||
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { NotificationsService as NotificationsServiceAbstraction } from "@bitwarden/common/abstractions/notifications.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
|
||||
|
@ -88,6 +89,7 @@ import { FolderApiService } from "@bitwarden/common/services/folder/folder-api.s
|
|||
import { FolderService } from "@bitwarden/common/services/folder/folder.service";
|
||||
import { FormValidationErrorsService } from "@bitwarden/common/services/formValidationErrors.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/services/keyConnector.service";
|
||||
import { LoginService } from "@bitwarden/common/services/login.service";
|
||||
import { NotificationsService } from "@bitwarden/common/services/notifications.service";
|
||||
import { OrganizationApiService } from "@bitwarden/common/services/organization/organization-api.service";
|
||||
import { OrganizationService } from "@bitwarden/common/services/organization/organization.service";
|
||||
|
@ -578,6 +580,10 @@ import { AbstractThemingService } from "./theming/theming.service.abstraction";
|
|||
useClass: ValidationService,
|
||||
deps: [I18nServiceAbstraction, PlatformUtilsServiceAbstraction],
|
||||
},
|
||||
{
|
||||
provide: LoginServiceAbstraction,
|
||||
useClass: LoginService,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class JslibServicesModule {}
|
||||
|
|
|
@ -479,6 +479,7 @@ export abstract class ApiService {
|
|||
putDeviceVerificationSettings: (
|
||||
request: DeviceVerificationRequest
|
||||
) => Promise<DeviceVerificationResponse>;
|
||||
getKnownDevice: (email: string, deviceIdentifier: string) => Promise<boolean>;
|
||||
|
||||
getEmergencyAccessTrusted: () => Promise<ListResponse<EmergencyAccessGranteeDetailsResponse>>;
|
||||
getEmergencyAccessGranted: () => Promise<ListResponse<EmergencyAccessGrantorDetailsResponse>>;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
export abstract class LoginService {
|
||||
getEmail: () => string;
|
||||
getRememberEmail: () => boolean;
|
||||
setEmail: (value: string) => void;
|
||||
setRememberEmail: (value: boolean) => void;
|
||||
clearValues: () => void;
|
||||
}
|
|
@ -1518,6 +1518,12 @@ export class ApiService implements ApiServiceAbstraction {
|
|||
return new DeviceVerificationResponse(r);
|
||||
}
|
||||
|
||||
async getKnownDevice(email: string, deviceIdentifier: string): Promise<boolean> {
|
||||
const path = `/devices/knowndevice/${email}/${deviceIdentifier}`;
|
||||
const r = await this.send("GET", path, null, false, true);
|
||||
return r as boolean;
|
||||
}
|
||||
|
||||
// Emergency Access APIs
|
||||
|
||||
async getEmergencyAccessTrusted(): Promise<ListResponse<EmergencyAccessGranteeDetailsResponse>> {
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { LoginService as LoginServiceAbstraction } from "../abstractions/login.service";
|
||||
|
||||
export class LoginService implements LoginServiceAbstraction {
|
||||
private _email: string;
|
||||
private _rememberEmail: boolean;
|
||||
|
||||
getEmail() {
|
||||
return this._email;
|
||||
}
|
||||
|
||||
getRememberEmail() {
|
||||
return this._rememberEmail;
|
||||
}
|
||||
|
||||
setEmail(value: string) {
|
||||
this._email = value;
|
||||
}
|
||||
|
||||
setRememberEmail(value: boolean) {
|
||||
this._rememberEmail = value;
|
||||
}
|
||||
|
||||
clearValues() {
|
||||
this._email = null;
|
||||
this._rememberEmail = null;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue