1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-13 14:53:33 +00:00

[PM-5264] Implement StateProvider in LoginEmailService (#7662)

* setup StateProvider in LoginService

* replace implementations

* replace implementation

* remove stateService

* change storage location for web to 'disk-local'

* implement migrate() method of Migrator

* add RememberedEmailMigrator to migrate.ts

* add rollback

* add tests

* replace implementation

* replace implementation

* add StateProvider to Desktop services

* rename LoginService to RememberEmailService

* update state definition

* rename file

* rename to storedEmail

* rename service to EmailService to avoid confusion

* add jsdocs

* refactor login.component.ts

* fix typos

* fix test

* rename to LoginEmailService

* update factory

* more renaming

* remove duplicate logic and rename method

* convert storedEmail to observable

* refactor to remove setStoredEmail() method

* move service to libs/auth/common

* address floating promises

* remove comment

* remove unnecessary deps in service registration
This commit is contained in:
rr-bw
2024-03-30 11:00:27 -07:00
committed by GitHub
parent 77cfa8a5ad
commit 2e51d96416
42 changed files with 396 additions and 240 deletions

View File

@@ -0,0 +1,28 @@
import { LoginEmailServiceAbstraction, LoginEmailService } from "@bitwarden/auth/common";
import {
CachedServices,
factory,
FactoryOptions,
} from "../../../platform/background/service-factories/factory-options";
import {
stateProviderFactory,
StateProviderInitOptions,
} from "../../../platform/background/service-factories/state-provider.factory";
type LoginEmailServiceFactoryOptions = FactoryOptions;
export type LoginEmailServiceInitOptions = LoginEmailServiceFactoryOptions &
StateProviderInitOptions;
export function loginEmailServiceFactory(
cache: { loginEmailService?: LoginEmailServiceAbstraction } & CachedServices,
opts: LoginEmailServiceInitOptions,
): Promise<LoginEmailServiceAbstraction> {
return factory(
cache,
"loginEmailService",
opts,
async () => new LoginEmailService(await stateProviderFactory(cache, opts)),
);
}

View File

@@ -2,8 +2,8 @@ import { Component } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { HintComponent as BaseHintComponent } from "@bitwarden/angular/auth/components/hint.component"; import { HintComponent as BaseHintComponent } from "@bitwarden/angular/auth/components/hint.component";
import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -20,9 +20,9 @@ export class HintComponent extends BaseHintComponent {
apiService: ApiService, apiService: ApiService,
logService: LogService, logService: LogService,
private route: ActivatedRoute, private route: ActivatedRoute,
loginService: LoginService, loginEmailService: LoginEmailServiceAbstraction,
) { ) {
super(router, i18nService, apiService, platformUtilsService, logService, loginService); super(router, i18nService, apiService, platformUtilsService, logService, loginEmailService);
super.onSuccessfulSubmit = async () => { super.onSuccessfulSubmit = async () => {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.

View File

@@ -30,7 +30,7 @@
</form> </form>
<p class="createAccountLink"> <p class="createAccountLink">
{{ "newAroundHere" | i18n }} {{ "newAroundHere" | i18n }}
<a routerLink="/register" (click)="setFormValues()">{{ "createAccount" | i18n }}</a> <a routerLink="/register" (click)="setLoginEmailValues()">{{ "createAccount" | i18n }}</a>
</p> </p>
</div> </div>
</div> </div>

View File

@@ -1,14 +1,13 @@
import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms"; import { FormBuilder, Validators } from "@angular/forms";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { Subject, takeUntil } from "rxjs"; import { Subject, firstValueFrom, takeUntil } from "rxjs";
import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component"; import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service"; import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { AccountSwitcherService } from "./account-switching/services/account-switcher.service"; import { AccountSwitcherService } from "./account-switching/services/account-switcher.service";
@@ -29,38 +28,32 @@ export class HomeComponent implements OnInit, OnDestroy {
constructor( constructor(
protected platformUtilsService: PlatformUtilsService, protected platformUtilsService: PlatformUtilsService,
private stateService: StateService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
private router: Router, private router: Router,
private i18nService: I18nService, private i18nService: I18nService,
private environmentService: EnvironmentService, private environmentService: EnvironmentService,
private loginService: LoginService, private loginEmailService: LoginEmailServiceAbstraction,
private accountSwitcherService: AccountSwitcherService, private accountSwitcherService: AccountSwitcherService,
) {} ) {}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
let savedEmail = this.loginService.getEmail(); const email = this.loginEmailService.getEmail();
const rememberEmail = this.loginService.getRememberEmail(); const rememberEmail = this.loginEmailService.getRememberEmail();
if (savedEmail != null) { if (email != null) {
this.formGroup.patchValue({ this.formGroup.patchValue({ email, rememberEmail });
email: savedEmail,
rememberEmail: rememberEmail,
});
} else { } else {
savedEmail = await this.stateService.getRememberedEmail(); const storedEmail = await firstValueFrom(this.loginEmailService.storedEmail$);
if (savedEmail != null) {
this.formGroup.patchValue({ if (storedEmail != null) {
email: savedEmail, this.formGroup.patchValue({ email: storedEmail, rememberEmail: true });
rememberEmail: true,
});
} }
} }
this.environmentSelector.onOpenSelfHostedSettings this.environmentSelector.onOpenSelfHostedSettings
.pipe(takeUntil(this.destroyed$)) .pipe(takeUntil(this.destroyed$))
.subscribe(() => { .subscribe(() => {
this.setFormValues(); this.setLoginEmailValues();
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["environment"]); this.router.navigate(["environment"]);
@@ -76,8 +69,9 @@ export class HomeComponent implements OnInit, OnDestroy {
return this.accountSwitcherService.availableAccounts$; return this.accountSwitcherService.availableAccounts$;
} }
submit() { async submit() {
this.formGroup.markAllAsTouched(); this.formGroup.markAllAsTouched();
if (this.formGroup.invalid) { if (this.formGroup.invalid) {
this.platformUtilsService.showToast( this.platformUtilsService.showToast(
"error", "error",
@@ -87,15 +81,12 @@ export class HomeComponent implements OnInit, OnDestroy {
return; return;
} }
this.loginService.setEmail(this.formGroup.value.email); this.setLoginEmailValues();
this.loginService.setRememberEmail(this.formGroup.value.rememberEmail); await this.router.navigate(["login"], { queryParams: { email: this.formGroup.value.email } });
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["login"], { queryParams: { email: this.formGroup.value.email } });
} }
setFormValues() { setLoginEmailValues() {
this.loginService.setEmail(this.formGroup.value.email); this.loginEmailService.setEmail(this.formGroup.value.email);
this.loginService.setRememberEmail(this.formGroup.value.rememberEmail); this.loginEmailService.setRememberEmail(this.formGroup.value.rememberEmail);
} }
} }

View File

@@ -6,12 +6,12 @@ import { LoginViaAuthRequestComponent as BaseLoginWithDeviceComponent } from "@b
import { import {
AuthRequestServiceAbstraction, AuthRequestServiceAbstraction,
LoginStrategyServiceAbstraction, LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service"; import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@@ -44,7 +44,7 @@ export class LoginViaAuthRequestComponent extends BaseLoginWithDeviceComponent {
anonymousHubService: AnonymousHubService, anonymousHubService: AnonymousHubService,
validationService: ValidationService, validationService: ValidationService,
stateService: StateService, stateService: StateService,
loginService: LoginService, loginEmailService: LoginEmailServiceAbstraction,
syncService: SyncService, syncService: SyncService,
deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
authRequestService: AuthRequestServiceAbstraction, authRequestService: AuthRequestServiceAbstraction,
@@ -66,7 +66,7 @@ export class LoginViaAuthRequestComponent extends BaseLoginWithDeviceComponent {
anonymousHubService, anonymousHubService,
validationService, validationService,
stateService, stateService,
loginService, loginEmailService,
deviceTrustCryptoService, deviceTrustCryptoService,
authRequestService, authRequestService,
loginStrategyService, loginStrategyService,

View File

@@ -52,7 +52,7 @@
</div> </div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
<button type="button" class="btn link" routerLink="/hint" (click)="setFormValues()"> <button type="button" class="btn link" routerLink="/hint" (click)="setLoginEmailValues()">
<b>{{ "getMasterPasswordHint" | i18n }}</b> <b>{{ "getMasterPasswordHint" | i18n }}</b>
</button> </button>
</div> </div>

View File

@@ -5,9 +5,11 @@ import { firstValueFrom } from "rxjs";
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component"; import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component";
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
} from "@bitwarden/auth/common";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
@@ -46,7 +48,7 @@ export class LoginComponent extends BaseLoginComponent {
formBuilder: FormBuilder, formBuilder: FormBuilder,
formValidationErrorService: FormValidationErrorsService, formValidationErrorService: FormValidationErrorsService,
route: ActivatedRoute, route: ActivatedRoute,
loginService: LoginService, loginEmailService: LoginEmailServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction,
webAuthnLoginService: WebAuthnLoginServiceAbstraction, webAuthnLoginService: WebAuthnLoginServiceAbstraction,
) { ) {
@@ -66,7 +68,7 @@ export class LoginComponent extends BaseLoginComponent {
formBuilder, formBuilder,
formValidationErrorService, formValidationErrorService,
route, route,
loginService, loginEmailService,
ssoLoginService, ssoLoginService,
webAuthnLoginService, webAuthnLoginService,
); );
@@ -77,8 +79,8 @@ export class LoginComponent extends BaseLoginComponent {
this.showPasswordless = flagEnabled("showPasswordless"); this.showPasswordless = flagEnabled("showPasswordless");
if (this.showPasswordless) { if (this.showPasswordless) {
this.formGroup.controls.email.setValue(this.loginService.getEmail()); this.formGroup.controls.email.setValue(this.loginEmailService.getEmail());
this.formGroup.controls.rememberEmail.setValue(this.loginService.getRememberEmail()); this.formGroup.controls.rememberEmail.setValue(this.loginEmailService.getRememberEmail());
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.validateEmail(); this.validateEmail();
@@ -94,7 +96,7 @@ export class LoginComponent extends BaseLoginComponent {
async launchSsoBrowser() { async launchSsoBrowser() {
// Save off email for SSO // Save off email for SSO
await this.ssoLoginService.setSsoEmail(this.formGroup.value.email); await this.ssoLoginService.setSsoEmail(this.formGroup.value.email);
await this.loginService.saveEmailSettings(); await this.loginEmailService.saveEmailSettings();
// Generate necessary sso params // Generate necessary sso params
const passwordOptions: any = { const passwordOptions: any = {
type: "password", type: "password",

View File

@@ -7,10 +7,10 @@ import { TwoFactorComponent as BaseTwoFactorComponent } from "@bitwarden/angular
import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { import {
LoginStrategyServiceAbstraction, LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
@@ -57,7 +57,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
logService: LogService, logService: LogService,
twoFactorService: TwoFactorService, twoFactorService: TwoFactorService,
appIdService: AppIdService, appIdService: AppIdService,
loginService: LoginService, loginEmailService: LoginEmailServiceAbstraction,
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
configService: ConfigService, configService: ConfigService,
ssoLoginService: SsoLoginServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction,
@@ -78,7 +78,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
logService, logService,
twoFactorService, twoFactorService,
appIdService, appIdService,
loginService, loginEmailService,
userDecryptionOptionsService, userDecryptionOptionsService,
ssoLoginService, ssoLoginService,
configService, configService,

View File

@@ -9,6 +9,7 @@ import {
UserDecryptionOptionsService, UserDecryptionOptionsService,
AuthRequestServiceAbstraction, AuthRequestServiceAbstraction,
AuthRequestService, AuthRequestService,
LoginEmailServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service"; import { ApiService as ApiServiceAbstraction } from "@bitwarden/common/abstractions/api.service";
import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service"; import { AuditService as AuditServiceAbstraction } from "@bitwarden/common/abstractions/audit.service";
@@ -258,6 +259,7 @@ export default class MainBackground {
auditService: AuditServiceAbstraction; auditService: AuditServiceAbstraction;
authService: AuthServiceAbstraction; authService: AuthServiceAbstraction;
loginStrategyService: LoginStrategyServiceAbstraction; loginStrategyService: LoginStrategyServiceAbstraction;
loginEmailService: LoginEmailServiceAbstraction;
importApiService: ImportApiServiceAbstraction; importApiService: ImportApiServiceAbstraction;
importService: ImportServiceAbstraction; importService: ImportServiceAbstraction;
exportService: VaultExportServiceAbstraction; exportService: VaultExportServiceAbstraction;
@@ -1080,7 +1082,9 @@ export default class MainBackground {
await this.stateService.setActiveUser(userId); await this.stateService.setActiveUser(userId);
if (userId == null) { if (userId == null) {
await this.stateService.setRememberedEmail(null); this.loginEmailService.setRememberEmail(false);
await this.loginEmailService.saveEmailSettings();
await this.refreshBadge(); await this.refreshBadge();
await this.refreshMenu(); await this.refreshMenu();
await this.overlayBackground.updateOverlayCiphers(); await this.overlayBackground.updateOverlayCiphers();

View File

@@ -30,13 +30,11 @@ import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/ab
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction"; import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { AuthService } from "@bitwarden/common/auth/services/auth.service"; import { AuthService } from "@bitwarden/common/auth/services/auth.service";
import { LoginService } from "@bitwarden/common/auth/services/login.service";
import { import {
AutofillSettingsService, AutofillSettingsService,
AutofillSettingsServiceAbstraction, AutofillSettingsServiceAbstraction,
@@ -429,11 +427,6 @@ const safeProviders: SafeProvider[] = [
useClass: BrowserFileDownloadService, useClass: BrowserFileDownloadService,
deps: [], deps: [],
}), }),
safeProvider({
provide: LoginServiceAbstraction,
useClass: LoginService,
deps: [StateServiceAbstraction],
}),
safeProvider({ safeProvider({
provide: SYSTEM_THEME_OBSERVABLE, provide: SYSTEM_THEME_OBSERVABLE,
useFactory: (platformUtilsService: PlatformUtilsService) => { useFactory: (platformUtilsService: PlatformUtilsService) => {

View File

@@ -4,6 +4,7 @@ import { Component, OnDestroy, OnInit } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { concatMap, firstValueFrom, Subject, takeUntil } from "rxjs"; import { concatMap, firstValueFrom, Subject, takeUntil } from "rxjs";
import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service"; import { AvatarService } from "@bitwarden/common/auth/abstractions/avatar.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
@@ -91,6 +92,7 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
private router: Router, private router: Router,
private tokenService: TokenService, private tokenService: TokenService,
private environmentService: EnvironmentService, private environmentService: EnvironmentService,
private loginEmailService: LoginEmailServiceAbstraction,
) {} ) {}
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
@@ -137,7 +139,10 @@ export class AccountSwitcherComponent implements OnInit, OnDestroy {
async addAccount() { async addAccount() {
this.close(); this.close();
await this.stateService.setRememberedEmail(null);
this.loginEmailService.setRememberEmail(false);
await this.loginEmailService.saveEmailSettings();
await this.router.navigate(["/login"]); await this.router.navigate(["/login"]);
await this.stateService.setActiveUser(null); await this.stateService.setActiveUser(null);
} }

View File

@@ -20,9 +20,7 @@ import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaul
import { PolicyService as PolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { PolicyService as PolicyServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService as AccountServiceAbstraction } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/auth/abstractions/auth.service";
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
import { LoginService } from "@bitwarden/common/auth/services/login.service";
import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service"; import { AutofillSettingsServiceAbstraction } from "@bitwarden/common/autofill/services/autofill-settings.service";
import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/platform/abstractions/broadcaster.service"; import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/platform/abstractions/broadcaster.service";
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/platform/abstractions/crypto-function.service";
@@ -221,11 +219,6 @@ const safeProviders: SafeProvider[] = [
DesktopAutofillSettingsService, DesktopAutofillSettingsService,
], ],
}), }),
safeProvider({
provide: LoginServiceAbstraction,
useClass: LoginService,
deps: [StateServiceAbstraction],
}),
safeProvider({ safeProvider({
provide: CryptoFunctionServiceAbstraction, provide: CryptoFunctionServiceAbstraction,
useClass: RendererCryptoFunctionService, useClass: RendererCryptoFunctionService,

View File

@@ -2,8 +2,8 @@ import { Component } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { HintComponent as BaseHintComponent } from "@bitwarden/angular/auth/components/hint.component"; import { HintComponent as BaseHintComponent } from "@bitwarden/angular/auth/components/hint.component";
import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -19,8 +19,8 @@ export class HintComponent extends BaseHintComponent {
i18nService: I18nService, i18nService: I18nService,
apiService: ApiService, apiService: ApiService,
logService: LogService, logService: LogService,
loginService: LoginService, loginEmailService: LoginEmailServiceAbstraction,
) { ) {
super(router, i18nService, apiService, platformUtilsService, logService, loginService); super(router, i18nService, apiService, platformUtilsService, logService, loginEmailService);
} }
} }

View File

@@ -7,12 +7,12 @@ import { ModalService } from "@bitwarden/angular/services/modal.service";
import { import {
AuthRequestServiceAbstraction, AuthRequestServiceAbstraction,
LoginStrategyServiceAbstraction, LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service"; import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service"; import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
@@ -53,7 +53,7 @@ export class LoginViaAuthRequestComponent extends BaseLoginWithDeviceComponent {
private modalService: ModalService, private modalService: ModalService,
syncService: SyncService, syncService: SyncService,
stateService: StateService, stateService: StateService,
loginService: LoginService, loginEmailService: LoginEmailServiceAbstraction,
deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
authRequestService: AuthRequestServiceAbstraction, authRequestService: AuthRequestServiceAbstraction,
loginStrategyService: LoginStrategyServiceAbstraction, loginStrategyService: LoginStrategyServiceAbstraction,
@@ -74,7 +74,7 @@ export class LoginViaAuthRequestComponent extends BaseLoginWithDeviceComponent {
anonymousHubService, anonymousHubService,
validationService, validationService,
stateService, stateService,
loginService, loginEmailService,
deviceTrustCryptoService, deviceTrustCryptoService,
authRequestService, authRequestService,
loginStrategyService, loginStrategyService,

View File

@@ -99,7 +99,7 @@
class="btn block" class="btn block"
type="button" type="button"
routerLink="/accessibility-cookie" routerLink="/accessibility-cookie"
(click)="setFormValues()" (click)="setLoginEmailValues()"
> >
<i class="bwi bwi-universal-access" aria-hidden="true"></i> <i class="bwi bwi-universal-access" aria-hidden="true"></i>
{{ "loadAccessibilityCookie" | i18n }} {{ "loadAccessibilityCookie" | i18n }}
@@ -139,7 +139,7 @@
type="button" type="button"
class="text text-primary password-hint-btn" class="text text-primary password-hint-btn"
routerLink="/hint" routerLink="/hint"
(click)="setFormValues()" (click)="setLoginEmailValues()"
> >
{{ "getMasterPasswordHint" | i18n }} {{ "getMasterPasswordHint" | i18n }}
</button> </button>

View File

@@ -7,9 +7,11 @@ import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component"; import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component";
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ModalService } from "@bitwarden/angular/services/modal.service";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
} from "@bitwarden/auth/common";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
@@ -69,7 +71,7 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
formBuilder: FormBuilder, formBuilder: FormBuilder,
formValidationErrorService: FormValidationErrorsService, formValidationErrorService: FormValidationErrorsService,
route: ActivatedRoute, route: ActivatedRoute,
loginService: LoginService, loginEmailService: LoginEmailServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction,
webAuthnLoginService: WebAuthnLoginServiceAbstraction, webAuthnLoginService: WebAuthnLoginServiceAbstraction,
) { ) {
@@ -89,7 +91,7 @@ export class LoginComponent extends BaseLoginComponent implements OnDestroy {
formBuilder, formBuilder,
formValidationErrorService, formValidationErrorService,
route, route,
loginService, loginEmailService,
ssoLoginService, ssoLoginService,
webAuthnLoginService, webAuthnLoginService,
); );

View File

@@ -7,10 +7,10 @@ import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ModalService } from "@bitwarden/angular/services/modal.service";
import { import {
LoginStrategyServiceAbstraction, LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
@@ -56,7 +56,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
logService: LogService, logService: LogService,
twoFactorService: TwoFactorService, twoFactorService: TwoFactorService,
appIdService: AppIdService, appIdService: AppIdService,
loginService: LoginService, loginEmailService: LoginEmailServiceAbstraction,
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction,
configService: ConfigService, configService: ConfigService,
@@ -75,7 +75,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent {
logService, logService,
twoFactorService, twoFactorService,
appIdService, appIdService,
loginService, loginEmailService,
userDecryptionOptionsService, userDecryptionOptionsService,
ssoLoginService, ssoLoginService,
configService, configService,

View File

@@ -2,8 +2,8 @@ import { Component } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { HintComponent as BaseHintComponent } from "@bitwarden/angular/auth/components/hint.component"; import { HintComponent as BaseHintComponent } from "@bitwarden/angular/auth/components/hint.component";
import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
@@ -19,8 +19,8 @@ export class HintComponent extends BaseHintComponent {
apiService: ApiService, apiService: ApiService,
platformUtilsService: PlatformUtilsService, platformUtilsService: PlatformUtilsService,
logService: LogService, logService: LogService,
loginService: LoginService, loginEmailService: LoginEmailServiceAbstraction,
) { ) {
super(router, i18nService, apiService, platformUtilsService, logService, loginService); super(router, i18nService, apiService, platformUtilsService, logService, loginEmailService);
} }
} }

View File

@@ -1,6 +1,6 @@
<form <form
#form #form
(ngSubmit)="submit()" (ngSubmit)="submit(false)"
[appApiAction]="formPromise" [appApiAction]="formPromise"
class="tw-container tw-mx-auto" class="tw-container tw-mx-auto"
[formGroup]="formGroup" [formGroup]="formGroup"
@@ -91,7 +91,7 @@
class="-tw-mt-2" class="-tw-mt-2"
routerLink="/hint" routerLink="/hint"
(mousedown)="goToHint()" (mousedown)="goToHint()"
(click)="setFormValues()" (click)="setLoginEmailValues()"
>{{ "getMasterPasswordHint" | i18n }}</a >{{ "getMasterPasswordHint" | i18n }}</a
> >
</div> </div>

View File

@@ -6,7 +6,10 @@ import { first } from "rxjs/operators";
import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component"; import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.component";
import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service";
import { LoginStrategyServiceAbstraction } from "@bitwarden/auth/common"; import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
} from "@bitwarden/auth/common";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction"; import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction"; import { InternalPolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data"; import { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data";
@@ -14,7 +17,6 @@ import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/mod
import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; import { Policy } from "@bitwarden/common/admin-console/models/domain/policy";
import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response"; import { PolicyResponse } from "@bitwarden/common/admin-console/models/response/policy.response";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
@@ -62,7 +64,7 @@ export class LoginComponent extends BaseLoginComponent implements OnInit {
private routerService: RouterService, private routerService: RouterService,
formBuilder: FormBuilder, formBuilder: FormBuilder,
formValidationErrorService: FormValidationErrorsService, formValidationErrorService: FormValidationErrorsService,
loginService: LoginService, loginEmailService: LoginEmailServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction,
webAuthnLoginService: WebAuthnLoginServiceAbstraction, webAuthnLoginService: WebAuthnLoginServiceAbstraction,
) { ) {
@@ -82,7 +84,7 @@ export class LoginComponent extends BaseLoginComponent implements OnInit {
formBuilder, formBuilder,
formValidationErrorService, formValidationErrorService,
route, route,
loginService, loginEmailService,
ssoLoginService, ssoLoginService,
webAuthnLoginService, webAuthnLoginService,
); );
@@ -173,14 +175,14 @@ export class LoginComponent extends BaseLoginComponent implements OnInit {
} }
} }
this.loginService.clearValues(); this.loginEmailService.clearValues();
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate([this.successRoute]); this.router.navigate([this.successRoute]);
} }
goToHint() { goToHint() {
this.setFormValues(); this.setLoginEmailValues();
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigateByUrl("/hint"); this.router.navigateByUrl("/hint");
@@ -201,15 +203,6 @@ export class LoginComponent extends BaseLoginComponent implements OnInit {
this.router.navigate(["/register"]); this.router.navigate(["/register"]);
} }
async submit() {
const rememberEmail = this.formGroup.value.rememberEmail;
if (!rememberEmail) {
await this.stateService.setRememberedEmail(null);
}
await super.submit(false);
}
protected override handleMigrateEncryptionKey(result: AuthResult): boolean { protected override handleMigrateEncryptionKey(result: AuthResult): boolean {
if (!result.requiresEncryptionKeyMigration) { if (!result.requiresEncryptionKeyMigration) {
return false; return false;

View File

@@ -6,10 +6,10 @@ import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { ModalService } from "@bitwarden/angular/services/modal.service"; import { ModalService } from "@bitwarden/angular/services/modal.service";
import { import {
LoginStrategyServiceAbstraction, LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type"; import { TwoFactorProviderType } from "@bitwarden/common/auth/enums/two-factor-provider-type";
@@ -46,7 +46,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
logService: LogService, logService: LogService,
twoFactorService: TwoFactorService, twoFactorService: TwoFactorService,
appIdService: AppIdService, appIdService: AppIdService,
loginService: LoginService, loginEmailService: LoginEmailServiceAbstraction,
userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
ssoLoginService: SsoLoginServiceAbstraction, ssoLoginService: SsoLoginServiceAbstraction,
configService: ConfigService, configService: ConfigService,
@@ -65,7 +65,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
logService, logService,
twoFactorService, twoFactorService,
appIdService, appIdService,
loginService, loginEmailService,
userDecryptionOptionsService, userDecryptionOptionsService,
ssoLoginService, ssoLoginService,
configService, configService,
@@ -103,7 +103,7 @@ export class TwoFactorComponent extends BaseTwoFactorComponent implements OnDest
} }
goAfterLogIn = async () => { goAfterLogIn = async () => {
this.loginService.clearValues(); this.loginEmailService.clearValues();
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate([this.successRoute], { this.router.navigate([this.successRoute], {

View File

@@ -16,8 +16,6 @@ import {
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module"; import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service"; import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service"; import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
import { LoginService } from "@bitwarden/common/auth/services/login.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service"; import { FileDownloadService } from "@bitwarden/common/platform/abstractions/file-download/file-download.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/platform/abstractions/i18n.service";
@@ -29,6 +27,7 @@ import { AbstractStorageService } from "@bitwarden/common/platform/abstractions/
import { ThemeType } from "@bitwarden/common/platform/enums"; import { ThemeType } from "@bitwarden/common/platform/enums";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory"; import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service"; import { MemoryStorageService } from "@bitwarden/common/platform/services/memory-storage.service";
// eslint-disable-next-line import/no-restricted-paths -- Implementation for memory storage
import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service"; import { MigrationBuilderService } from "@bitwarden/common/platform/services/migration-builder.service";
import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner"; import { MigrationRunner } from "@bitwarden/common/platform/services/migration-runner";
import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider"; import { StorageServiceProvider } from "@bitwarden/common/platform/services/storage-service.provider";
@@ -117,11 +116,6 @@ import { WebPlatformUtilsService } from "./web-platform-utils.service";
provide: FileDownloadService, provide: FileDownloadService,
useClass: WebFileDownloadService, useClass: WebFileDownloadService,
}, },
{
provide: LoginServiceAbstraction,
useClass: LoginService,
deps: [StateService],
},
CollectionAdminService, CollectionAdminService,
{ {
provide: OBSERVABLE_DISK_LOCAL_STORAGE, provide: OBSERVABLE_DISK_LOCAL_STORAGE,

View File

@@ -15,6 +15,7 @@ import {
} from "rxjs"; } from "rxjs";
import { import {
LoginEmailServiceAbstraction,
UserDecryptionOptions, UserDecryptionOptions,
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
@@ -23,7 +24,6 @@ import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-conso
import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service"; import { OrganizationUserService } from "@bitwarden/common/admin-console/abstractions/organization-user/organization-user.service";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction"; import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TokenService } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService } from "@bitwarden/common/auth/abstractions/token.service";
@@ -82,7 +82,7 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
protected activatedRoute: ActivatedRoute, protected activatedRoute: ActivatedRoute,
protected messagingService: MessagingService, protected messagingService: MessagingService,
protected tokenService: TokenService, protected tokenService: TokenService,
protected loginService: LoginService, protected loginEmailService: LoginEmailServiceAbstraction,
protected organizationApiService: OrganizationApiServiceAbstraction, protected organizationApiService: OrganizationApiServiceAbstraction,
protected cryptoService: CryptoService, protected cryptoService: CryptoService,
protected organizationUserService: OrganizationUserService, protected organizationUserService: OrganizationUserService,
@@ -244,23 +244,17 @@ export class BaseLoginDecryptionOptionsComponent implements OnInit, OnDestroy {
return; return;
} }
this.loginService.setEmail(this.data.userEmail); this.loginEmailService.setEmail(this.data.userEmail);
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await this.router.navigate(["/login-with-device"]);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/login-with-device"]);
} }
async requestAdminApproval() { async requestAdminApproval() {
this.loginService.setEmail(this.data.userEmail); this.loginEmailService.setEmail(this.data.userEmail);
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await this.router.navigate(["/admin-approval-requested"]);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/admin-approval-requested"]);
} }
async approveWithMasterPassword() { async approveWithMasterPassword() {
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. await this.router.navigate(["/lock"], { queryParams: { from: "login-initiated" } });
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/lock"], { queryParams: { from: "login-initiated" } });
} }
async createUser() { async createUser() {

View File

@@ -1,8 +1,8 @@
import { Directive, OnInit } from "@angular/core"; import { Directive, OnInit } from "@angular/core";
import { Router } from "@angular/router"; import { Router } from "@angular/router";
import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { PasswordHintRequest } from "@bitwarden/common/auth/models/request/password-hint.request"; import { PasswordHintRequest } from "@bitwarden/common/auth/models/request/password-hint.request";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
@@ -22,11 +22,11 @@ export class HintComponent implements OnInit {
protected apiService: ApiService, protected apiService: ApiService,
protected platformUtilsService: PlatformUtilsService, protected platformUtilsService: PlatformUtilsService,
private logService: LogService, private logService: LogService,
private loginService: LoginService, private loginEmailService: LoginEmailServiceAbstraction,
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
this.email = this.loginService.getEmail() ?? ""; this.email = this.loginEmailService.getEmail() ?? "";
} }
async submit() { async submit() {

View File

@@ -6,12 +6,12 @@ import {
AuthRequestLoginCredentials, AuthRequestLoginCredentials,
AuthRequestServiceAbstraction, AuthRequestServiceAbstraction,
LoginStrategyServiceAbstraction, LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service"; import { AnonymousHubService } from "@bitwarden/common/auth/abstractions/anonymous-hub.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction"; import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abstractions/device-trust-crypto.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { AuthRequestType } from "@bitwarden/common/auth/enums/auth-request-type"; import { AuthRequestType } from "@bitwarden/common/auth/enums/auth-request-type";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status"; import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable"; import { AdminAuthRequestStorable } from "@bitwarden/common/auth/models/domain/admin-auth-req-storable";
@@ -83,7 +83,7 @@ export class LoginViaAuthRequestComponent
private anonymousHubService: AnonymousHubService, private anonymousHubService: AnonymousHubService,
private validationService: ValidationService, private validationService: ValidationService,
private stateService: StateService, private stateService: StateService,
private loginService: LoginService, private loginEmailService: LoginEmailServiceAbstraction,
private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction, private deviceTrustCryptoService: DeviceTrustCryptoServiceAbstraction,
private authRequestService: AuthRequestServiceAbstraction, private authRequestService: AuthRequestServiceAbstraction,
private loginStrategyService: LoginStrategyServiceAbstraction, private loginStrategyService: LoginStrategyServiceAbstraction,
@@ -94,7 +94,7 @@ export class LoginViaAuthRequestComponent
// Why would the existence of the email depend on the navigation? // Why would the existence of the email depend on the navigation?
const navigation = this.router.getCurrentNavigation(); const navigation = this.router.getCurrentNavigation();
if (navigation) { if (navigation) {
this.email = this.loginService.getEmail(); this.email = this.loginEmailService.getEmail();
} }
// Gets signalR push notification // Gets signalR push notification
@@ -151,7 +151,7 @@ export class LoginViaAuthRequestComponent
} else { } else {
// Standard auth request // Standard auth request
// TODO: evaluate if we can remove the setting of this.email in the constructor // TODO: evaluate if we can remove the setting of this.email in the constructor
this.email = this.loginService.getEmail(); this.email = this.loginEmailService.getEmail();
if (!this.email) { if (!this.email) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("userEmailMissing")); this.platformUtilsService.showToast("error", null, this.i18nService.t("userEmailMissing"));
@@ -472,17 +472,10 @@ export class LoginViaAuthRequestComponent
} }
} }
async setRememberEmailValues() {
const rememberEmail = this.loginService.getRememberEmail();
const rememberedEmail = this.loginService.getEmail();
await this.stateService.setRememberedEmail(rememberEmail ? rememberedEmail : null);
this.loginService.clearValues();
}
private async handleSuccessfulLoginNavigation() { private async handleSuccessfulLoginNavigation() {
if (this.state === State.StandardAuthRequest) { if (this.state === State.StandardAuthRequest) {
// Only need to set remembered email on standard login with auth req flow // Only need to set remembered email on standard login with auth req flow
await this.setRememberEmailValues(); await this.loginEmailService.saveEmailSettings();
} }
if (this.onSuccessfulLogin != null) { if (this.onSuccessfulLogin != null) {

View File

@@ -4,9 +4,12 @@ import { ActivatedRoute, Router } from "@angular/router";
import { Subject, firstValueFrom } from "rxjs"; import { Subject, firstValueFrom } from "rxjs";
import { take, takeUntil } from "rxjs/operators"; import { take, takeUntil } from "rxjs/operators";
import { LoginStrategyServiceAbstraction, PasswordLoginCredentials } from "@bitwarden/auth/common"; import {
LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
PasswordLoginCredentials,
} from "@bitwarden/auth/common";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction"; import { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-login.service.abstraction";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
@@ -77,7 +80,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
protected formBuilder: FormBuilder, protected formBuilder: FormBuilder,
protected formValidationErrorService: FormValidationErrorsService, protected formValidationErrorService: FormValidationErrorsService,
protected route: ActivatedRoute, protected route: ActivatedRoute,
protected loginService: LoginService, protected loginEmailService: LoginEmailServiceAbstraction,
protected ssoLoginService: SsoLoginServiceAbstraction, protected ssoLoginService: SsoLoginServiceAbstraction,
protected webAuthnLoginService: WebAuthnLoginServiceAbstraction, protected webAuthnLoginService: WebAuthnLoginServiceAbstraction,
) { ) {
@@ -93,25 +96,23 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
const queryParamsEmail = params.email; const queryParamsEmail = params.email;
if (queryParamsEmail != null && queryParamsEmail.indexOf("@") > -1) { if (queryParamsEmail != null && queryParamsEmail.indexOf("@") > -1) {
this.formGroup.get("email").setValue(queryParamsEmail); this.formGroup.controls.email.setValue(queryParamsEmail);
this.loginService.setEmail(queryParamsEmail);
this.paramEmailSet = true; this.paramEmailSet = true;
} }
}); });
let email = this.loginService.getEmail();
if (email == null || email === "") {
email = await this.stateService.getRememberedEmail();
}
if (!this.paramEmailSet) { if (!this.paramEmailSet) {
this.formGroup.get("email")?.setValue(email ?? ""); const storedEmail = await firstValueFrom(this.loginEmailService.storedEmail$);
this.formGroup.controls.email.setValue(storedEmail ?? "");
} }
let rememberEmail = this.loginService.getRememberEmail();
let rememberEmail = this.loginEmailService.getRememberEmail();
if (rememberEmail == null) { if (rememberEmail == null) {
rememberEmail = (await this.stateService.getRememberedEmail()) != null; rememberEmail = (await firstValueFrom(this.loginEmailService.storedEmail$)) != null;
} }
this.formGroup.get("rememberEmail")?.setValue(rememberEmail);
this.formGroup.controls.rememberEmail.setValue(rememberEmail);
} }
ngOnDestroy() { ngOnDestroy() {
@@ -148,8 +149,10 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
this.formPromise = this.loginStrategyService.logIn(credentials); this.formPromise = this.loginStrategyService.logIn(credentials);
const response = await this.formPromise; const response = await this.formPromise;
this.setFormValues();
await this.loginService.saveEmailSettings(); this.setLoginEmailValues();
await this.loginEmailService.saveEmailSettings();
if (this.handleCaptchaRequired(response)) { if (this.handleCaptchaRequired(response)) {
return; return;
} else if (this.handleMigrateEncryptionKey(response)) { } else if (this.handleMigrateEncryptionKey(response)) {
@@ -214,7 +217,7 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
return; return;
} }
this.setFormValues(); this.setLoginEmailValues();
// FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling. // FIXME: Verify that this floating promise is intentional. If it is, add an explanatory comment and ensure there is proper error handling.
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.router.navigate(["/login-with-device"]); this.router.navigate(["/login-with-device"]);
@@ -292,14 +295,14 @@ export class LoginComponent extends CaptchaProtectedComponent implements OnInit,
} }
} }
setFormValues() { setLoginEmailValues() {
this.loginService.setEmail(this.formGroup.value.email); this.loginEmailService.setEmail(this.formGroup.value.email);
this.loginService.setRememberEmail(this.formGroup.value.rememberEmail); this.loginEmailService.setRememberEmail(this.formGroup.value.rememberEmail);
} }
async saveEmailSettings() { async saveEmailSettings() {
this.setFormValues(); this.setLoginEmailValues();
await this.loginService.saveEmailSettings(); await this.loginEmailService.saveEmailSettings();
// Save off email for SSO // Save off email for SSO
await this.ssoLoginService.setSsoEmail(this.formGroup.value.email); await this.ssoLoginService.setSsoEmail(this.formGroup.value.email);

View File

@@ -7,14 +7,14 @@ import { BehaviorSubject } from "rxjs";
// eslint-disable-next-line no-restricted-imports // eslint-disable-next-line no-restricted-imports
import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { import {
FakeKeyConnectorUserDecryptionOption as KeyConnectorUserDecryptionOption,
LoginStrategyServiceAbstraction, LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
FakeKeyConnectorUserDecryptionOption as KeyConnectorUserDecryptionOption,
FakeTrustedDeviceUserDecryptionOption as TrustedDeviceUserDecryptionOption, FakeTrustedDeviceUserDecryptionOption as TrustedDeviceUserDecryptionOption,
FakeUserDecryptionOptions as UserDecryptionOptions, FakeUserDecryptionOptions as UserDecryptionOptions,
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result";
@@ -59,7 +59,7 @@ describe("TwoFactorComponent", () => {
let mockLogService: MockProxy<LogService>; let mockLogService: MockProxy<LogService>;
let mockTwoFactorService: MockProxy<TwoFactorService>; let mockTwoFactorService: MockProxy<TwoFactorService>;
let mockAppIdService: MockProxy<AppIdService>; let mockAppIdService: MockProxy<AppIdService>;
let mockLoginService: MockProxy<LoginService>; let mockLoginEmailService: MockProxy<LoginEmailServiceAbstraction>;
let mockUserDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>; let mockUserDecryptionOptionsService: MockProxy<UserDecryptionOptionsServiceAbstraction>;
let mockSsoLoginService: MockProxy<SsoLoginServiceAbstraction>; let mockSsoLoginService: MockProxy<SsoLoginServiceAbstraction>;
let mockConfigService: MockProxy<ConfigService>; let mockConfigService: MockProxy<ConfigService>;
@@ -89,7 +89,7 @@ describe("TwoFactorComponent", () => {
mockLogService = mock<LogService>(); mockLogService = mock<LogService>();
mockTwoFactorService = mock<TwoFactorService>(); mockTwoFactorService = mock<TwoFactorService>();
mockAppIdService = mock<AppIdService>(); mockAppIdService = mock<AppIdService>();
mockLoginService = mock<LoginService>(); mockLoginEmailService = mock<LoginEmailServiceAbstraction>();
mockUserDecryptionOptionsService = mock<UserDecryptionOptionsServiceAbstraction>(); mockUserDecryptionOptionsService = mock<UserDecryptionOptionsServiceAbstraction>();
mockSsoLoginService = mock<SsoLoginServiceAbstraction>(); mockSsoLoginService = mock<SsoLoginServiceAbstraction>();
mockConfigService = mock<ConfigService>(); mockConfigService = mock<ConfigService>();
@@ -163,7 +163,7 @@ describe("TwoFactorComponent", () => {
{ provide: LogService, useValue: mockLogService }, { provide: LogService, useValue: mockLogService },
{ provide: TwoFactorService, useValue: mockTwoFactorService }, { provide: TwoFactorService, useValue: mockTwoFactorService },
{ provide: AppIdService, useValue: mockAppIdService }, { provide: AppIdService, useValue: mockAppIdService },
{ provide: LoginService, useValue: mockLoginService }, { provide: LoginEmailServiceAbstraction, useValue: mockLoginEmailService },
{ {
provide: UserDecryptionOptionsServiceAbstraction, provide: UserDecryptionOptionsServiceAbstraction,
useValue: mockUserDecryptionOptionsService, useValue: mockUserDecryptionOptionsService,
@@ -280,11 +280,11 @@ describe("TwoFactorComponent", () => {
expect(component.onSuccessfulLogin).toHaveBeenCalled(); expect(component.onSuccessfulLogin).toHaveBeenCalled();
}); });
it("calls loginService.clearValues() when login is successful", async () => { it("calls loginEmailService.clearValues() when login is successful", async () => {
// Arrange // Arrange
mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult()); mockLoginStrategyService.logInTwoFactor.mockResolvedValue(new AuthResult());
// spy on loginService.clearValues // spy on loginEmailService.clearValues
const clearValuesSpy = jest.spyOn(mockLoginService, "clearValues"); const clearValuesSpy = jest.spyOn(mockLoginEmailService, "clearValues");
// Act // Act
await component.doSubmit(); await component.doSubmit();

View File

@@ -8,12 +8,12 @@ import { first } from "rxjs/operators";
import { WINDOW } from "@bitwarden/angular/services/injection-tokens"; import { WINDOW } from "@bitwarden/angular/services/injection-tokens";
import { import {
LoginStrategyServiceAbstraction, LoginStrategyServiceAbstraction,
LoginEmailServiceAbstraction,
TrustedDeviceUserDecryptionOption, TrustedDeviceUserDecryptionOption,
UserDecryptionOptions, UserDecryptionOptions,
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
} from "@bitwarden/auth/common"; } from "@bitwarden/auth/common";
import { ApiService } from "@bitwarden/common/abstractions/api.service"; import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { LoginService } from "@bitwarden/common/auth/abstractions/login.service";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service"; import { TwoFactorService } from "@bitwarden/common/auth/abstractions/two-factor.service";
import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type"; import { AuthenticationType } from "@bitwarden/common/auth/enums/authentication-type";
@@ -88,7 +88,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
protected logService: LogService, protected logService: LogService,
protected twoFactorService: TwoFactorService, protected twoFactorService: TwoFactorService,
protected appIdService: AppIdService, protected appIdService: AppIdService,
protected loginService: LoginService, protected loginEmailService: LoginEmailServiceAbstraction,
protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction, protected userDecryptionOptionsService: UserDecryptionOptionsServiceAbstraction,
protected ssoLoginService: SsoLoginServiceAbstraction, protected ssoLoginService: SsoLoginServiceAbstraction,
protected configService: ConfigService, protected configService: ConfigService,
@@ -288,7 +288,7 @@ export class TwoFactorComponent extends CaptchaProtectedComponent implements OnI
// - TDE login decryption options component // - TDE login decryption options component
// - Browser SSO on extension open // - Browser SSO on extension open
await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgIdentifier); await this.ssoLoginService.setActiveUserOrganizationSsoIdentifier(this.orgIdentifier);
this.loginService.clearValues(); this.loginEmailService.clearValues();
// note: this flow affects both TDE & standard users // note: this flow affects both TDE & standard users
if (this.isForcePasswordResetRequired(authResult)) { if (this.isForcePasswordResetRequired(authResult)) {

View File

@@ -7,6 +7,8 @@ import {
PinCryptoService, PinCryptoService,
LoginStrategyServiceAbstraction, LoginStrategyServiceAbstraction,
LoginStrategyService, LoginStrategyService,
LoginEmailServiceAbstraction,
LoginEmailService,
InternalUserDecryptionOptionsServiceAbstraction, InternalUserDecryptionOptionsServiceAbstraction,
UserDecryptionOptionsService, UserDecryptionOptionsService,
UserDecryptionOptionsServiceAbstraction, UserDecryptionOptionsServiceAbstraction,
@@ -58,7 +60,6 @@ import { DeviceTrustCryptoServiceAbstraction } from "@bitwarden/common/auth/abst
import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction"; import { DevicesServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices/devices.service.abstraction";
import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction";
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { LoginService as LoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/login.service";
import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction"; import { PasswordResetEnrollmentServiceAbstraction } from "@bitwarden/common/auth/abstractions/password-reset-enrollment.service.abstraction";
import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction";
import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service"; import { TokenService as TokenServiceAbstraction } from "@bitwarden/common/auth/abstractions/token.service";
@@ -77,7 +78,6 @@ import { DeviceTrustCryptoService } from "@bitwarden/common/auth/services/device
import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation"; import { DevicesServiceImplementation } from "@bitwarden/common/auth/services/devices/devices.service.implementation";
import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation"; import { DevicesApiServiceImplementation } from "@bitwarden/common/auth/services/devices-api.service.implementation";
import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service"; import { KeyConnectorService } from "@bitwarden/common/auth/services/key-connector.service";
import { LoginService } from "@bitwarden/common/auth/services/login.service";
import { PasswordResetEnrollmentServiceImplementation } from "@bitwarden/common/auth/services/password-reset-enrollment.service.implementation"; import { PasswordResetEnrollmentServiceImplementation } from "@bitwarden/common/auth/services/password-reset-enrollment.service.implementation";
import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service"; import { SsoLoginService } from "@bitwarden/common/auth/services/sso-login.service";
import { TokenService } from "@bitwarden/common/auth/services/token.service"; import { TokenService } from "@bitwarden/common/auth/services/token.service";
@@ -874,9 +874,9 @@ const safeProviders: SafeProvider[] = [
deps: [I18nServiceAbstraction, PlatformUtilsServiceAbstraction], deps: [I18nServiceAbstraction, PlatformUtilsServiceAbstraction],
}), }),
safeProvider({ safeProvider({
provide: LoginServiceAbstraction, provide: LoginEmailServiceAbstraction,
useClass: LoginService, useClass: LoginEmailService,
deps: [StateServiceAbstraction], deps: [StateProvider],
}), }),
safeProvider({ safeProvider({
provide: OrgDomainInternalServiceAbstraction, provide: OrgDomainInternalServiceAbstraction,

View File

@@ -1,4 +1,5 @@
export * from "./pin-crypto.service.abstraction"; export * from "./pin-crypto.service.abstraction";
export * from "./login-email.service";
export * from "./login-strategy.service"; export * from "./login-strategy.service";
export * from "./user-decryption-options.service.abstraction"; export * from "./user-decryption-options.service.abstraction";
export * from "./auth-request.service.abstraction"; export * from "./auth-request.service.abstraction";

View File

@@ -0,0 +1,38 @@
import { Observable } from "rxjs";
export abstract class LoginEmailServiceAbstraction {
/**
* An observable that monitors the storedEmail
*/
storedEmail$: Observable<string>;
/**
* Gets the current email being used in the login process.
* @returns A string of the email.
*/
getEmail: () => string;
/**
* Sets the current email being used in the login process.
* @param email The email to be set.
*/
setEmail: (email: string) => void;
/**
* Gets whether or not the email should be stored on disk.
* @returns A boolean stating whether or not the email should be stored on disk.
*/
getRememberEmail: () => boolean;
/**
* Sets whether or not the email should be stored on disk.
*/
setRememberEmail: (value: boolean) => void;
/**
* Sets the email and rememberEmail properties to null.
*/
clearValues: () => void;
/**
* - If rememberEmail is true, sets the storedEmail on disk to the current email.
* - If rememberEmail is false, sets the storedEmail on disk to null.
* - Then sets the email and rememberEmail properties to null.
* @returns A promise that resolves once the email settings are saved.
*/
saveEmailSettings: () => Promise<void>;
}

View File

@@ -1,4 +1,5 @@
export * from "./pin-crypto/pin-crypto.service.implementation"; export * from "./pin-crypto/pin-crypto.service.implementation";
export * from "./login-email/login-email.service";
export * from "./login-strategies/login-strategy.service"; export * from "./login-strategies/login-strategy.service";
export * from "./user-decryption-options/user-decryption-options.service"; export * from "./user-decryption-options/user-decryption-options.service";
export * from "./auth-request/auth-request.service"; export * from "./auth-request/auth-request.service";

View File

@@ -0,0 +1,52 @@
import { Observable } from "rxjs";
import {
GlobalState,
KeyDefinition,
LOGIN_EMAIL_DISK,
StateProvider,
} from "../../../../../common/src/platform/state";
import { LoginEmailServiceAbstraction } from "../../abstractions/login-email.service";
const STORED_EMAIL = new KeyDefinition<string>(LOGIN_EMAIL_DISK, "storedEmail", {
deserializer: (value: string) => value,
});
export class LoginEmailService implements LoginEmailServiceAbstraction {
private email: string;
private rememberEmail: boolean;
private readonly storedEmailState: GlobalState<string>;
storedEmail$: Observable<string>;
constructor(private stateProvider: StateProvider) {
this.storedEmailState = this.stateProvider.getGlobal(STORED_EMAIL);
this.storedEmail$ = this.storedEmailState.state$;
}
getEmail() {
return this.email;
}
setEmail(email: string) {
this.email = email;
}
getRememberEmail() {
return this.rememberEmail;
}
setRememberEmail(value: boolean) {
this.rememberEmail = value;
}
clearValues() {
this.email = null;
this.rememberEmail = null;
}
async saveEmailSettings() {
await this.storedEmailState.update(() => (this.rememberEmail ? this.email : null));
this.clearValues();
}
}

View File

@@ -1,8 +0,0 @@
export abstract class LoginService {
getEmail: () => string;
getRememberEmail: () => boolean;
setEmail: (value: string) => void;
setRememberEmail: (value: boolean) => void;
clearValues: () => void;
saveEmailSettings: () => Promise<void>;
}

View File

@@ -1,35 +0,0 @@
import { StateService } from "../../platform/abstractions/state.service";
import { LoginService as LoginServiceAbstraction } from "../abstractions/login.service";
export class LoginService implements LoginServiceAbstraction {
private _email: string;
private _rememberEmail: boolean;
constructor(private stateService: StateService) {}
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;
}
async saveEmailSettings() {
await this.stateService.setRememberedEmail(this._rememberEmail ? this._email : null);
this.clearValues();
}
}

View File

@@ -262,8 +262,6 @@ export abstract class StateService<T extends Account = Account> {
* Sets the user's Pin, encrypted by the user key * Sets the user's Pin, encrypted by the user key
*/ */
setProtectedPin: (value: string, options?: StorageOptions) => Promise<void>; setProtectedPin: (value: string, options?: StorageOptions) => Promise<void>;
getRememberedEmail: (options?: StorageOptions) => Promise<string>;
setRememberedEmail: (value: string, options?: StorageOptions) => Promise<void>;
getSecurityStamp: (options?: StorageOptions) => Promise<string>; getSecurityStamp: (options?: StorageOptions) => Promise<string>;
setSecurityStamp: (value: string, options?: StorageOptions) => Promise<void>; setSecurityStamp: (value: string, options?: StorageOptions) => Promise<void>;
getUserId: (options?: StorageOptions) => Promise<string>; getUserId: (options?: StorageOptions) => Promise<string>;

View File

@@ -3,7 +3,6 @@ import { ThemeType } from "../../enums";
export class GlobalState { export class GlobalState {
installedVersion?: string; installedVersion?: string;
organizationInvitation?: any; organizationInvitation?: any;
rememberedEmail?: string;
theme?: ThemeType = ThemeType.System; theme?: ThemeType = ThemeType.System;
twoFactorToken?: string; twoFactorToken?: string;
biometricFingerprintValidated?: boolean; biometricFingerprintValidated?: boolean;

View File

@@ -1241,23 +1241,6 @@ export class StateService<
); );
} }
async getRememberedEmail(options?: StorageOptions): Promise<string> {
return (
await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()))
)?.rememberedEmail;
}
async setRememberedEmail(value: string, options?: StorageOptions): Promise<void> {
const globals = await this.getGlobals(
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()),
);
globals.rememberedEmail = value;
await this.saveGlobals(
globals,
this.reconcileOptions(options, await this.defaultOnDiskLocalOptions()),
);
}
async getSecurityStamp(options?: StorageOptions): Promise<string> { async getSecurityStamp(options?: StorageOptions): Promise<string> {
return ( return (
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))

View File

@@ -38,13 +38,16 @@ export const BILLING_DISK = new StateDefinition("billing", "disk");
export const KEY_CONNECTOR_DISK = new StateDefinition("keyConnector", "disk"); export const KEY_CONNECTOR_DISK = new StateDefinition("keyConnector", "disk");
export const ACCOUNT_MEMORY = new StateDefinition("account", "memory"); export const ACCOUNT_MEMORY = new StateDefinition("account", "memory");
export const AVATAR_DISK = new StateDefinition("avatar", "disk", { web: "disk-local" }); export const AVATAR_DISK = new StateDefinition("avatar", "disk", { web: "disk-local" });
export const LOGIN_EMAIL_DISK = new StateDefinition("loginEmail", "disk", {
web: "disk-local",
});
export const LOGIN_STRATEGY_MEMORY = new StateDefinition("loginStrategy", "memory");
export const SSO_DISK = new StateDefinition("ssoLogin", "disk"); export const SSO_DISK = new StateDefinition("ssoLogin", "disk");
export const TOKEN_DISK = new StateDefinition("token", "disk"); export const TOKEN_DISK = new StateDefinition("token", "disk");
export const TOKEN_DISK_LOCAL = new StateDefinition("tokenDiskLocal", "disk", { export const TOKEN_DISK_LOCAL = new StateDefinition("tokenDiskLocal", "disk", {
web: "disk-local", web: "disk-local",
}); });
export const TOKEN_MEMORY = new StateDefinition("token", "memory"); export const TOKEN_MEMORY = new StateDefinition("token", "memory");
export const LOGIN_STRATEGY_MEMORY = new StateDefinition("loginStrategy", "memory");
export const USER_DECRYPTION_OPTIONS_DISK = new StateDefinition("userDecryptionOptions", "disk"); export const USER_DECRYPTION_OPTIONS_DISK = new StateDefinition("userDecryptionOptions", "disk");
// Autofill // Autofill

View File

@@ -47,6 +47,7 @@ import { MoveDdgToStateProviderMigrator } from "./migrations/48-move-ddg-to-stat
import { AccountServerConfigMigrator } from "./migrations/49-move-account-server-configs"; import { AccountServerConfigMigrator } from "./migrations/49-move-account-server-configs";
import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys"; import { AddKeyTypeToOrgKeysMigrator } from "./migrations/5-add-key-type-to-org-keys";
import { KeyConnectorMigrator } from "./migrations/50-move-key-connector-to-state-provider"; import { KeyConnectorMigrator } from "./migrations/50-move-key-connector-to-state-provider";
import { RememberedEmailMigrator } from "./migrations/51-move-remembered-email-to-state-providers";
import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key"; import { RemoveLegacyEtmKeyMigrator } from "./migrations/6-remove-legacy-etm-key";
import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account"; import { MoveBiometricAutoPromptToAccount } from "./migrations/7-move-biometric-auto-prompt-to-account";
import { MoveStateVersionMigrator } from "./migrations/8-move-state-version"; import { MoveStateVersionMigrator } from "./migrations/8-move-state-version";
@@ -54,7 +55,7 @@ import { MoveBrowserSettingsToGlobal } from "./migrations/9-move-browser-setting
import { MinVersionMigrator } from "./migrations/min-version"; import { MinVersionMigrator } from "./migrations/min-version";
export const MIN_VERSION = 3; export const MIN_VERSION = 3;
export const CURRENT_VERSION = 50; export const CURRENT_VERSION = 51;
export type MinVersion = typeof MIN_VERSION; export type MinVersion = typeof MIN_VERSION;
@@ -107,7 +108,8 @@ export function createMigrationBuilder() {
.with(MoveDesktopSettingsMigrator, 46, 47) .with(MoveDesktopSettingsMigrator, 46, 47)
.with(MoveDdgToStateProviderMigrator, 47, 48) .with(MoveDdgToStateProviderMigrator, 47, 48)
.with(AccountServerConfigMigrator, 48, 49) .with(AccountServerConfigMigrator, 48, 49)
.with(KeyConnectorMigrator, 49, CURRENT_VERSION); .with(KeyConnectorMigrator, 49, 50)
.with(RememberedEmailMigrator, 50, CURRENT_VERSION);
} }
export async function currentVersion( export async function currentVersion(

View File

@@ -0,0 +1,81 @@
import { MockProxy } from "jest-mock-extended";
import { MigrationHelper } from "../migration-helper";
import { mockMigrationHelper, runMigrator } from "../migration-helper.spec";
import { RememberedEmailMigrator } from "./51-move-remembered-email-to-state-providers";
function rollbackJSON() {
return {
global: {
extra: "data",
},
global_loginEmail_storedEmail: "user@example.com",
};
}
describe("RememberedEmailMigrator", () => {
const migrator = new RememberedEmailMigrator(50, 51);
describe("migrate", () => {
it("should migrate the rememberedEmail property from the legacy global object to a global StorageKey as 'global_loginEmail_storedEmail'", async () => {
const output = await runMigrator(migrator, {
global: {
rememberedEmail: "user@example.com",
extra: "data", // Represents a global property that should persist after migration
},
});
expect(output).toEqual({
global: {
extra: "data",
},
global_loginEmail_storedEmail: "user@example.com",
});
});
it("should remove the rememberedEmail property from the legacy global object", async () => {
const output = await runMigrator(migrator, {
global: {
rememberedEmail: "user@example.com",
},
});
expect(output.global).not.toHaveProperty("rememberedEmail");
});
});
describe("rollback", () => {
let helper: MockProxy<MigrationHelper>;
let sut: RememberedEmailMigrator;
const keyDefinitionLike = {
key: "storedEmail",
stateDefinition: {
name: "loginEmail",
},
};
beforeEach(() => {
helper = mockMigrationHelper(rollbackJSON(), 51);
sut = new RememberedEmailMigrator(50, 51);
});
it("should null out the storedEmail global StorageKey", async () => {
await sut.rollback(helper);
expect(helper.setToGlobal).toHaveBeenCalledTimes(1);
expect(helper.setToGlobal).toHaveBeenCalledWith(keyDefinitionLike, null);
});
it("should add the rememberedEmail property back to legacy global object", async () => {
await sut.rollback(helper);
expect(helper.set).toHaveBeenCalledTimes(1);
expect(helper.set).toHaveBeenCalledWith("global", {
rememberedEmail: "user@example.com",
extra: "data",
});
});
});
});

View File

@@ -0,0 +1,46 @@
import { KeyDefinitionLike, MigrationHelper, StateDefinitionLike } from "../migration-helper";
import { Migrator } from "../migrator";
type ExpectedGlobalState = { rememberedEmail?: string };
const LOGIN_EMAIL_STATE: StateDefinitionLike = { name: "loginEmail" };
const STORED_EMAIL: KeyDefinitionLike = {
key: "storedEmail",
stateDefinition: LOGIN_EMAIL_STATE,
};
export class RememberedEmailMigrator extends Migrator<50, 51> {
async migrate(helper: MigrationHelper): Promise<void> {
const legacyGlobal = await helper.get<ExpectedGlobalState>("global");
// Move global data
if (legacyGlobal?.rememberedEmail != null) {
await helper.setToGlobal(STORED_EMAIL, legacyGlobal.rememberedEmail);
}
// Delete legacy global data
delete legacyGlobal?.rememberedEmail;
await helper.set("global", legacyGlobal);
}
async rollback(helper: MigrationHelper): Promise<void> {
let legacyGlobal = await helper.get<ExpectedGlobalState>("global");
let updatedLegacyGlobal = false;
const globalStoredEmail = await helper.getFromGlobal<string>(STORED_EMAIL);
if (globalStoredEmail) {
if (!legacyGlobal) {
legacyGlobal = {};
}
updatedLegacyGlobal = true;
legacyGlobal.rememberedEmail = globalStoredEmail;
await helper.setToGlobal(STORED_EMAIL, null);
}
if (updatedLegacyGlobal) {
await helper.set("global", legacyGlobal);
}
}
}