diff --git a/apps/browser/src/auth/popup/home.component.html b/apps/browser/src/auth/popup/home.component.html deleted file mode 100644 index 08043cf88bb..00000000000 --- a/apps/browser/src/auth/popup/home.component.html +++ /dev/null @@ -1,36 +0,0 @@ - -
-
-
-

{{ "loginOrCreateNewAccount" | i18n }}

-
-
-
-
- - -
- -
- - -
-
-
-
- -
-
- -
-
diff --git a/apps/browser/src/auth/popup/home.component.ts b/apps/browser/src/auth/popup/home.component.ts deleted file mode 100644 index 0c4510204d1..00000000000 --- a/apps/browser/src/auth/popup/home.component.ts +++ /dev/null @@ -1,130 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, OnDestroy, OnInit, ViewChild } from "@angular/core"; -import { FormBuilder, Validators } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { Subject, firstValueFrom, switchMap, takeUntil, tap } from "rxjs"; - -import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components/environment-selector.component"; -import { LoginEmailServiceAbstraction } from "@bitwarden/auth/common"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { ToastService } from "@bitwarden/components"; - -import { AccountSwitcherService } from "./account-switching/services/account-switcher.service"; - -@Component({ - selector: "app-home", - templateUrl: "home.component.html", -}) -export class HomeComponent implements OnInit, OnDestroy { - @ViewChild(EnvironmentSelectorComponent, { static: true }) - environmentSelector!: EnvironmentSelectorComponent; - private destroyed$: Subject = new Subject(); - - loginInitiated = false; - formGroup = this.formBuilder.group({ - email: ["", [Validators.required, Validators.email]], - rememberEmail: [false], - }); - - constructor( - protected platformUtilsService: PlatformUtilsService, - private formBuilder: FormBuilder, - private router: Router, - private i18nService: I18nService, - private loginEmailService: LoginEmailServiceAbstraction, - private accountSwitcherService: AccountSwitcherService, - private toastService: ToastService, - private configService: ConfigService, - private route: ActivatedRoute, - ) {} - - async ngOnInit(): Promise { - this.listenForUnauthUiRefreshFlagChanges(); - - const email = await firstValueFrom(this.loginEmailService.loginEmail$); - const rememberEmail = this.loginEmailService.getRememberEmail(); - - if (email != null) { - this.formGroup.patchValue({ email, rememberEmail }); - } else { - const storedEmail = await firstValueFrom(this.loginEmailService.storedEmail$); - - if (storedEmail != null) { - this.formGroup.patchValue({ email: storedEmail, rememberEmail: true }); - } - } - - this.environmentSelector.onOpenSelfHostedSettings - .pipe( - switchMap(async () => { - await this.setLoginEmailValues(); - await this.router.navigate(["environment"]); - }), - takeUntil(this.destroyed$), - ) - .subscribe(); - } - - ngOnDestroy(): void { - this.destroyed$.next(); - this.destroyed$.complete(); - } - - private listenForUnauthUiRefreshFlagChanges() { - this.configService - .getFeatureFlag$(FeatureFlag.UnauthenticatedExtensionUIRefresh) - .pipe( - tap(async (flag) => { - // If the flag is turned ON, we must force a reload to ensure the correct UI is shown - if (flag) { - const qParams = await firstValueFrom(this.route.queryParams); - - const uniqueQueryParams = { - ...qParams, - // adding a unique timestamp to the query params to force a reload - t: new Date().getTime().toString(), - }; - - await this.router.navigate(["/login"], { - queryParams: uniqueQueryParams, - }); - } - }), - takeUntil(this.destroyed$), - ) - .subscribe(); - } - - get availableAccounts$() { - return this.accountSwitcherService.availableAccounts$; - } - - async submit() { - this.formGroup.markAllAsTouched(); - - if (this.formGroup.invalid) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccured"), - message: this.i18nService.t("invalidEmail"), - }); - return; - } - - await this.setLoginEmailValues(); - await this.router.navigate(["login"], { - queryParams: { email: this.formGroup.controls.email.value }, - }); - } - - async setLoginEmailValues() { - // Note: Browser saves email settings here instead of the login component - this.loginEmailService.setRememberEmail(this.formGroup.controls.rememberEmail.value); - await this.loginEmailService.setLoginEmail(this.formGroup.controls.email.value); - await this.loginEmailService.saveEmailSettings(); - } -} diff --git a/apps/browser/src/auth/popup/login-v1.component.html b/apps/browser/src/auth/popup/login-v1.component.html deleted file mode 100644 index 145a9cbc754..00000000000 --- a/apps/browser/src/auth/popup/login-v1.component.html +++ /dev/null @@ -1,81 +0,0 @@ -
-
-

- {{ "logIn" | i18n }} -

-
-
-
-
-
-
- - - - -
-
- -
-
-
- -
-
- -
- -
-
diff --git a/apps/browser/src/auth/popup/login-v1.component.ts b/apps/browser/src/auth/popup/login-v1.component.ts deleted file mode 100644 index b2c52f248c6..00000000000 --- a/apps/browser/src/auth/popup/login-v1.component.ts +++ /dev/null @@ -1,142 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, NgZone, OnInit } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { firstValueFrom } from "rxjs"; - -import { LoginComponentV1 as BaseLoginComponent } from "@bitwarden/angular/auth/components/login-v1.component"; -import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; -import { - LoginStrategyServiceAbstraction, - LoginEmailServiceAbstraction, -} from "@bitwarden/auth/common"; -import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; - -@Component({ - selector: "app-login", - templateUrl: "login-v1.component.html", -}) -export class LoginComponentV1 extends BaseLoginComponent implements OnInit { - constructor( - devicesApiService: DevicesApiServiceAbstraction, - appIdService: AppIdService, - loginStrategyService: LoginStrategyServiceAbstraction, - router: Router, - protected platformUtilsService: PlatformUtilsService, - protected i18nService: I18nService, - protected stateService: StateService, - protected environmentService: EnvironmentService, - protected passwordGenerationService: PasswordGenerationServiceAbstraction, - protected cryptoFunctionService: CryptoFunctionService, - syncService: SyncService, - logService: LogService, - ngZone: NgZone, - formBuilder: FormBuilder, - formValidationErrorService: FormValidationErrorsService, - route: ActivatedRoute, - loginEmailService: LoginEmailServiceAbstraction, - ssoLoginService: SsoLoginServiceAbstraction, - toastService: ToastService, - ) { - super( - devicesApiService, - appIdService, - loginStrategyService, - router, - platformUtilsService, - i18nService, - stateService, - environmentService, - passwordGenerationService, - cryptoFunctionService, - logService, - ngZone, - formBuilder, - formValidationErrorService, - route, - loginEmailService, - ssoLoginService, - toastService, - ); - this.onSuccessfulLogin = async () => { - await syncService.fullSync(true); - }; - this.successRoute = "/tabs/vault"; - } - - async ngOnInit(): Promise { - await super.ngOnInit(); - await this.validateEmail(); - } - - settings() { - // 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(["environment"]); - } - - async launchSsoBrowser() { - // Save off email for SSO - await this.ssoLoginService.setSsoEmail(this.formGroup.value.email); - - // Generate necessary sso params - const passwordOptions: any = { - type: "password", - length: 64, - uppercase: true, - lowercase: true, - numbers: true, - special: false, - }; - - const state = - (await this.passwordGenerationService.generatePassword(passwordOptions)) + - ":clientId=browser"; - const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); - const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256"); - const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); - - await this.ssoLoginService.setCodeVerifier(codeVerifier); - await this.ssoLoginService.setSsoState(state); - - const env = await firstValueFrom(this.environmentService.environment$); - let url = env.getWebVaultUrl(); - if (url == null) { - url = "https://vault.bitwarden.com"; - } - - const redirectUri = url + "/sso-connector.html"; - - // Launch browser - this.platformUtilsService.launchUri( - url + - "/#/sso?clientId=browser" + - "&redirectUri=" + - encodeURIComponent(redirectUri) + - "&state=" + - state + - "&codeChallenge=" + - codeChallenge + - "&email=" + - encodeURIComponent(this.formGroup.controls.email.value), - ); - } - - async saveEmailSettings() { - // values should be saved on home component - return; - } -} diff --git a/apps/browser/src/popup/app.module.ts b/apps/browser/src/popup/app.module.ts index a190cb134ac..13aeb25b998 100644 --- a/apps/browser/src/popup/app.module.ts +++ b/apps/browser/src/popup/app.module.ts @@ -22,9 +22,7 @@ import { CurrentAccountComponent } from "../auth/popup/account-switching/current import { EnvironmentComponent } from "../auth/popup/environment.component"; import { ExtensionAnonLayoutWrapperComponent } from "../auth/popup/extension-anon-layout-wrapper/extension-anon-layout-wrapper.component"; import { HintComponent } from "../auth/popup/hint.component"; -import { HomeComponent } from "../auth/popup/home.component"; import { LoginDecryptionOptionsComponentV1 } from "../auth/popup/login-decryption-options/login-decryption-options-v1.component"; -import { LoginComponentV1 } from "../auth/popup/login-v1.component"; import { LoginViaAuthRequestComponentV1 } from "../auth/popup/login-via-auth-request-v1.component"; import { RemovePasswordComponent } from "../auth/popup/remove-password.component"; import { SetPasswordComponent } from "../auth/popup/set-password.component"; @@ -98,9 +96,7 @@ import "../platform/popup/locales"; ColorPasswordCountPipe, EnvironmentComponent, HintComponent, - HomeComponent, LoginViaAuthRequestComponentV1, - LoginComponentV1, LoginDecryptionOptionsComponentV1, SetPasswordComponent, SsoComponentV1, diff --git a/apps/desktop/src/auth/login/login-v1.component.html b/apps/desktop/src/auth/login/login-v1.component.html deleted file mode 100644 index aae4cec239d..00000000000 --- a/apps/desktop/src/auth/login/login-v1.component.html +++ /dev/null @@ -1,157 +0,0 @@ -
-
-
- - Bitwarden - -

{{ "loginOrCreateNewAccount" | i18n }}

- - -
-
-
- - -
-
- - -
-
- -
-
-
- -
-
-
-

{{ "newAroundHere" | i18n }}

- -
-
- - -
-
-
-
- - -
-
- -
-
-
-
-
-
- -
- -
-
-
-
-
- -
-
- -
-
- -
-
-
- -
-

{{ "loggingInAs" | i18n }} {{ loggedEmail }}

- {{ "notYou" | i18n }} -
-
-
-
-
-
- diff --git a/apps/desktop/src/auth/login/login-v1.component.ts b/apps/desktop/src/auth/login/login-v1.component.ts deleted file mode 100644 index ff8688353be..00000000000 --- a/apps/desktop/src/auth/login/login-v1.component.ts +++ /dev/null @@ -1,266 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, NgZone, OnDestroy, OnInit, ViewChild, ViewContainerRef } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { Subject, firstValueFrom, takeUntil, tap } from "rxjs"; - -import { LoginComponentV1 as BaseLoginComponent } from "@bitwarden/angular/auth/components/login-v1.component"; -import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; -import { ModalService } from "@bitwarden/angular/services/modal.service"; -import { - LoginStrategyServiceAbstraction, - LoginEmailServiceAbstraction, -} from "@bitwarden/auth/common"; -import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { BroadcasterService } from "@bitwarden/common/platform/abstractions/broadcaster.service"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; -import { ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; - -import { EnvironmentComponent } from "../environment.component"; - -const BroadcasterSubscriptionId = "LoginComponent"; - -@Component({ - selector: "app-login", - templateUrl: "login-v1.component.html", -}) -export class LoginComponentV1 extends BaseLoginComponent implements OnInit, OnDestroy { - @ViewChild("environment", { read: ViewContainerRef, static: true }) - environmentModal: ViewContainerRef; - - protected componentDestroyed$: Subject = new Subject(); - webVaultHostname = ""; - - showingModal = false; - - private deferFocus: boolean = null; - - get loggedEmail() { - return this.formGroup.value.email; - } - - constructor( - devicesApiService: DevicesApiServiceAbstraction, - appIdService: AppIdService, - loginStrategyService: LoginStrategyServiceAbstraction, - router: Router, - i18nService: I18nService, - syncService: SyncService, - private modalService: ModalService, - platformUtilsService: PlatformUtilsService, - stateService: StateService, - environmentService: EnvironmentService, - passwordGenerationService: PasswordGenerationServiceAbstraction, - cryptoFunctionService: CryptoFunctionService, - private broadcasterService: BroadcasterService, - ngZone: NgZone, - private messagingService: MessagingService, - logService: LogService, - formBuilder: FormBuilder, - formValidationErrorService: FormValidationErrorsService, - route: ActivatedRoute, - loginEmailService: LoginEmailServiceAbstraction, - ssoLoginService: SsoLoginServiceAbstraction, - toastService: ToastService, - private configService: ConfigService, - ) { - super( - devicesApiService, - appIdService, - loginStrategyService, - router, - platformUtilsService, - i18nService, - stateService, - environmentService, - passwordGenerationService, - cryptoFunctionService, - logService, - ngZone, - formBuilder, - formValidationErrorService, - route, - loginEmailService, - ssoLoginService, - toastService, - ); - this.onSuccessfulLogin = () => { - return syncService.fullSync(true); - }; - } - - async ngOnInit() { - this.listenForUnauthUiRefreshFlagChanges(); - - await super.ngOnInit(); - await this.getLoginWithDevice(this.loggedEmail); - this.broadcasterService.subscribe(BroadcasterSubscriptionId, async (message: any) => { - this.ngZone.run(() => { - switch (message.command) { - case "windowHidden": - this.onWindowHidden(); - break; - case "windowIsFocused": - if (this.deferFocus === null) { - this.deferFocus = !message.windowIsFocused; - if (!this.deferFocus) { - this.focusInput(); - } - } else if (this.deferFocus && message.windowIsFocused) { - this.focusInput(); - this.deferFocus = false; - } - break; - default: - } - }); - }); - this.messagingService.send("getWindowIsFocused"); - } - - ngOnDestroy() { - this.broadcasterService.unsubscribe(BroadcasterSubscriptionId); - this.componentDestroyed$.next(); - this.componentDestroyed$.complete(); - } - - private listenForUnauthUiRefreshFlagChanges() { - this.configService - .getFeatureFlag$(FeatureFlag.UnauthenticatedExtensionUIRefresh) - .pipe( - tap(async (flag) => { - if (flag) { - const qParams = await firstValueFrom(this.route.queryParams); - - const uniqueQueryParams = { - ...qParams, - // adding a unique timestamp to the query params to force a reload - t: new Date().getTime().toString(), - }; - - await this.router.navigate(["/"], { - queryParams: uniqueQueryParams, - }); - } - }), - takeUntil(this.componentDestroyed$), - ) - .subscribe(); - } - - async settings() { - const [modal, childComponent] = await this.modalService.openViewRef( - EnvironmentComponent, - this.environmentModal, - ); - - modal.onShown.pipe(takeUntil(this.componentDestroyed$)).subscribe(() => { - this.showingModal = true; - }); - - modal.onClosed.pipe(takeUntil(this.componentDestroyed$)).subscribe(() => { - this.showingModal = false; - }); - - // eslint-disable-next-line rxjs/no-async-subscribe - childComponent.onSaved.pipe(takeUntil(this.componentDestroyed$)).subscribe(async () => { - modal.close(); - await this.getLoginWithDevice(this.loggedEmail); - }); - } - - onWindowHidden() { - this.showPassword = false; - } - - async continue() { - await super.validateEmail(); - if (!this.formGroup.controls.email.valid) { - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccured"), - message: this.i18nService.t("invalidEmail"), - }); - return; - } - this.focusInput(); - } - - async submit() { - if (!this.validatedEmail) { - return; - } - - await super.submit(); - if (this.captchaSiteKey) { - const content = document.getElementById("content") as HTMLDivElement; - content.setAttribute("style", "width:335px"); - } - } - - private focusInput() { - const email = this.loggedEmail; - document.getElementById(email == null || email === "" ? "email" : "masterPassword")?.focus(); - } - - async launchSsoBrowser(clientId: string, ssoRedirectUri: string) { - if (!ipc.platform.isAppImage && !ipc.platform.isSnapStore && !ipc.platform.isDev) { - return super.launchSsoBrowser(clientId, ssoRedirectUri); - } - const email = this.formGroup.controls.email.value; - - // Save off email for SSO - await this.ssoLoginService.setSsoEmail(email); - - // Generate necessary sso params - const passwordOptions: any = { - type: "password", - length: 64, - uppercase: true, - lowercase: true, - numbers: true, - special: false, - }; - const state = await this.passwordGenerationService.generatePassword(passwordOptions); - const ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); - const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); - const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); - - // Save sso params - await this.ssoLoginService.setSsoState(state); - await this.ssoLoginService.setCodeVerifier(ssoCodeVerifier); - - try { - await ipc.platform.localhostCallbackService.openSsoPrompt(codeChallenge, state, email); - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (err) { - this.platformUtilsService.showToast( - "error", - this.i18nService.t("errorOccured"), - this.i18nService.t("ssoError"), - ); - } - } - - /** - * Force the validatedEmail flag to false, which will show the login page. - */ - invalidateEmail() { - this.validatedEmail = false; - } -} diff --git a/apps/desktop/src/auth/login/login.module.ts b/apps/desktop/src/auth/login/login.module.ts index 427cbcb2069..8cd1bdb9212 100644 --- a/apps/desktop/src/auth/login/login.module.ts +++ b/apps/desktop/src/auth/login/login.module.ts @@ -6,17 +6,15 @@ import { EnvironmentSelectorComponent } from "@bitwarden/angular/auth/components import { SharedModule } from "../../app/shared/shared.module"; import { LoginDecryptionOptionsComponentV1 } from "./login-decryption-options/login-decryption-options-v1.component"; -import { LoginComponentV1 } from "./login-v1.component"; import { LoginViaAuthRequestComponentV1 } from "./login-via-auth-request-v1.component"; @NgModule({ imports: [SharedModule, RouterModule], declarations: [ - LoginComponentV1, LoginViaAuthRequestComponentV1, EnvironmentSelectorComponent, LoginDecryptionOptionsComponentV1, ], - exports: [LoginComponentV1, LoginViaAuthRequestComponentV1], + exports: [LoginViaAuthRequestComponentV1], }) export class LoginModule {} diff --git a/apps/web/src/app/auth/login/login-v1.component.html b/apps/web/src/app/auth/login/login-v1.component.html deleted file mode 100644 index b41e55a03b0..00000000000 --- a/apps/web/src/app/auth/login/login-v1.component.html +++ /dev/null @@ -1,129 +0,0 @@ -
- -
- - {{ "emailAddress" | i18n }} - - -
- -
- - - {{ "rememberEmail" | i18n }} - -
- -
- -
- -
-

{{ "or" | i18n }}

- - - {{ "logInWithPasskey" | i18n }} - -
- -
- -

- {{ "newAroundHere" | i18n }} - - - {{ "createAccount" | i18n }} - -

-
- -
-
- - {{ "masterPass" | i18n }} - - - - {{ "getMasterPasswordHint" | i18n }} -
- -
- -
- -
- -
- -
- -
- - - -
- -
-

{{ "loggingInAs" | i18n }} {{ loggedEmail }}

- {{ "notYou" | i18n }} -
-
-
diff --git a/apps/web/src/app/auth/login/login-v1.component.ts b/apps/web/src/app/auth/login/login-v1.component.ts deleted file mode 100644 index 247aee4828c..00000000000 --- a/apps/web/src/app/auth/login/login-v1.component.ts +++ /dev/null @@ -1,224 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Component, NgZone, OnInit } from "@angular/core"; -import { FormBuilder } from "@angular/forms"; -import { ActivatedRoute, Router } from "@angular/router"; -import { takeUntil } from "rxjs"; -import { first } from "rxjs/operators"; - -import { LoginComponentV1 as BaseLoginComponent } from "@bitwarden/angular/auth/components/login-v1.component"; -import { FormValidationErrorsService } from "@bitwarden/angular/platform/abstractions/form-validation-errors.service"; -import { - LoginStrategyServiceAbstraction, - LoginEmailServiceAbstraction, -} from "@bitwarden/auth/common"; -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 { PolicyData } from "@bitwarden/common/admin-console/models/data/policy.data"; -import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options"; -import { Policy } from "@bitwarden/common/admin-console/models/domain/policy"; -import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength"; -import { UserId } from "@bitwarden/common/types/guid"; -import { ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; - -import { RouterService } from "../../core"; -import { AcceptOrganizationInviteService } from "../organization-invite/accept-organization.service"; -import { OrganizationInvite } from "../organization-invite/organization-invite"; - -@Component({ - selector: "app-login", - templateUrl: "login-v1.component.html", -}) -export class LoginComponentV1 extends BaseLoginComponent implements OnInit { - showResetPasswordAutoEnrollWarning = false; - enforcedPasswordPolicyOptions: MasterPasswordPolicyOptions; - policies: Policy[]; - - constructor( - private acceptOrganizationInviteService: AcceptOrganizationInviteService, - devicesApiService: DevicesApiServiceAbstraction, - appIdService: AppIdService, - loginStrategyService: LoginStrategyServiceAbstraction, - router: Router, - i18nService: I18nService, - route: ActivatedRoute, - platformUtilsService: PlatformUtilsService, - environmentService: EnvironmentService, - passwordGenerationService: PasswordGenerationServiceAbstraction, - private passwordStrengthService: PasswordStrengthServiceAbstraction, - cryptoFunctionService: CryptoFunctionService, - private policyApiService: PolicyApiServiceAbstraction, - private policyService: InternalPolicyService, - logService: LogService, - ngZone: NgZone, - protected stateService: StateService, - private routerService: RouterService, - formBuilder: FormBuilder, - formValidationErrorService: FormValidationErrorsService, - loginEmailService: LoginEmailServiceAbstraction, - ssoLoginService: SsoLoginServiceAbstraction, - toastService: ToastService, - ) { - super( - devicesApiService, - appIdService, - loginStrategyService, - router, - platformUtilsService, - i18nService, - stateService, - environmentService, - passwordGenerationService, - cryptoFunctionService, - logService, - ngZone, - formBuilder, - formValidationErrorService, - route, - loginEmailService, - ssoLoginService, - toastService, - ); - this.onSuccessfulLoginNavigate = this.goAfterLogIn; - } - - submitForm = async (showToast = true) => { - return await this.submitFormHelper(showToast); - }; - - private async submitFormHelper(showToast: boolean) { - await super.submit(showToast); - } - - async ngOnInit() { - // eslint-disable-next-line rxjs-angular/prefer-takeuntil, rxjs/no-async-subscribe - this.route.queryParams.pipe(first()).subscribe(async (qParams) => { - // If there is a query parameter called 'org', set previousUrl to `/create-organization?org=paramValue` - if (qParams.org != null) { - const route = this.router.createUrlTree(["create-organization"], { - queryParams: { plan: qParams.org }, - }); - this.routerService.setPreviousUrl(route.toString()); - } - - /** - * If there is a query parameter called 'sponsorshipToken', that means they are coming - * from an email for sponsoring a families organization. If so, then set the prevousUrl - * to `/setup/families-for-enterprise?token=paramValue` - */ - if (qParams.sponsorshipToken != null) { - const route = this.router.createUrlTree(["setup/families-for-enterprise"], { - queryParams: { token: qParams.sponsorshipToken }, - }); - this.routerService.setPreviousUrl(route.toString()); - } - - await super.ngOnInit(); - }); - - // If there's an existing org invite, use it to get the password policies - const orgInvite = await this.acceptOrganizationInviteService.getOrganizationInvite(); - if (orgInvite != null) { - await this.initPasswordPolicies(orgInvite); - } - } - - async goAfterLogIn(userId: UserId) { - const masterPassword = this.formGroup.value.masterPassword; - - // Check master password against policy - if (this.enforcedPasswordPolicyOptions != null) { - const strengthResult = this.passwordStrengthService.getPasswordStrength( - masterPassword, - this.formGroup.value.email, - ); - const masterPasswordScore = strengthResult == null ? null : strengthResult.score; - - // If invalid, save policies and require update - if ( - !this.policyService.evaluateMasterPassword( - masterPasswordScore, - masterPassword, - this.enforcedPasswordPolicyOptions, - ) - ) { - const policiesData: { [id: string]: PolicyData } = {}; - this.policies.map((p) => (policiesData[p.id] = PolicyData.fromPolicy(p))); - await this.policyService.replace(policiesData, userId); - await this.router.navigate(["update-password"]); - return; - } - } - - this.loginEmailService.clearValues(); - await this.router.navigate([this.successRoute]); - } - - async goToHint() { - await this.saveEmailSettings(); - await this.router.navigateByUrl("/hint"); - } - - async goToRegister() { - if (this.emailFormControl.valid) { - await this.router.navigate(["/signup"], { - queryParams: { email: this.emailFormControl.value }, - }); - return; - } - - await this.router.navigate(["/signup"]); - } - - protected override async handleMigrateEncryptionKey(result: AuthResult): Promise { - if (!result.requiresEncryptionKeyMigration) { - return false; - } - await this.router.navigate(["migrate-legacy-encryption"]); - return true; - } - - private async initPasswordPolicies(invite: OrganizationInvite): Promise { - try { - this.policies = await this.policyApiService.getPoliciesByToken( - invite.organizationId, - invite.token, - invite.email, - invite.organizationUserId, - ); - } catch (e) { - this.logService.error(e); - } - - if (this.policies == null) { - return; - } - - const resetPasswordPolicy = this.policyService.getResetPasswordPolicyOptions( - this.policies, - invite.organizationId, - ); - - // Set to true if policy enabled and auto-enroll enabled - this.showResetPasswordAutoEnrollWarning = - resetPasswordPolicy[1] && resetPasswordPolicy[0].autoEnrollEnabled; - - this.policyService - .masterPasswordPolicyOptions$(this.policies) - .pipe(takeUntil(this.destroy$)) - .subscribe((enforcedPasswordPolicyOptions) => { - this.enforcedPasswordPolicyOptions = enforcedPasswordPolicyOptions; - }); - } -} diff --git a/apps/web/src/app/auth/login/login.module.ts b/apps/web/src/app/auth/login/login.module.ts index a33a6b8a5a8..60c20eb4b7b 100644 --- a/apps/web/src/app/auth/login/login.module.ts +++ b/apps/web/src/app/auth/login/login.module.ts @@ -5,20 +5,17 @@ import { CheckboxModule } from "@bitwarden/components"; import { SharedModule } from "../../../app/shared"; import { LoginDecryptionOptionsComponentV1 } from "./login-decryption-options/login-decryption-options-v1.component"; -import { LoginComponentV1 } from "./login-v1.component"; import { LoginViaAuthRequestComponentV1 } from "./login-via-auth-request-v1.component"; import { LoginViaWebAuthnComponent } from "./login-via-webauthn/login-via-webauthn.component"; @NgModule({ imports: [SharedModule, CheckboxModule], declarations: [ - LoginComponentV1, LoginViaAuthRequestComponentV1, LoginDecryptionOptionsComponentV1, LoginViaWebAuthnComponent, ], exports: [ - LoginComponentV1, LoginViaAuthRequestComponentV1, LoginDecryptionOptionsComponentV1, LoginViaWebAuthnComponent, diff --git a/libs/angular/src/auth/components/environment-selector.component.ts b/libs/angular/src/auth/components/environment-selector.component.ts index c984b7f0cba..16a249dda97 100644 --- a/libs/angular/src/auth/components/environment-selector.component.ts +++ b/libs/angular/src/auth/components/environment-selector.component.ts @@ -1,5 +1,3 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore import { animate, state, style, transition, trigger } from "@angular/animations"; import { ConnectedPosition } from "@angular/cdk/overlay"; import { Component, EventEmitter, Output, Input, OnInit, OnDestroy } from "@angular/core"; @@ -7,8 +5,6 @@ import { ActivatedRoute } from "@angular/router"; import { Observable, map, Subject, takeUntil } from "rxjs"; import { SelfHostedEnvConfigDialogComponent } from "@bitwarden/auth/angular"; -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; import { EnvironmentService, Region, @@ -88,7 +84,6 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy { protected environmentService: EnvironmentService, private route: ActivatedRoute, private dialogService: DialogService, - private configService: ConfigService, private toastService: ToastService, private i18nService: I18nService, ) {} @@ -113,24 +108,18 @@ export class EnvironmentSelectorComponent implements OnInit, OnDestroy { } /** - * Opens the self-hosted settings dialog. - * - * If the `UnauthenticatedExtensionUIRefresh` feature flag is enabled, - * the self-hosted settings dialog is opened directly. Otherwise, the - * `onOpenSelfHostedSettings` event is emitted. + * Opens the self-hosted settings dialog when the self-hosted option is selected. */ - if (option === Region.SelfHosted) { - if (await this.configService.getFeatureFlag(FeatureFlag.UnauthenticatedExtensionUIRefresh)) { - if (await SelfHostedEnvConfigDialogComponent.open(this.dialogService)) { - this.toastService.showToast({ - variant: "success", - title: null, - message: this.i18nService.t("environmentSaved"), - }); - } - } else { - this.onOpenSelfHostedSettings.emit(); - } + if ( + option === Region.SelfHosted && + (await SelfHostedEnvConfigDialogComponent.open(this.dialogService)) + ) { + this.toastService.showToast({ + variant: "success", + title: "", + message: this.i18nService.t("environmentSaved"), + }); + return; } diff --git a/libs/angular/src/auth/components/login-v1.component.ts b/libs/angular/src/auth/components/login-v1.component.ts deleted file mode 100644 index 26903716edf..00000000000 --- a/libs/angular/src/auth/components/login-v1.component.ts +++ /dev/null @@ -1,401 +0,0 @@ -// FIXME: Update this file to be type safe and remove this and next line -// @ts-strict-ignore -import { Directive, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from "@angular/core"; -import { FormBuilder, Validators } from "@angular/forms"; -import { ActivatedRoute, NavigationSkipped, Router } from "@angular/router"; -import { Subject, firstValueFrom, of } from "rxjs"; -import { switchMap, take, takeUntil } from "rxjs/operators"; - -import { - LoginStrategyServiceAbstraction, - LoginEmailServiceAbstraction, - PasswordLoginCredentials, -} from "@bitwarden/auth/common"; -import { DevicesApiServiceAbstraction } from "@bitwarden/common/auth/abstractions/devices-api.service.abstraction"; -import { SsoLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/sso-login.service.abstraction"; -import { AuthResult } from "@bitwarden/common/auth/models/domain/auth-result"; -import { ForceSetPasswordReason } from "@bitwarden/common/auth/models/domain/force-set-password-reason"; -import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; -import { CryptoFunctionService } from "@bitwarden/common/platform/abstractions/crypto-function.service"; -import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service"; -import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; -import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; -import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service"; -import { StateService } from "@bitwarden/common/platform/abstractions/state.service"; -import { Utils } from "@bitwarden/common/platform/misc/utils"; -import { UserId } from "@bitwarden/common/types/guid"; -import { ToastService } from "@bitwarden/components"; -import { PasswordGenerationServiceAbstraction } from "@bitwarden/generator-legacy"; - -import { - AllValidationErrors, - FormValidationErrorsService, -} from "../../platform/abstractions/form-validation-errors.service"; - -import { CaptchaProtectedComponent } from "./captcha-protected.component"; - -@Directive() -export class LoginComponentV1 extends CaptchaProtectedComponent implements OnInit, OnDestroy { - @ViewChild("masterPasswordInput", { static: true }) masterPasswordInput: ElementRef; - - showPassword = false; - formPromise: Promise; - - onSuccessfulLogin: () => Promise; - onSuccessfulLoginNavigate: (userId: UserId) => Promise; - onSuccessfulLoginTwoFactorNavigate: () => Promise; - onSuccessfulLoginForceResetNavigate: () => Promise; - - showLoginWithDevice: boolean; - validatedEmail = false; - paramEmailSet = false; - - get emailFormControl() { - return this.formGroup.controls.email; - } - - formGroup = this.formBuilder.nonNullable.group({ - email: ["", [Validators.required, Validators.email]], - masterPassword: [ - "", - [Validators.required, Validators.minLength(Utils.originalMinimumPasswordLength)], - ], - rememberEmail: [false], - }); - - protected twoFactorRoute = "2fa"; - protected successRoute = "vault"; - protected forcePasswordResetRoute = "update-temp-password"; - - protected destroy$ = new Subject(); - - get loggedEmail() { - return this.formGroup.controls.email.value; - } - - constructor( - protected devicesApiService: DevicesApiServiceAbstraction, - protected appIdService: AppIdService, - protected loginStrategyService: LoginStrategyServiceAbstraction, - protected router: Router, - platformUtilsService: PlatformUtilsService, - i18nService: I18nService, - protected stateService: StateService, - environmentService: EnvironmentService, - protected passwordGenerationService: PasswordGenerationServiceAbstraction, - protected cryptoFunctionService: CryptoFunctionService, - protected logService: LogService, - protected ngZone: NgZone, - protected formBuilder: FormBuilder, - protected formValidationErrorService: FormValidationErrorsService, - protected route: ActivatedRoute, - protected loginEmailService: LoginEmailServiceAbstraction, - protected ssoLoginService: SsoLoginServiceAbstraction, - protected toastService: ToastService, - ) { - super(environmentService, i18nService, platformUtilsService, toastService); - } - - async ngOnInit() { - this.route?.queryParams - .pipe( - switchMap((params) => { - if (!params) { - // If no params,loadEmailSettings from state - return this.loadEmailSettings(); - } - - const queryParamsEmail = params.email; - - if (queryParamsEmail != null && queryParamsEmail.indexOf("@") > -1) { - this.formGroup.controls.email.setValue(queryParamsEmail); - this.paramEmailSet = true; - } - - // If paramEmailSet is false, loadEmailSettings from state - return this.paramEmailSet ? of(null) : this.loadEmailSettings(); - }), - takeUntil(this.destroy$), - ) - .subscribe(); - - // If the user navigates to /login from /login, reset the validatedEmail flag - // This should bring the user back to the login screen with the email field - this.router.events.pipe(takeUntil(this.destroy$)).subscribe((event) => { - if (event instanceof NavigationSkipped && event.url === "/login") { - this.validatedEmail = false; - } - }); - - // Backup check to handle unknown case where activatedRoute is not available - // This shouldn't happen under normal circumstances - if (!this.route) { - await this.loadEmailSettings(); - } - } - - ngOnDestroy() { - this.destroy$.next(); - this.destroy$.complete(); - } - - async submit(showToast = true) { - await this.setupCaptcha(); - - this.formGroup.markAllAsTouched(); - - //web - if (this.formGroup.invalid && !showToast) { - return; - } - - //desktop, browser; This should be removed once all clients use reactive forms - if (this.formGroup.invalid && showToast) { - const errorText = this.getErrorToastMessage(); - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccurred"), - message: errorText, - }); - return; - } - - try { - const credentials = new PasswordLoginCredentials( - this.formGroup.controls.email.value, - this.formGroup.controls.masterPassword.value, - this.captchaToken, - undefined, - ); - - this.formPromise = this.loginStrategyService.logIn(credentials); - const response = await this.formPromise; - - await this.saveEmailSettings(); - - if (this.handleCaptchaRequired(response)) { - return; - } else if (await this.handleMigrateEncryptionKey(response)) { - return; - } else if (response.requiresTwoFactor) { - if (this.onSuccessfulLoginTwoFactorNavigate != null) { - // 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.onSuccessfulLoginTwoFactorNavigate(); - } else { - // 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([this.twoFactorRoute]); - } - } else if (response.forcePasswordReset != ForceSetPasswordReason.None) { - if (this.onSuccessfulLoginForceResetNavigate != null) { - // 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.onSuccessfulLoginForceResetNavigate(); - } else { - 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. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([this.forcePasswordResetRoute]); - } - } else { - if (this.onSuccessfulLogin != null) { - // 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.onSuccessfulLogin(); - } - - if (this.onSuccessfulLoginNavigate != null) { - // 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.onSuccessfulLoginNavigate(response.userId); - } else { - 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. - // eslint-disable-next-line @typescript-eslint/no-floating-promises - this.router.navigate([this.successRoute]); - } - } - } catch (e) { - this.logService.error(e); - } - } - - togglePassword() { - this.showPassword = !this.showPassword; - if (this.ngZone.isStable) { - document.getElementById("masterPassword").focus(); - } else { - this.ngZone.onStable - .pipe(take(1)) - .subscribe(() => document.getElementById("masterPassword").focus()); - } - } - - async startAuthRequestLogin() { - this.formGroup.get("masterPassword")?.clearValidators(); - this.formGroup.get("masterPassword")?.updateValueAndValidity(); - - if (!this.formGroup.valid) { - return; - } - - await this.saveEmailSettings(); - await this.router.navigate(["/login-with-device"]); - } - - async launchSsoBrowser(clientId: string, ssoRedirectUri: string) { - // Save off email for SSO - await this.ssoLoginService.setSsoEmail(this.formGroup.value.email); - - // Generate necessary sso params - const passwordOptions: any = { - type: "password", - length: 64, - uppercase: true, - lowercase: true, - numbers: true, - special: false, - }; - const state = await this.passwordGenerationService.generatePassword(passwordOptions); - const ssoCodeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions); - const codeVerifierHash = await this.cryptoFunctionService.hash(ssoCodeVerifier, "sha256"); - const codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash); - - // Save sso params - await this.ssoLoginService.setSsoState(state); - await this.ssoLoginService.setCodeVerifier(ssoCodeVerifier); - - // Build URI - const env = await firstValueFrom(this.environmentService.environment$); - const webUrl = env.getWebVaultUrl(); - - // Launch browser - this.platformUtilsService.launchUri( - webUrl + - "/#/sso?clientId=" + - clientId + - "&redirectUri=" + - encodeURIComponent(ssoRedirectUri) + - "&state=" + - state + - "&codeChallenge=" + - codeChallenge + - "&email=" + - encodeURIComponent(this.formGroup.controls.email.value), - ); - } - - async validateEmail() { - this.formGroup.controls.email.markAsTouched(); - const emailValid = this.formGroup.get("email").valid; - - if (emailValid) { - this.toggleValidateEmail(true); - await this.getLoginWithDevice(this.loggedEmail); - } - } - - toggleValidateEmail(value: boolean) { - this.validatedEmail = value; - if (!this.validatedEmail) { - // Reset master password only when going from validated to not validated - // so that autofill can work properly - this.formGroup.controls.masterPassword.reset(); - } else { - // Mark MP as untouched so that, when users enter email and hit enter, - // the MP field doesn't load with validation errors - this.formGroup.controls.masterPassword.markAsUntouched(); - - // When email is validated, focus on master password after - // waiting for input to be rendered - if (this.ngZone.isStable) { - this.masterPasswordInput?.nativeElement?.focus(); - } else { - this.ngZone.onStable.pipe(take(1)).subscribe(() => { - this.masterPasswordInput?.nativeElement?.focus(); - }); - } - } - } - - private async loadEmailSettings() { - // Try to load from memory first - const email = await firstValueFrom(this.loginEmailService.loginEmail$); - const rememberEmail = this.loginEmailService.getRememberEmail(); - - if (email) { - this.formGroup.controls.email.setValue(email); - this.formGroup.controls.rememberEmail.setValue(rememberEmail); - } else { - // If not in memory, check email on disk - const storedEmail = await firstValueFrom(this.loginEmailService.storedEmail$); - if (storedEmail) { - // If we have a stored email, rememberEmail should default to true - this.formGroup.controls.email.setValue(storedEmail); - this.formGroup.controls.rememberEmail.setValue(true); - } - } - } - - protected async saveEmailSettings() { - // Save off email for SSO - await this.ssoLoginService.setSsoEmail(this.formGroup.value.email); - - this.loginEmailService.setLoginEmail(this.formGroup.value.email); - this.loginEmailService.setRememberEmail(this.formGroup.value.rememberEmail); - await this.loginEmailService.saveEmailSettings(); - } - - // Legacy accounts used the master key to encrypt data. Migration is required but only performed on web - protected async handleMigrateEncryptionKey(result: AuthResult): Promise { - if (!result.requiresEncryptionKeyMigration) { - return false; - } - - this.toastService.showToast({ - variant: "error", - title: this.i18nService.t("errorOccured"), - message: this.i18nService.t("encryptionKeyMigrationRequired"), - }); - return true; - } - - private getErrorToastMessage() { - const error: AllValidationErrors = this.formValidationErrorService - .getFormValidationErrors(this.formGroup.controls) - .shift(); - - if (error) { - switch (error.errorName) { - case "email": - return this.i18nService.t("invalidEmail"); - case "minlength": - return this.i18nService.t("masterPasswordMinlength", Utils.originalMinimumPasswordLength); - default: - return this.i18nService.t(this.errorTag(error)); - } - } - - return; - } - - private errorTag(error: AllValidationErrors): string { - const name = error.errorName.charAt(0).toUpperCase() + error.errorName.slice(1); - return `${error.controlName}${name}`; - } - - async getLoginWithDevice(email: string) { - try { - const deviceIdentifier = await this.appIdService.getAppId(); - this.showLoginWithDevice = await this.devicesApiService.getKnownDevice( - email, - deviceIdentifier, - ); - // FIXME: Remove when updating file. Eslint update - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e) { - this.showLoginWithDevice = false; - } - } -} diff --git a/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.spec.ts b/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.spec.ts deleted file mode 100644 index 887f528d547..00000000000 --- a/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.spec.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { TestBed } from "@angular/core/testing"; -import { Navigation, Router, UrlTree } from "@angular/router"; -import { mock, MockProxy } from "jest-mock-extended"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -import { unauthUiRefreshRedirect } from "./unauth-ui-refresh-redirect"; - -describe("unauthUiRefreshRedirect", () => { - let configService: MockProxy; - let router: MockProxy; - - beforeEach(() => { - configService = mock(); - router = mock(); - - TestBed.configureTestingModule({ - providers: [ - { provide: ConfigService, useValue: configService }, - { provide: Router, useValue: router }, - ], - }); - }); - - it("returns true when UnauthenticatedExtensionUIRefresh flag is disabled", async () => { - configService.getFeatureFlag.mockResolvedValue(false); - - const result = await TestBed.runInInjectionContext(() => - unauthUiRefreshRedirect("/redirect")(), - ); - - expect(result).toBe(true); - expect(configService.getFeatureFlag).toHaveBeenCalledWith( - FeatureFlag.UnauthenticatedExtensionUIRefresh, - ); - expect(router.parseUrl).not.toHaveBeenCalled(); - }); - - it("returns UrlTree when UnauthenticatedExtensionUIRefresh flag is enabled and preserves query params", async () => { - configService.getFeatureFlag.mockResolvedValue(true); - - const urlTree = new UrlTree(); - urlTree.queryParams = { test: "test" }; - - const navigation: Navigation = { - extras: {}, - id: 0, - initialUrl: new UrlTree(), - extractedUrl: urlTree, - trigger: "imperative", - previousNavigation: undefined, - }; - - router.getCurrentNavigation.mockReturnValue(navigation); - - await TestBed.runInInjectionContext(() => unauthUiRefreshRedirect("/redirect")()); - - expect(configService.getFeatureFlag).toHaveBeenCalledWith( - FeatureFlag.UnauthenticatedExtensionUIRefresh, - ); - expect(router.createUrlTree).toHaveBeenCalledWith(["/redirect"], { - queryParams: urlTree.queryParams, - }); - }); -}); diff --git a/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.ts b/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.ts deleted file mode 100644 index 2cb53d5324f..00000000000 --- a/libs/angular/src/auth/functions/unauth-ui-refresh-redirect.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { inject } from "@angular/core"; -import { UrlTree, Router } from "@angular/router"; - -import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum"; -import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; - -/** - * Helper function to redirect to a new URL based on the UnauthenticatedExtensionUIRefresh feature flag. - * @param redirectUrl - The URL to redirect to if the UnauthenticatedExtensionUIRefresh flag is enabled. - */ -export function unauthUiRefreshRedirect(redirectUrl: string): () => Promise { - return async () => { - const configService = inject(ConfigService); - const router = inject(Router); - const shouldRedirect = await configService.getFeatureFlag( - FeatureFlag.UnauthenticatedExtensionUIRefresh, - ); - if (shouldRedirect) { - const currentNavigation = router.getCurrentNavigation(); - const queryParams = currentNavigation?.extractedUrl?.queryParams || {}; - - // Preserve query params when redirecting as it is likely that the refreshed component - // will be consuming the same query params. - return router.createUrlTree([redirectUrl], { queryParams }); - } else { - return true; - } - }; -}