mirror of
https://github.com/bitwarden/browser
synced 2025-12-17 16:53:34 +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:
@@ -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 --"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user