import { Component, NgZone } from "@angular/core"; import { FormBuilder } from "@angular/forms"; import { ActivatedRoute, Router } from "@angular/router"; import { firstValueFrom } from "rxjs"; import { LoginComponent as BaseLoginComponent } from "@bitwarden/angular/auth/components/login.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 { WebAuthnLoginServiceAbstraction } from "@bitwarden/common/auth/abstractions/webauthn/webauthn-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 { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password"; import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction"; import { flagEnabled } from "../../platform/flags"; @Component({ selector: "app-login", templateUrl: "login.component.html", }) export class LoginComponent extends BaseLoginComponent { showPasswordless = false; 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, webAuthnLoginService: WebAuthnLoginServiceAbstraction, ) { super( devicesApiService, appIdService, loginStrategyService, router, platformUtilsService, i18nService, stateService, environmentService, passwordGenerationService, cryptoFunctionService, logService, ngZone, formBuilder, formValidationErrorService, route, loginEmailService, ssoLoginService, webAuthnLoginService, ); super.onSuccessfulLogin = async () => { await syncService.fullSync(true); }; super.successRoute = "/tabs/vault"; this.showPasswordless = flagEnabled("showPasswordless"); if (this.showPasswordless) { this.formGroup.controls.email.setValue(this.loginEmailService.getEmail()); 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. // eslint-disable-next-line @typescript-eslint/no-floating-promises 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); await this.loginEmailService.saveEmailSettings(); // 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), ); } }