mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 06:13:38 +00:00
Auth/ps 2298 reorg auth (#4564)
* Move auth service factories to Auth team * Move authentication componenets to Auth team * Move auth guard services to Auth team * Move Duo content script to Auth team * Move auth CLI commands to Auth team * Move Desktop Account components to Auth Team * Move Desktop guards to Auth team * Move two-factor provider images to Auth team * Move web Accounts components to Auth Team * Move web settings components to Auth Team * Move web two factor images to Auth Team * Fix missed import changes for Auth Team * Fix Linting errors * Fix missed CLI imports * Fix missed Desktop imports * Revert images move * Fix missed imports in Web * Move angular lib components to Auth Team * Move angular auth guards to Auth team * Move strategy specs to Auth team * Update .eslintignore for new paths * Move lib common abstractions to Auth team * Move services to Auth team * Move common lib enums to Auth team * Move webauthn iframe to Auth team * Move lib common domain models to Auth team * Move common lib requests to Auth team * Move response models to Auth team * Clean up whitelist * Move bit web components to Auth team * Move SSO and SCIM files to Auth team * Revert move SCIM to Auth team SCIM belongs to Admin Console team * Move captcha to Auth team * Move key connector to Auth team * Move emergency access to auth team * Delete extra file * linter fixes * Move kdf config to auth team * Fix whitelist * Fix duo autoformat * Complete two factor provider request move * Fix whitelist names * Fix login capitalization * Revert hint dependency reordering * Revert hint dependency reordering * Revert hint component This components is being picked up as a move between clients * Move web hint component to Auth team * Move new files to auth team * Fix desktop build * Fix browser build
This commit is contained in:
@@ -1,53 +0,0 @@
|
||||
import { Directive, Input } from "@angular/core";
|
||||
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { CaptchaIFrame } from "@bitwarden/common/misc/captcha_iframe";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
|
||||
@Directive()
|
||||
export abstract class CaptchaProtectedComponent {
|
||||
@Input() captchaSiteKey: string = null;
|
||||
captchaToken: string = null;
|
||||
captcha: CaptchaIFrame;
|
||||
|
||||
constructor(
|
||||
protected environmentService: EnvironmentService,
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService
|
||||
) {}
|
||||
|
||||
async setupCaptcha() {
|
||||
const webVaultUrl = this.environmentService.getWebVaultUrl();
|
||||
|
||||
this.captcha = new CaptchaIFrame(
|
||||
window,
|
||||
webVaultUrl,
|
||||
this.i18nService,
|
||||
(token: string) => {
|
||||
this.captchaToken = token;
|
||||
},
|
||||
(error: string) => {
|
||||
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), error);
|
||||
},
|
||||
(info: string) => {
|
||||
this.platformUtilsService.showToast("info", this.i18nService.t("info"), info);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
showCaptcha() {
|
||||
return !Utils.isNullOrWhitespace(this.captchaSiteKey);
|
||||
}
|
||||
|
||||
protected handleCaptchaRequired(response: { captchaSiteKey: string }): boolean {
|
||||
if (Utils.isNullOrWhitespace(response.captchaSiteKey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.captchaSiteKey = response.captchaSiteKey;
|
||||
this.captcha.init(response.captchaSiteKey);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
import { Directive, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { KdfType } from "@bitwarden/common/enums/kdfType";
|
||||
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
||||
import { KdfConfig } from "@bitwarden/common/models/domain/kdf-config";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/master-password-policy-options";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
||||
|
||||
import { PasswordColorText } from "../shared/components/password-strength/password-strength.component";
|
||||
|
||||
@Directive()
|
||||
export class ChangePasswordComponent implements OnInit, OnDestroy {
|
||||
masterPassword: string;
|
||||
masterPasswordRetype: string;
|
||||
formPromise: Promise<any>;
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
passwordStrengthResult: any;
|
||||
color: string;
|
||||
text: string;
|
||||
|
||||
protected email: string;
|
||||
protected kdf: KdfType;
|
||||
protected kdfConfig: KdfConfig;
|
||||
|
||||
protected destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
protected i18nService: I18nService,
|
||||
protected cryptoService: CryptoService,
|
||||
protected messagingService: MessagingService,
|
||||
protected passwordGenerationService: PasswordGenerationService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected policyService: PolicyService,
|
||||
protected stateService: StateService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.email = await this.stateService.getEmail();
|
||||
this.policyService
|
||||
.masterPasswordPolicyOptions$()
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(
|
||||
(enforcedPasswordPolicyOptions) =>
|
||||
(this.enforcedPolicyOptions ??= enforcedPasswordPolicyOptions)
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (!(await this.strongPassword())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await this.setupSubmitActions())) {
|
||||
return;
|
||||
}
|
||||
|
||||
const email = await this.stateService.getEmail();
|
||||
if (this.kdf == null) {
|
||||
this.kdf = await this.stateService.getKdfType();
|
||||
}
|
||||
if (this.kdfConfig == null) {
|
||||
this.kdfConfig = await this.stateService.getKdfConfig();
|
||||
}
|
||||
const key = await this.cryptoService.makeKey(
|
||||
this.masterPassword,
|
||||
email.trim().toLowerCase(),
|
||||
this.kdf,
|
||||
this.kdfConfig
|
||||
);
|
||||
const masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
|
||||
|
||||
let encKey: [SymmetricCryptoKey, EncString] = null;
|
||||
const existingEncKey = await this.cryptoService.getEncKey();
|
||||
if (existingEncKey == null) {
|
||||
encKey = await this.cryptoService.makeEncKey(key);
|
||||
} else {
|
||||
encKey = await this.cryptoService.remakeEncKey(key);
|
||||
}
|
||||
|
||||
await this.performSubmitActions(masterPasswordHash, key, encKey);
|
||||
}
|
||||
|
||||
async setupSubmitActions(): Promise<boolean> {
|
||||
// Override in sub-class
|
||||
// Can be used for additional validation and/or other processes the should occur before changing passwords
|
||||
return true;
|
||||
}
|
||||
|
||||
async performSubmitActions(
|
||||
masterPasswordHash: string,
|
||||
key: SymmetricCryptoKey,
|
||||
encKey: [SymmetricCryptoKey, EncString]
|
||||
) {
|
||||
// Override in sub-class
|
||||
}
|
||||
|
||||
async strongPassword(): Promise<boolean> {
|
||||
if (this.masterPassword == null || this.masterPassword === "") {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("masterPasswordRequired")
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (this.masterPassword.length < 8) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("masterPasswordMinlength")
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (this.masterPassword !== this.masterPasswordRetype) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("masterPassDoesntMatch")
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const strengthResult = this.passwordStrengthResult;
|
||||
|
||||
if (
|
||||
this.enforcedPolicyOptions != null &&
|
||||
!this.policyService.evaluateMasterPassword(
|
||||
strengthResult.score,
|
||||
this.masterPassword,
|
||||
this.enforcedPolicyOptions
|
||||
)
|
||||
) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("masterPasswordPolicyRequirementsNotMet")
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strengthResult != null && strengthResult.score < 3) {
|
||||
const result = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("weakMasterPasswordDesc"),
|
||||
this.i18nService.t("weakMasterPassword"),
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async logOut() {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("logOutConfirmation"),
|
||||
this.i18nService.t("logOut"),
|
||||
this.i18nService.t("logOut"),
|
||||
this.i18nService.t("cancel")
|
||||
);
|
||||
if (confirmed) {
|
||||
this.messagingService.send("logout");
|
||||
}
|
||||
}
|
||||
|
||||
getStrengthResult(result: any) {
|
||||
this.passwordStrengthResult = result;
|
||||
}
|
||||
|
||||
getPasswordScoreText(event: PasswordColorText) {
|
||||
this.color = event.color;
|
||||
this.text = event.text;
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import { Directive, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { LoginService } from "@bitwarden/common/abstractions/login.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { PasswordHintRequest } from "@bitwarden/common/models/request/password-hint.request";
|
||||
|
||||
@Directive()
|
||||
export class HintComponent implements OnInit {
|
||||
email = "";
|
||||
formPromise: Promise<any>;
|
||||
|
||||
protected successRoute = "login";
|
||||
protected onSuccessfulSubmit: () => void;
|
||||
|
||||
constructor(
|
||||
protected router: Router,
|
||||
protected i18nService: I18nService,
|
||||
protected apiService: ApiService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
private logService: LogService,
|
||||
private loginService: LoginService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.email = this.loginService.getEmail() ?? "";
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.email == null || this.email === "") {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("emailRequired")
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (this.email.indexOf("@") === -1) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("invalidEmail")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.formPromise = this.apiService.postPasswordHint(new PasswordHintRequest(this.email));
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("masterPassSent"));
|
||||
if (this.onSuccessfulSubmit != null) {
|
||||
this.onSuccessfulSubmit();
|
||||
} else if (this.router != null) {
|
||||
this.router.navigate([this.successRoute]);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,286 +0,0 @@
|
||||
import { Directive, NgZone, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { Subject } from "rxjs";
|
||||
import { concatMap, take, takeUntil } from "rxjs/operators";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { VaultTimeoutService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeout.service";
|
||||
import { VaultTimeoutSettingsService } from "@bitwarden/common/abstractions/vaultTimeout/vaultTimeoutSettings.service";
|
||||
import { HashPurpose } from "@bitwarden/common/enums/hashPurpose";
|
||||
import { KeySuffixOptions } from "@bitwarden/common/enums/keySuffixOptions";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
||||
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secret-verification.request";
|
||||
|
||||
@Directive()
|
||||
export class LockComponent implements OnInit, OnDestroy {
|
||||
masterPassword = "";
|
||||
pin = "";
|
||||
showPassword = false;
|
||||
email: string;
|
||||
pinLock = false;
|
||||
webVaultHostname = "";
|
||||
formPromise: Promise<any>;
|
||||
supportsBiometric: boolean;
|
||||
biometricLock: boolean;
|
||||
biometricText: string;
|
||||
hideInput: boolean;
|
||||
|
||||
protected successRoute = "vault";
|
||||
protected onSuccessfulSubmit: () => Promise<void>;
|
||||
|
||||
private invalidPinAttempts = 0;
|
||||
private pinSet: [boolean, boolean];
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
protected router: Router,
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected messagingService: MessagingService,
|
||||
protected cryptoService: CryptoService,
|
||||
protected vaultTimeoutService: VaultTimeoutService,
|
||||
protected vaultTimeoutSettingsService: VaultTimeoutSettingsService,
|
||||
protected environmentService: EnvironmentService,
|
||||
protected stateService: StateService,
|
||||
protected apiService: ApiService,
|
||||
protected logService: LogService,
|
||||
private keyConnectorService: KeyConnectorService,
|
||||
protected ngZone: NgZone
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.stateService.activeAccount$
|
||||
.pipe(
|
||||
concatMap(async () => {
|
||||
await this.load();
|
||||
}),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
async submit() {
|
||||
if (this.pinLock) {
|
||||
return await this.handlePinRequiredUnlock();
|
||||
}
|
||||
|
||||
await this.handleMasterPasswordRequiredUnlock();
|
||||
}
|
||||
|
||||
async logOut() {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("logOutConfirmation"),
|
||||
this.i18nService.t("logOut"),
|
||||
this.i18nService.t("logOut"),
|
||||
this.i18nService.t("cancel")
|
||||
);
|
||||
if (confirmed) {
|
||||
this.messagingService.send("logout");
|
||||
}
|
||||
}
|
||||
|
||||
async unlockBiometric(): Promise<boolean> {
|
||||
if (!this.biometricLock) {
|
||||
return;
|
||||
}
|
||||
|
||||
const success = (await this.cryptoService.getKey(KeySuffixOptions.Biometric)) != null;
|
||||
|
||||
if (success) {
|
||||
await this.doContinue();
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
togglePassword() {
|
||||
this.showPassword = !this.showPassword;
|
||||
const input = document.getElementById(this.pinLock ? "pin" : "masterPassword");
|
||||
if (this.ngZone.isStable) {
|
||||
input.focus();
|
||||
} else {
|
||||
this.ngZone.onStable.pipe(take(1)).subscribe(() => input.focus());
|
||||
}
|
||||
}
|
||||
|
||||
private async handlePinRequiredUnlock() {
|
||||
if (this.pin == null || this.pin === "") {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("pinRequired")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
return await this.doUnlockWithPin();
|
||||
}
|
||||
|
||||
private async doUnlockWithPin() {
|
||||
let failed = true;
|
||||
try {
|
||||
const kdf = await this.stateService.getKdfType();
|
||||
const kdfConfig = await this.stateService.getKdfConfig();
|
||||
if (this.pinSet[0]) {
|
||||
const key = await this.cryptoService.makeKeyFromPin(
|
||||
this.pin,
|
||||
this.email,
|
||||
kdf,
|
||||
kdfConfig,
|
||||
await this.stateService.getDecryptedPinProtected()
|
||||
);
|
||||
const encKey = await this.cryptoService.getEncKey(key);
|
||||
const protectedPin = await this.stateService.getProtectedPin();
|
||||
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
|
||||
failed = decPin !== this.pin;
|
||||
if (!failed) {
|
||||
await this.setKeyAndContinue(key);
|
||||
}
|
||||
} else {
|
||||
const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfConfig);
|
||||
failed = false;
|
||||
await this.setKeyAndContinue(key);
|
||||
}
|
||||
} catch {
|
||||
failed = true;
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
this.invalidPinAttempts++;
|
||||
if (this.invalidPinAttempts >= 5) {
|
||||
this.messagingService.send("logout");
|
||||
return;
|
||||
}
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("invalidPin")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleMasterPasswordRequiredUnlock() {
|
||||
if (this.masterPassword == null || this.masterPassword === "") {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("masterPasswordRequired")
|
||||
);
|
||||
return;
|
||||
}
|
||||
await this.doUnlockWithMasterPassword();
|
||||
}
|
||||
|
||||
private async doUnlockWithMasterPassword() {
|
||||
const kdf = await this.stateService.getKdfType();
|
||||
const kdfConfig = await this.stateService.getKdfConfig();
|
||||
|
||||
const key = await this.cryptoService.makeKey(this.masterPassword, this.email, kdf, kdfConfig);
|
||||
const storedKeyHash = await this.cryptoService.getKeyHash();
|
||||
|
||||
let passwordValid = false;
|
||||
|
||||
if (storedKeyHash != null) {
|
||||
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, key);
|
||||
} else {
|
||||
const request = new SecretVerificationRequest();
|
||||
const serverKeyHash = await this.cryptoService.hashPassword(
|
||||
this.masterPassword,
|
||||
key,
|
||||
HashPurpose.ServerAuthorization
|
||||
);
|
||||
request.masterPasswordHash = serverKeyHash;
|
||||
try {
|
||||
this.formPromise = this.apiService.postAccountVerifyPassword(request);
|
||||
await this.formPromise;
|
||||
passwordValid = true;
|
||||
const localKeyHash = await this.cryptoService.hashPassword(
|
||||
this.masterPassword,
|
||||
key,
|
||||
HashPurpose.LocalAuthorization
|
||||
);
|
||||
await this.cryptoService.setKeyHash(localKeyHash);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!passwordValid) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("invalidMasterPassword")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pinSet[0]) {
|
||||
const protectedPin = await this.stateService.getProtectedPin();
|
||||
const encKey = await this.cryptoService.getEncKey(key);
|
||||
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
|
||||
const pinKey = await this.cryptoService.makePinKey(decPin, this.email, kdf, kdfConfig);
|
||||
await this.stateService.setDecryptedPinProtected(
|
||||
await this.cryptoService.encrypt(key.key, pinKey)
|
||||
);
|
||||
}
|
||||
await this.setKeyAndContinue(key);
|
||||
}
|
||||
private async setKeyAndContinue(key: SymmetricCryptoKey) {
|
||||
await this.cryptoService.setKey(key);
|
||||
await this.doContinue();
|
||||
}
|
||||
|
||||
private async doContinue() {
|
||||
await this.stateService.setEverBeenUnlocked(true);
|
||||
const disableFavicon = await this.stateService.getDisableFavicon();
|
||||
await this.stateService.setDisableFavicon(!!disableFavicon);
|
||||
this.messagingService.send("unlocked");
|
||||
if (this.onSuccessfulSubmit != null) {
|
||||
await this.onSuccessfulSubmit();
|
||||
} else if (this.router != null) {
|
||||
this.router.navigate([this.successRoute]);
|
||||
}
|
||||
}
|
||||
|
||||
private async load() {
|
||||
this.pinSet = await this.vaultTimeoutSettingsService.isPinLockSet();
|
||||
this.pinLock =
|
||||
(this.pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) ||
|
||||
this.pinSet[1];
|
||||
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
|
||||
this.biometricLock =
|
||||
(await this.vaultTimeoutSettingsService.isBiometricLockSet()) &&
|
||||
((await this.cryptoService.hasKeyStored(KeySuffixOptions.Biometric)) ||
|
||||
!this.platformUtilsService.supportsSecureStorage());
|
||||
this.biometricText = await this.stateService.getBiometricText();
|
||||
this.email = await this.stateService.getEmail();
|
||||
const usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
|
||||
this.hideInput = usesKeyConnector && !this.pinLock;
|
||||
|
||||
// Users with key connector and without biometric or pin has no MP to unlock using
|
||||
if (usesKeyConnector && !(this.biometricLock || this.pinLock)) {
|
||||
await this.vaultTimeoutService.logOut();
|
||||
}
|
||||
|
||||
const webVaultUrl = this.environmentService.getWebVaultUrl();
|
||||
const vaultUrl =
|
||||
webVaultUrl === "https://vault.bitwarden.com" ? "https://bitwarden.com" : webVaultUrl;
|
||||
this.webVaultHostname = Utils.getHostname(vaultUrl);
|
||||
}
|
||||
}
|
||||
@@ -1,210 +0,0 @@
|
||||
import { Directive, OnDestroy, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
import { Subject, takeUntil } from "rxjs";
|
||||
|
||||
import { AnonymousHubService } from "@bitwarden/common/abstractions/anonymousHub.service";
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AppIdService } from "@bitwarden/common/abstractions/appId.service";
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { LoginService } from "@bitwarden/common/abstractions/login.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { ValidationService } from "@bitwarden/common/abstractions/validation.service";
|
||||
import { AuthRequestType } from "@bitwarden/common/enums/authRequestType";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { PasswordlessLogInCredentials } from "@bitwarden/common/models/domain/log-in-credentials";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
||||
import { PasswordlessCreateAuthRequest } from "@bitwarden/common/models/request/passwordless-create-auth.request";
|
||||
import { AuthRequestResponse } from "@bitwarden/common/models/response/auth-request.response";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
|
||||
import { CaptchaProtectedComponent } from "./captchaProtected.component";
|
||||
|
||||
@Directive()
|
||||
export class LoginWithDeviceComponent
|
||||
extends CaptchaProtectedComponent
|
||||
implements OnInit, OnDestroy
|
||||
{
|
||||
private destroy$ = new Subject<void>();
|
||||
email: string;
|
||||
showResendNotification = false;
|
||||
passwordlessRequest: PasswordlessCreateAuthRequest;
|
||||
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
|
||||
onSuccessfulLogin: () => Promise<any>;
|
||||
onSuccessfulLoginNavigate: () => Promise<any>;
|
||||
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
|
||||
|
||||
protected twoFactorRoute = "2fa";
|
||||
protected successRoute = "vault";
|
||||
protected forcePasswordResetRoute = "update-temp-password";
|
||||
private resendTimeout = 12000;
|
||||
private authRequestKeyPair: [publicKey: ArrayBuffer, privateKey: ArrayBuffer];
|
||||
|
||||
constructor(
|
||||
protected router: Router,
|
||||
private cryptoService: CryptoService,
|
||||
private cryptoFunctionService: CryptoFunctionService,
|
||||
private appIdService: AppIdService,
|
||||
private passwordGenerationService: PasswordGenerationService,
|
||||
private apiService: ApiService,
|
||||
private authService: AuthService,
|
||||
private logService: LogService,
|
||||
environmentService: EnvironmentService,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
private anonymousHubService: AnonymousHubService,
|
||||
private validationService: ValidationService,
|
||||
private stateService: StateService,
|
||||
private loginService: LoginService
|
||||
) {
|
||||
super(environmentService, i18nService, platformUtilsService);
|
||||
|
||||
const navigation = this.router.getCurrentNavigation();
|
||||
if (navigation) {
|
||||
this.email = navigation.extras?.state?.email;
|
||||
}
|
||||
|
||||
//gets signalR push notification
|
||||
this.authService
|
||||
.getPushNotifcationObs$()
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((id) => {
|
||||
this.confirmResponse(id);
|
||||
});
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
if (!this.email) {
|
||||
this.router.navigate(["/login"]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.startPasswordlessLogin();
|
||||
}
|
||||
|
||||
async startPasswordlessLogin() {
|
||||
this.showResendNotification = false;
|
||||
|
||||
try {
|
||||
await this.buildAuthRequest();
|
||||
const reqResponse = await this.apiService.postAuthRequest(this.passwordlessRequest);
|
||||
|
||||
if (reqResponse.id) {
|
||||
this.anonymousHubService.createHubConnection(reqResponse.id);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.showResendNotification = true;
|
||||
}, this.resendTimeout);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
this.anonymousHubService.stopHubConnection();
|
||||
}
|
||||
|
||||
private async confirmResponse(requestId: string) {
|
||||
try {
|
||||
const response = await this.apiService.getAuthResponse(
|
||||
requestId,
|
||||
this.passwordlessRequest.accessCode
|
||||
);
|
||||
|
||||
if (!response.requestApproved) {
|
||||
return;
|
||||
}
|
||||
|
||||
const credentials = await this.buildLoginCredntials(requestId, response);
|
||||
const loginResponse = await this.authService.logIn(credentials);
|
||||
|
||||
if (loginResponse.requiresTwoFactor) {
|
||||
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
|
||||
this.onSuccessfulLoginTwoFactorNavigate();
|
||||
} else {
|
||||
this.router.navigate([this.twoFactorRoute]);
|
||||
}
|
||||
} else if (loginResponse.forcePasswordReset) {
|
||||
if (this.onSuccessfulLoginForceResetNavigate != null) {
|
||||
this.onSuccessfulLoginForceResetNavigate();
|
||||
} else {
|
||||
this.router.navigate([this.forcePasswordResetRoute]);
|
||||
}
|
||||
} else {
|
||||
await this.setRememberEmailValues();
|
||||
if (this.onSuccessfulLogin != null) {
|
||||
this.onSuccessfulLogin();
|
||||
}
|
||||
if (this.onSuccessfulLoginNavigate != null) {
|
||||
this.onSuccessfulLoginNavigate();
|
||||
} else {
|
||||
this.router.navigate([this.successRoute]);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof ErrorResponse) {
|
||||
this.router.navigate(["/login"]);
|
||||
this.validationService.showError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logService.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async setRememberEmailValues() {
|
||||
const rememberEmail = this.loginService.getRememberEmail();
|
||||
const rememberedEmail = this.loginService.getEmail();
|
||||
await this.stateService.setRememberedEmail(rememberEmail ? rememberedEmail : null);
|
||||
this.loginService.clearValues();
|
||||
}
|
||||
|
||||
private async buildAuthRequest() {
|
||||
this.authRequestKeyPair = await this.cryptoFunctionService.rsaGenerateKeyPair(2048);
|
||||
const fingerprint = await (
|
||||
await this.cryptoService.getFingerprint(this.email, this.authRequestKeyPair[0])
|
||||
).join("-");
|
||||
const deviceIdentifier = await this.appIdService.getAppId();
|
||||
const publicKey = Utils.fromBufferToB64(this.authRequestKeyPair[0]);
|
||||
const accessCode = await this.passwordGenerationService.generatePassword({ length: 25 });
|
||||
|
||||
this.passwordlessRequest = new PasswordlessCreateAuthRequest(
|
||||
this.email,
|
||||
deviceIdentifier,
|
||||
publicKey,
|
||||
AuthRequestType.AuthenticateAndUnlock,
|
||||
accessCode,
|
||||
fingerprint
|
||||
);
|
||||
}
|
||||
|
||||
private async buildLoginCredntials(
|
||||
requestId: string,
|
||||
response: AuthRequestResponse
|
||||
): Promise<PasswordlessLogInCredentials> {
|
||||
const decKey = await this.cryptoService.rsaDecrypt(response.key, this.authRequestKeyPair[1]);
|
||||
const decMasterPasswordHash = await this.cryptoService.rsaDecrypt(
|
||||
response.masterPasswordHash,
|
||||
this.authRequestKeyPair[1]
|
||||
);
|
||||
const key = new SymmetricCryptoKey(decKey);
|
||||
const localHashedPassword = Utils.fromBufferToUtf8(decMasterPasswordHash);
|
||||
|
||||
return new PasswordlessLogInCredentials(
|
||||
this.email,
|
||||
this.passwordlessRequest.accessCode,
|
||||
requestId,
|
||||
key,
|
||||
localHashedPassword
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,284 +0,0 @@
|
||||
import { Directive, NgZone, OnInit } from "@angular/core";
|
||||
import { FormBuilder, Validators } from "@angular/forms";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { take } from "rxjs/operators";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AppIdService } from "@bitwarden/common/abstractions/appId.service";
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import {
|
||||
AllValidationErrors,
|
||||
FormValidationErrorsService,
|
||||
} from "@bitwarden/common/abstractions/formValidationErrors.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { LoginService } from "@bitwarden/common/abstractions/login.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { AuthResult } from "@bitwarden/common/models/domain/auth-result";
|
||||
import { PasswordLogInCredentials } from "@bitwarden/common/models/domain/log-in-credentials";
|
||||
|
||||
import { CaptchaProtectedComponent } from "./captchaProtected.component";
|
||||
|
||||
@Directive()
|
||||
export class LoginComponent extends CaptchaProtectedComponent implements OnInit {
|
||||
showPassword = false;
|
||||
formPromise: Promise<AuthResult>;
|
||||
onSuccessfulLogin: () => Promise<any>;
|
||||
onSuccessfulLoginNavigate: () => Promise<any>;
|
||||
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
|
||||
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
|
||||
selfHosted = false;
|
||||
showLoginWithDevice: boolean;
|
||||
validatedEmail = false;
|
||||
paramEmailSet = false;
|
||||
|
||||
formGroup = this.formBuilder.group({
|
||||
email: ["", [Validators.required, Validators.email]],
|
||||
masterPassword: ["", [Validators.required, Validators.minLength(8)]],
|
||||
rememberEmail: [false],
|
||||
});
|
||||
|
||||
protected twoFactorRoute = "2fa";
|
||||
protected successRoute = "vault";
|
||||
protected forcePasswordResetRoute = "update-temp-password";
|
||||
|
||||
get loggedEmail() {
|
||||
return this.formGroup.value.email;
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected apiService: ApiService,
|
||||
protected appIdService: AppIdService,
|
||||
protected authService: AuthService,
|
||||
protected router: Router,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
i18nService: I18nService,
|
||||
protected stateService: StateService,
|
||||
environmentService: EnvironmentService,
|
||||
protected passwordGenerationService: PasswordGenerationService,
|
||||
protected cryptoFunctionService: CryptoFunctionService,
|
||||
protected logService: LogService,
|
||||
protected ngZone: NgZone,
|
||||
protected formBuilder: FormBuilder,
|
||||
protected formValidationErrorService: FormValidationErrorsService,
|
||||
protected route: ActivatedRoute,
|
||||
protected loginService: LoginService
|
||||
) {
|
||||
super(environmentService, i18nService, platformUtilsService);
|
||||
this.selfHosted = platformUtilsService.isSelfHost();
|
||||
}
|
||||
|
||||
get selfHostedDomain() {
|
||||
return this.environmentService.hasBaseUrl() ? this.environmentService.getWebVaultUrl() : null;
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.route?.queryParams.subscribe((params) => {
|
||||
if (params != null) {
|
||||
const queryParamsEmail = params["email"];
|
||||
if (queryParamsEmail != null && queryParamsEmail.indexOf("@") > -1) {
|
||||
this.formGroup.get("email").setValue(queryParamsEmail);
|
||||
this.loginService.setEmail(queryParamsEmail);
|
||||
this.paramEmailSet = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
let email = this.loginService.getEmail();
|
||||
|
||||
if (email == null || email === "") {
|
||||
email = await this.stateService.getRememberedEmail();
|
||||
}
|
||||
|
||||
if (!this.paramEmailSet) {
|
||||
this.formGroup.get("email")?.setValue(email ?? "");
|
||||
}
|
||||
let rememberEmail = this.loginService.getRememberEmail();
|
||||
if (rememberEmail == null) {
|
||||
rememberEmail = (await this.stateService.getRememberedEmail()) != null;
|
||||
}
|
||||
this.formGroup.get("rememberEmail")?.setValue(rememberEmail);
|
||||
}
|
||||
|
||||
async submit(showToast = true) {
|
||||
const data = this.formGroup.value;
|
||||
|
||||
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.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errorText);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const credentials = new PasswordLogInCredentials(
|
||||
data.email,
|
||||
data.masterPassword,
|
||||
this.captchaToken,
|
||||
null
|
||||
);
|
||||
this.formPromise = this.authService.logIn(credentials);
|
||||
const response = await this.formPromise;
|
||||
this.setFormValues();
|
||||
await this.loginService.saveEmailSettings();
|
||||
if (this.handleCaptchaRequired(response)) {
|
||||
return;
|
||||
} else if (response.requiresTwoFactor) {
|
||||
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
|
||||
this.onSuccessfulLoginTwoFactorNavigate();
|
||||
} else {
|
||||
this.router.navigate([this.twoFactorRoute]);
|
||||
}
|
||||
} else if (response.forcePasswordReset) {
|
||||
if (this.onSuccessfulLoginForceResetNavigate != null) {
|
||||
this.onSuccessfulLoginForceResetNavigate();
|
||||
} else {
|
||||
this.router.navigate([this.forcePasswordResetRoute]);
|
||||
}
|
||||
} else {
|
||||
const disableFavicon = await this.stateService.getDisableFavicon();
|
||||
await this.stateService.setDisableFavicon(!!disableFavicon);
|
||||
if (this.onSuccessfulLogin != null) {
|
||||
this.onSuccessfulLogin();
|
||||
}
|
||||
if (this.onSuccessfulLoginNavigate != null) {
|
||||
this.onSuccessfulLoginNavigate();
|
||||
} else {
|
||||
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 startPasswordlessLogin() {
|
||||
this.formGroup.get("masterPassword")?.clearValidators();
|
||||
this.formGroup.get("masterPassword")?.updateValueAndValidity();
|
||||
|
||||
if (!this.formGroup.valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const email = this.formGroup.get("email").value;
|
||||
this.router.navigate(["/login-with-device"], { state: { email: email } });
|
||||
}
|
||||
|
||||
async launchSsoBrowser(clientId: string, ssoRedirectUri: string) {
|
||||
await this.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);
|
||||
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.stateService.setSsoState(state);
|
||||
await this.stateService.setSsoCodeVerifier(ssoCodeVerifier);
|
||||
|
||||
// Build URI
|
||||
const webUrl = this.environmentService.getWebVaultUrl();
|
||||
|
||||
// Launch browser
|
||||
this.platformUtilsService.launchUri(
|
||||
webUrl +
|
||||
"/#/sso?clientId=" +
|
||||
clientId +
|
||||
"&redirectUri=" +
|
||||
encodeURIComponent(ssoRedirectUri) +
|
||||
"&state=" +
|
||||
state +
|
||||
"&codeChallenge=" +
|
||||
codeChallenge
|
||||
);
|
||||
}
|
||||
|
||||
async validateEmail() {
|
||||
this.formGroup.controls.email.markAsTouched();
|
||||
const emailInvalid = this.formGroup.get("email").invalid;
|
||||
if (!emailInvalid) {
|
||||
this.toggleValidateEmail(true);
|
||||
await this.getLoginWithDevice(this.loggedEmail);
|
||||
}
|
||||
}
|
||||
|
||||
toggleValidateEmail(value: boolean) {
|
||||
this.validatedEmail = value;
|
||||
this.formGroup.controls.masterPassword.reset();
|
||||
}
|
||||
|
||||
setFormValues() {
|
||||
this.loginService.setEmail(this.formGroup.value.email);
|
||||
this.loginService.setRememberEmail(this.formGroup.value.rememberEmail);
|
||||
}
|
||||
|
||||
async saveEmailSettings() {
|
||||
this.setFormValues();
|
||||
await this.loginService.saveEmailSettings();
|
||||
}
|
||||
|
||||
private getErrorToastMessage() {
|
||||
const error: AllValidationErrors = this.formValidationErrorService
|
||||
.getFormValidationErrors(this.formGroup.controls)
|
||||
.shift();
|
||||
|
||||
if (error) {
|
||||
switch (error.errorName) {
|
||||
case "email":
|
||||
return this.i18nService.t("invalidEmail");
|
||||
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();
|
||||
const res = await this.apiService.getKnownDevice(email, deviceIdentifier);
|
||||
//ensure the application is not self-hosted
|
||||
this.showLoginWithDevice = res && !this.selfHosted;
|
||||
} catch (e) {
|
||||
this.showLoginWithDevice = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuditService } from "@bitwarden/common/abstractions/audit.service";
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import {
|
||||
@@ -16,19 +15,19 @@ import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
|
||||
import { PasswordLogInCredentials } from "@bitwarden/common/auth/models/domain/log-in-credentials";
|
||||
import { RegisterResponse } from "@bitwarden/common/auth/models/response/register.response";
|
||||
import { DEFAULT_KDF_CONFIG, DEFAULT_KDF_TYPE } from "@bitwarden/common/enums/kdfType";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { PasswordLogInCredentials } from "@bitwarden/common/models/domain/log-in-credentials";
|
||||
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
|
||||
import { ReferenceEventRequest } from "@bitwarden/common/models/request/reference-event.request";
|
||||
import { RegisterRequest } from "@bitwarden/common/models/request/register.request";
|
||||
import { RegisterResponse } from "@bitwarden/common/models/response/authentication/register.response";
|
||||
|
||||
import { CaptchaProtectedComponent } from "../auth/components/captcha-protected.component";
|
||||
import { PasswordColorText } from "../shared/components/password-strength/password-strength.component";
|
||||
import { InputsFieldMatch } from "../validators/inputsFieldMatch.validator";
|
||||
|
||||
import { CaptchaProtectedComponent } from "./captchaProtected.component";
|
||||
|
||||
@Directive()
|
||||
export class RegisterComponent extends CaptchaProtectedComponent implements OnInit {
|
||||
@Input() isInTrialFlow = false;
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
import { Directive, OnInit } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
|
||||
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/abstractions/organization/organization-api.service.abstraction";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { Organization } from "@bitwarden/common/models/domain/organization";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
|
||||
@Directive()
|
||||
export class RemovePasswordComponent implements OnInit {
|
||||
actionPromise: Promise<void | boolean>;
|
||||
continuing = false;
|
||||
leaving = false;
|
||||
|
||||
loading = true;
|
||||
organization: Organization;
|
||||
email: string;
|
||||
|
||||
constructor(
|
||||
private router: Router,
|
||||
private stateService: StateService,
|
||||
private syncService: SyncService,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService,
|
||||
private keyConnectorService: KeyConnectorService,
|
||||
private organizationApiService: OrganizationApiServiceAbstraction
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.organization = await this.keyConnectorService.getManagingOrganization();
|
||||
this.email = await this.stateService.getEmail();
|
||||
await this.syncService.fullSync(false);
|
||||
this.loading = false;
|
||||
}
|
||||
|
||||
async convert() {
|
||||
this.continuing = true;
|
||||
this.actionPromise = this.keyConnectorService.migrateUser();
|
||||
|
||||
try {
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("removedMasterPassword")
|
||||
);
|
||||
await this.keyConnectorService.removeConvertAccountRequired();
|
||||
this.router.navigate([""]);
|
||||
} catch (e) {
|
||||
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async leave() {
|
||||
const confirmed = await this.platformUtilsService.showDialog(
|
||||
this.i18nService.t("leaveOrganizationConfirmation"),
|
||||
this.organization.name,
|
||||
this.i18nService.t("yes"),
|
||||
this.i18nService.t("no"),
|
||||
"warning"
|
||||
);
|
||||
if (!confirmed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.leaving = true;
|
||||
this.actionPromise = this.organizationApiService.leave(this.organization.id);
|
||||
await this.actionPromise;
|
||||
this.platformUtilsService.showToast("success", null, this.i18nService.t("leftOrganization"));
|
||||
await this.keyConnectorService.removeConvertAccountRequired();
|
||||
this.router.navigate([""]);
|
||||
} catch (e) {
|
||||
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,16 +14,16 @@ import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUti
|
||||
import { PolicyApiServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy-api.service.abstraction";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
|
||||
import { HashPurpose } from "@bitwarden/common/enums/hashPurpose";
|
||||
import { DEFAULT_KDF_TYPE, DEFAULT_KDF_CONFIG } from "@bitwarden/common/enums/kdfType";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
||||
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
|
||||
import { SetPasswordRequest } from "@bitwarden/common/models/request/set-password.request";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
|
||||
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
|
||||
import { ChangePasswordComponent as BaseChangePasswordComponent } from "../auth/components/change-password.component";
|
||||
|
||||
@Directive()
|
||||
export class SetPasswordComponent extends BaseChangePasswordComponent {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Directive, OnInit } from "@angular/core";
|
||||
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
|
||||
import { ModalRef } from "./modal/modal.ref";
|
||||
|
||||
@@ -1,261 +0,0 @@
|
||||
import { Directive } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { AuthResult } from "@bitwarden/common/models/domain/auth-result";
|
||||
import { SsoLogInCredentials } from "@bitwarden/common/models/domain/log-in-credentials";
|
||||
import { SsoPreValidateResponse } from "@bitwarden/common/models/response/sso-pre-validate.response";
|
||||
|
||||
@Directive()
|
||||
export class SsoComponent {
|
||||
identifier: string;
|
||||
loggingIn = false;
|
||||
|
||||
formPromise: Promise<AuthResult>;
|
||||
initiateSsoFormPromise: Promise<SsoPreValidateResponse>;
|
||||
onSuccessfulLogin: () => Promise<any>;
|
||||
onSuccessfulLoginNavigate: () => Promise<any>;
|
||||
onSuccessfulLoginTwoFactorNavigate: () => Promise<any>;
|
||||
onSuccessfulLoginChangePasswordNavigate: () => Promise<any>;
|
||||
onSuccessfulLoginForceResetNavigate: () => Promise<any>;
|
||||
|
||||
protected twoFactorRoute = "2fa";
|
||||
protected successRoute = "lock";
|
||||
protected changePasswordRoute = "set-password";
|
||||
protected forcePasswordResetRoute = "update-temp-password";
|
||||
protected clientId: string;
|
||||
protected redirectUri: string;
|
||||
protected state: string;
|
||||
protected codeChallenge: string;
|
||||
|
||||
constructor(
|
||||
protected authService: AuthService,
|
||||
protected router: Router,
|
||||
protected i18nService: I18nService,
|
||||
protected route: ActivatedRoute,
|
||||
protected stateService: StateService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected apiService: ApiService,
|
||||
protected cryptoFunctionService: CryptoFunctionService,
|
||||
protected environmentService: EnvironmentService,
|
||||
protected passwordGenerationService: PasswordGenerationService,
|
||||
protected logService: LogService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
// eslint-disable-next-line rxjs/no-async-subscribe
|
||||
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
|
||||
if (qParams.code != null && qParams.state != null) {
|
||||
const codeVerifier = await this.stateService.getSsoCodeVerifier();
|
||||
const state = await this.stateService.getSsoState();
|
||||
await this.stateService.setSsoCodeVerifier(null);
|
||||
await this.stateService.setSsoState(null);
|
||||
if (
|
||||
qParams.code != null &&
|
||||
codeVerifier != null &&
|
||||
state != null &&
|
||||
this.checkState(state, qParams.state)
|
||||
) {
|
||||
await this.logIn(
|
||||
qParams.code,
|
||||
codeVerifier,
|
||||
this.getOrgIdentifierFromState(qParams.state)
|
||||
);
|
||||
}
|
||||
} else if (
|
||||
qParams.clientId != null &&
|
||||
qParams.redirectUri != null &&
|
||||
qParams.state != null &&
|
||||
qParams.codeChallenge != null
|
||||
) {
|
||||
this.redirectUri = qParams.redirectUri;
|
||||
this.state = qParams.state;
|
||||
this.codeChallenge = qParams.codeChallenge;
|
||||
this.clientId = qParams.clientId;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async submit(returnUri?: string, includeUserIdentifier?: boolean) {
|
||||
if (this.identifier == null || this.identifier === "") {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("ssoValidationFailed"),
|
||||
this.i18nService.t("ssoIdentifierRequired")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.initiateSsoFormPromise = this.apiService.preValidateSso(this.identifier);
|
||||
const response = await this.initiateSsoFormPromise;
|
||||
|
||||
const authorizeUrl = await this.buildAuthorizeUrl(
|
||||
returnUri,
|
||||
includeUserIdentifier,
|
||||
response.token
|
||||
);
|
||||
this.platformUtilsService.launchUri(authorizeUrl, { sameWindow: true });
|
||||
}
|
||||
|
||||
protected async buildAuthorizeUrl(
|
||||
returnUri?: string,
|
||||
includeUserIdentifier?: boolean,
|
||||
token?: string
|
||||
): Promise<string> {
|
||||
let codeChallenge = this.codeChallenge;
|
||||
let state = this.state;
|
||||
|
||||
const passwordOptions: any = {
|
||||
type: "password",
|
||||
length: 64,
|
||||
uppercase: true,
|
||||
lowercase: true,
|
||||
numbers: true,
|
||||
special: false,
|
||||
};
|
||||
|
||||
if (codeChallenge == null) {
|
||||
const codeVerifier = await this.passwordGenerationService.generatePassword(passwordOptions);
|
||||
const codeVerifierHash = await this.cryptoFunctionService.hash(codeVerifier, "sha256");
|
||||
codeChallenge = Utils.fromBufferToUrlB64(codeVerifierHash);
|
||||
await this.stateService.setSsoCodeVerifier(codeVerifier);
|
||||
}
|
||||
|
||||
if (state == null) {
|
||||
state = await this.passwordGenerationService.generatePassword(passwordOptions);
|
||||
if (returnUri) {
|
||||
state += `_returnUri='${returnUri}'`;
|
||||
}
|
||||
}
|
||||
|
||||
// Add Organization Identifier to state
|
||||
state += `_identifier=${this.identifier}`;
|
||||
|
||||
// Save state (regardless of new or existing)
|
||||
await this.stateService.setSsoState(state);
|
||||
|
||||
let authorizeUrl =
|
||||
this.environmentService.getIdentityUrl() +
|
||||
"/connect/authorize?" +
|
||||
"client_id=" +
|
||||
this.clientId +
|
||||
"&redirect_uri=" +
|
||||
encodeURIComponent(this.redirectUri) +
|
||||
"&" +
|
||||
"response_type=code&scope=api offline_access&" +
|
||||
"state=" +
|
||||
state +
|
||||
"&code_challenge=" +
|
||||
codeChallenge +
|
||||
"&" +
|
||||
"code_challenge_method=S256&response_mode=query&" +
|
||||
"domain_hint=" +
|
||||
encodeURIComponent(this.identifier) +
|
||||
"&ssoToken=" +
|
||||
encodeURIComponent(token);
|
||||
|
||||
if (includeUserIdentifier) {
|
||||
const userIdentifier = await this.apiService.getSsoUserIdentifier();
|
||||
authorizeUrl += `&user_identifier=${encodeURIComponent(userIdentifier)}`;
|
||||
}
|
||||
|
||||
return authorizeUrl;
|
||||
}
|
||||
|
||||
private async logIn(code: string, codeVerifier: string, orgIdFromState: string) {
|
||||
this.loggingIn = true;
|
||||
try {
|
||||
const credentials = new SsoLogInCredentials(
|
||||
code,
|
||||
codeVerifier,
|
||||
this.redirectUri,
|
||||
orgIdFromState
|
||||
);
|
||||
this.formPromise = this.authService.logIn(credentials);
|
||||
const response = await this.formPromise;
|
||||
if (response.requiresTwoFactor) {
|
||||
if (this.onSuccessfulLoginTwoFactorNavigate != null) {
|
||||
this.onSuccessfulLoginTwoFactorNavigate();
|
||||
} else {
|
||||
this.router.navigate([this.twoFactorRoute], {
|
||||
queryParams: {
|
||||
identifier: orgIdFromState,
|
||||
sso: "true",
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if (response.resetMasterPassword) {
|
||||
if (this.onSuccessfulLoginChangePasswordNavigate != null) {
|
||||
this.onSuccessfulLoginChangePasswordNavigate();
|
||||
} else {
|
||||
this.router.navigate([this.changePasswordRoute], {
|
||||
queryParams: {
|
||||
identifier: orgIdFromState,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else if (response.forcePasswordReset) {
|
||||
if (this.onSuccessfulLoginForceResetNavigate != null) {
|
||||
this.onSuccessfulLoginForceResetNavigate();
|
||||
} else {
|
||||
this.router.navigate([this.forcePasswordResetRoute]);
|
||||
}
|
||||
} else {
|
||||
const disableFavicon = await this.stateService.getDisableFavicon();
|
||||
await this.stateService.setDisableFavicon(!!disableFavicon);
|
||||
if (this.onSuccessfulLogin != null) {
|
||||
this.onSuccessfulLogin();
|
||||
}
|
||||
if (this.onSuccessfulLoginNavigate != null) {
|
||||
this.onSuccessfulLoginNavigate();
|
||||
} else {
|
||||
this.router.navigate([this.successRoute]);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
|
||||
// TODO: Key Connector Service should pass this error message to the logout callback instead of displaying here
|
||||
if (e.message === "Key Connector error") {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
null,
|
||||
this.i18nService.t("ssoKeyConnectorError")
|
||||
);
|
||||
}
|
||||
}
|
||||
this.loggingIn = false;
|
||||
}
|
||||
|
||||
private getOrgIdentifierFromState(state: string): string {
|
||||
if (state === null || state === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const stateSplit = state.split("_identifier=");
|
||||
return stateSplit.length > 1 ? stateSplit[1] : null;
|
||||
}
|
||||
|
||||
private checkState(state: string, checkState: string): boolean {
|
||||
if (state === null || state === undefined) {
|
||||
return false;
|
||||
}
|
||||
if (checkState === null || checkState === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const stateSplit = state.split("_identifier=");
|
||||
const checkStateSplit = checkState.split("_identifier=");
|
||||
return stateSplit[0] === checkStateSplit[0];
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
import { Directive, EventEmitter, OnInit, Output } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
|
||||
|
||||
@Directive()
|
||||
export class TwoFactorOptionsComponent implements OnInit {
|
||||
@Output() onProviderSelected = new EventEmitter<TwoFactorProviderType>();
|
||||
@Output() onRecoverSelected = new EventEmitter();
|
||||
|
||||
providers: any[] = [];
|
||||
|
||||
constructor(
|
||||
protected twoFactorService: TwoFactorService,
|
||||
protected router: Router,
|
||||
protected i18nService: I18nService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected win: Window
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.providers = this.twoFactorService.getSupportedProviders(this.win);
|
||||
}
|
||||
|
||||
choose(p: any) {
|
||||
this.onProviderSelected.emit(p.type);
|
||||
}
|
||||
|
||||
recover() {
|
||||
this.platformUtilsService.launchUri("https://bitwarden.com/help/lost-two-step-device/");
|
||||
this.onRecoverSelected.emit();
|
||||
}
|
||||
}
|
||||
@@ -1,292 +0,0 @@
|
||||
import { Directive, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import * as DuoWebSDK from "duo_web_sdk";
|
||||
import { first } from "rxjs/operators";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { AppIdService } from "@bitwarden/common/abstractions/appId.service";
|
||||
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||
import { EnvironmentService } from "@bitwarden/common/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { LoginService } from "@bitwarden/common/abstractions/login.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { TwoFactorService } from "@bitwarden/common/abstractions/twoFactor.service";
|
||||
import { TwoFactorProviderType } from "@bitwarden/common/enums/twoFactorProviderType";
|
||||
import { WebAuthnIFrame } from "@bitwarden/common/misc/webauthn_iframe";
|
||||
import { AuthResult } from "@bitwarden/common/models/domain/auth-result";
|
||||
import { TokenTwoFactorRequest } from "@bitwarden/common/models/request/identity-token/token-two-factor.request";
|
||||
import { TwoFactorEmailRequest } from "@bitwarden/common/models/request/two-factor-email.request";
|
||||
import { TwoFactorProviders } from "@bitwarden/common/services/twoFactor.service";
|
||||
|
||||
import { CaptchaProtectedComponent } from "./captchaProtected.component";
|
||||
|
||||
@Directive()
|
||||
export class TwoFactorComponent extends CaptchaProtectedComponent implements OnInit, OnDestroy {
|
||||
token = "";
|
||||
remember = false;
|
||||
webAuthnReady = false;
|
||||
webAuthnNewTab = false;
|
||||
providers = TwoFactorProviders;
|
||||
providerType = TwoFactorProviderType;
|
||||
selectedProviderType: TwoFactorProviderType = TwoFactorProviderType.Authenticator;
|
||||
webAuthnSupported = false;
|
||||
webAuthn: WebAuthnIFrame = null;
|
||||
title = "";
|
||||
twoFactorEmail: string = null;
|
||||
formPromise: Promise<any>;
|
||||
emailPromise: Promise<any>;
|
||||
identifier: string = null;
|
||||
onSuccessfulLogin: () => Promise<any>;
|
||||
onSuccessfulLoginNavigate: () => Promise<any>;
|
||||
|
||||
get webAuthnAllow(): string {
|
||||
return `publickey-credentials-get ${this.environmentService.getWebVaultUrl()}`;
|
||||
}
|
||||
|
||||
protected loginRoute = "login";
|
||||
protected successRoute = "vault";
|
||||
|
||||
constructor(
|
||||
protected authService: AuthService,
|
||||
protected router: Router,
|
||||
protected i18nService: I18nService,
|
||||
protected apiService: ApiService,
|
||||
protected platformUtilsService: PlatformUtilsService,
|
||||
protected win: Window,
|
||||
protected environmentService: EnvironmentService,
|
||||
protected stateService: StateService,
|
||||
protected route: ActivatedRoute,
|
||||
protected logService: LogService,
|
||||
protected twoFactorService: TwoFactorService,
|
||||
protected appIdService: AppIdService,
|
||||
protected loginService: LoginService
|
||||
) {
|
||||
super(environmentService, i18nService, platformUtilsService);
|
||||
this.webAuthnSupported = this.platformUtilsService.supportsWebAuthn(win);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
if (!this.authing || this.twoFactorService.getProviders() == null) {
|
||||
this.router.navigate([this.loginRoute]);
|
||||
return;
|
||||
}
|
||||
|
||||
this.route.queryParams.pipe(first()).subscribe((qParams) => {
|
||||
if (qParams.identifier != null) {
|
||||
this.identifier = qParams.identifier;
|
||||
}
|
||||
});
|
||||
|
||||
if (this.needsLock) {
|
||||
this.successRoute = "lock";
|
||||
}
|
||||
|
||||
if (this.win != null && this.webAuthnSupported) {
|
||||
const webVaultUrl = this.environmentService.getWebVaultUrl();
|
||||
this.webAuthn = new WebAuthnIFrame(
|
||||
this.win,
|
||||
webVaultUrl,
|
||||
this.webAuthnNewTab,
|
||||
this.platformUtilsService,
|
||||
this.i18nService,
|
||||
(token: string) => {
|
||||
this.token = token;
|
||||
this.submit();
|
||||
},
|
||||
(error: string) => {
|
||||
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), error);
|
||||
},
|
||||
(info: string) => {
|
||||
if (info === "ready") {
|
||||
this.webAuthnReady = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
this.selectedProviderType = this.twoFactorService.getDefaultProvider(this.webAuthnSupported);
|
||||
await this.init();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.cleanupWebAuthn();
|
||||
this.webAuthn = null;
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (this.selectedProviderType == null) {
|
||||
this.title = this.i18nService.t("loginUnavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
this.cleanupWebAuthn();
|
||||
this.title = (TwoFactorProviders as any)[this.selectedProviderType].name;
|
||||
const providerData = this.twoFactorService.getProviders().get(this.selectedProviderType);
|
||||
switch (this.selectedProviderType) {
|
||||
case TwoFactorProviderType.WebAuthn:
|
||||
if (!this.webAuthnNewTab) {
|
||||
setTimeout(() => {
|
||||
this.authWebAuthn();
|
||||
}, 500);
|
||||
}
|
||||
break;
|
||||
case TwoFactorProviderType.Duo:
|
||||
case TwoFactorProviderType.OrganizationDuo:
|
||||
setTimeout(() => {
|
||||
DuoWebSDK.init({
|
||||
iframe: undefined,
|
||||
host: providerData.Host,
|
||||
sig_request: providerData.Signature,
|
||||
submit_callback: async (f: HTMLFormElement) => {
|
||||
const sig = f.querySelector('input[name="sig_response"]') as HTMLInputElement;
|
||||
if (sig != null) {
|
||||
this.token = sig.value;
|
||||
await this.submit();
|
||||
}
|
||||
},
|
||||
});
|
||||
}, 0);
|
||||
break;
|
||||
case TwoFactorProviderType.Email:
|
||||
this.twoFactorEmail = providerData.Email;
|
||||
if (this.twoFactorService.getProviders().size > 1) {
|
||||
await this.sendEmail(false);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async submit() {
|
||||
await this.setupCaptcha();
|
||||
|
||||
if (this.token == null || this.token === "") {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("verificationCodeRequired")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn) {
|
||||
if (this.webAuthn != null) {
|
||||
this.webAuthn.stop();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else if (
|
||||
this.selectedProviderType === TwoFactorProviderType.Email ||
|
||||
this.selectedProviderType === TwoFactorProviderType.Authenticator
|
||||
) {
|
||||
this.token = this.token.replace(" ", "").trim();
|
||||
}
|
||||
|
||||
try {
|
||||
await this.doSubmit();
|
||||
} catch {
|
||||
if (this.selectedProviderType === TwoFactorProviderType.WebAuthn && this.webAuthn != null) {
|
||||
this.webAuthn.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async doSubmit() {
|
||||
this.formPromise = this.authService.logInTwoFactor(
|
||||
new TokenTwoFactorRequest(this.selectedProviderType, this.token, this.remember),
|
||||
this.captchaToken
|
||||
);
|
||||
const response: AuthResult = await this.formPromise;
|
||||
const disableFavicon = await this.stateService.getDisableFavicon();
|
||||
await this.stateService.setDisableFavicon(!!disableFavicon);
|
||||
if (this.handleCaptchaRequired(response)) {
|
||||
return;
|
||||
}
|
||||
if (this.onSuccessfulLogin != null) {
|
||||
this.loginService.clearValues();
|
||||
this.onSuccessfulLogin();
|
||||
}
|
||||
if (response.resetMasterPassword) {
|
||||
this.successRoute = "set-password";
|
||||
}
|
||||
if (response.forcePasswordReset) {
|
||||
this.successRoute = "update-temp-password";
|
||||
}
|
||||
if (this.onSuccessfulLoginNavigate != null) {
|
||||
this.loginService.clearValues();
|
||||
this.onSuccessfulLoginNavigate();
|
||||
} else {
|
||||
this.loginService.clearValues();
|
||||
this.router.navigate([this.successRoute], {
|
||||
queryParams: {
|
||||
identifier: this.identifier,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async sendEmail(doToast: boolean) {
|
||||
if (this.selectedProviderType !== TwoFactorProviderType.Email) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.emailPromise != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const request = new TwoFactorEmailRequest();
|
||||
request.email = this.authService.email;
|
||||
request.masterPasswordHash = this.authService.masterPasswordHash;
|
||||
request.deviceIdentifier = await this.appIdService.getAppId();
|
||||
request.authRequestAccessCode = this.authService.accessCode;
|
||||
request.authRequestId = this.authService.authRequestId;
|
||||
this.emailPromise = this.apiService.postTwoFactorEmail(request);
|
||||
await this.emailPromise;
|
||||
if (doToast) {
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("verificationCodeEmailSent", this.twoFactorEmail)
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
|
||||
this.emailPromise = null;
|
||||
}
|
||||
|
||||
authWebAuthn() {
|
||||
const providerData = this.twoFactorService.getProviders().get(this.selectedProviderType);
|
||||
|
||||
if (!this.webAuthnSupported || this.webAuthn == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.webAuthn.init(providerData);
|
||||
}
|
||||
|
||||
private cleanupWebAuthn() {
|
||||
if (this.webAuthn != null) {
|
||||
this.webAuthn.stop();
|
||||
this.webAuthn.cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
get authing(): boolean {
|
||||
return (
|
||||
this.authService.authingWithPassword() ||
|
||||
this.authService.authingWithSso() ||
|
||||
this.authService.authingWithUserApiKey() ||
|
||||
this.authService.authingWithPasswordless()
|
||||
);
|
||||
}
|
||||
|
||||
get needsLock(): boolean {
|
||||
return this.authService.authingWithSso() || this.authService.authingWithUserApiKey();
|
||||
}
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
import { Directive } from "@angular/core";
|
||||
import { Router } from "@angular/router";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { VerificationType } from "@bitwarden/common/enums/verificationType";
|
||||
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/master-password-policy-options";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
||||
import { PasswordRequest } from "@bitwarden/common/models/request/password.request";
|
||||
import { Verification } from "@bitwarden/common/types/verification";
|
||||
|
||||
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
|
||||
|
||||
@Directive()
|
||||
export class UpdatePasswordComponent extends BaseChangePasswordComponent {
|
||||
hint: string;
|
||||
key: string;
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
showPassword = false;
|
||||
currentMasterPassword: string;
|
||||
|
||||
onSuccessfulChangePassword: () => Promise<void>;
|
||||
|
||||
constructor(
|
||||
protected router: Router,
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
policyService: PolicyService,
|
||||
cryptoService: CryptoService,
|
||||
messagingService: MessagingService,
|
||||
private apiService: ApiService,
|
||||
stateService: StateService,
|
||||
private userVerificationService: UserVerificationService,
|
||||
private logService: LogService
|
||||
) {
|
||||
super(
|
||||
i18nService,
|
||||
cryptoService,
|
||||
messagingService,
|
||||
passwordGenerationService,
|
||||
platformUtilsService,
|
||||
policyService,
|
||||
stateService
|
||||
);
|
||||
}
|
||||
|
||||
togglePassword(confirmField: boolean) {
|
||||
this.showPassword = !this.showPassword;
|
||||
document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
|
||||
}
|
||||
|
||||
async cancel() {
|
||||
await this.stateService.setOrganizationInvitation(null);
|
||||
this.router.navigate(["/vault"]);
|
||||
}
|
||||
|
||||
async setupSubmitActions(): Promise<boolean> {
|
||||
if (this.currentMasterPassword == null || this.currentMasterPassword === "") {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("errorOccurred"),
|
||||
this.i18nService.t("masterPasswordRequired")
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
const secret: Verification = {
|
||||
type: VerificationType.MasterPassword,
|
||||
secret: this.currentMasterPassword,
|
||||
};
|
||||
try {
|
||||
await this.userVerificationService.verifyUser(secret);
|
||||
} catch (e) {
|
||||
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), e.message);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.kdf = await this.stateService.getKdfType();
|
||||
this.kdfConfig = await this.stateService.getKdfConfig();
|
||||
return true;
|
||||
}
|
||||
|
||||
async performSubmitActions(
|
||||
masterPasswordHash: string,
|
||||
key: SymmetricCryptoKey,
|
||||
encKey: [SymmetricCryptoKey, EncString]
|
||||
) {
|
||||
try {
|
||||
// Create Request
|
||||
const request = new PasswordRequest();
|
||||
request.masterPasswordHash = await this.cryptoService.hashPassword(
|
||||
this.currentMasterPassword,
|
||||
null
|
||||
);
|
||||
request.newMasterPasswordHash = masterPasswordHash;
|
||||
request.key = encKey[1].encryptedString;
|
||||
|
||||
// Update user's password
|
||||
this.apiService.postPassword(request);
|
||||
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
this.i18nService.t("masterPasswordChanged"),
|
||||
this.i18nService.t("logBackIn")
|
||||
);
|
||||
|
||||
if (this.onSuccessfulChangePassword != null) {
|
||||
this.onSuccessfulChangePassword();
|
||||
} else {
|
||||
this.messagingService.send("logout");
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
import { Directive } from "@angular/core";
|
||||
|
||||
import { ApiService } from "@bitwarden/common/abstractions/api.service";
|
||||
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
||||
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||
import { StateService } from "@bitwarden/common/abstractions/state.service";
|
||||
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
||||
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/master-password-policy-options";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
||||
import { UpdateTempPasswordRequest } from "@bitwarden/common/models/request/update-temp-password.request";
|
||||
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
|
||||
|
||||
import { ChangePasswordComponent as BaseChangePasswordComponent } from "./change-password.component";
|
||||
|
||||
@Directive()
|
||||
export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
|
||||
hint: string;
|
||||
key: string;
|
||||
enforcedPolicyOptions: MasterPasswordPolicyOptions;
|
||||
showPassword = false;
|
||||
|
||||
onSuccessfulChangePassword: () => Promise<any>;
|
||||
|
||||
constructor(
|
||||
i18nService: I18nService,
|
||||
platformUtilsService: PlatformUtilsService,
|
||||
passwordGenerationService: PasswordGenerationService,
|
||||
policyService: PolicyService,
|
||||
cryptoService: CryptoService,
|
||||
messagingService: MessagingService,
|
||||
private apiService: ApiService,
|
||||
stateService: StateService,
|
||||
private syncService: SyncService,
|
||||
private logService: LogService
|
||||
) {
|
||||
super(
|
||||
i18nService,
|
||||
cryptoService,
|
||||
messagingService,
|
||||
passwordGenerationService,
|
||||
platformUtilsService,
|
||||
policyService,
|
||||
stateService
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
await this.syncService.fullSync(true);
|
||||
super.ngOnInit();
|
||||
}
|
||||
|
||||
togglePassword(confirmField: boolean) {
|
||||
this.showPassword = !this.showPassword;
|
||||
document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
|
||||
}
|
||||
|
||||
async setupSubmitActions(): Promise<boolean> {
|
||||
this.email = await this.stateService.getEmail();
|
||||
this.kdf = await this.stateService.getKdfType();
|
||||
this.kdfConfig = await this.stateService.getKdfConfig();
|
||||
return true;
|
||||
}
|
||||
|
||||
async submit() {
|
||||
// Validation
|
||||
if (!(await this.strongPassword())) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await this.setupSubmitActions())) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create new key and hash new password
|
||||
const newKey = await this.cryptoService.makeKey(
|
||||
this.masterPassword,
|
||||
this.email.trim().toLowerCase(),
|
||||
this.kdf,
|
||||
this.kdfConfig
|
||||
);
|
||||
const newPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, newKey);
|
||||
|
||||
// Grab user's current enc key
|
||||
const userEncKey = await this.cryptoService.getEncKey();
|
||||
|
||||
// Create new encKey for the User
|
||||
const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey);
|
||||
|
||||
await this.performSubmitActions(newPasswordHash, newKey, newEncKey);
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
async performSubmitActions(
|
||||
masterPasswordHash: string,
|
||||
key: SymmetricCryptoKey,
|
||||
encKey: [SymmetricCryptoKey, EncString]
|
||||
) {
|
||||
try {
|
||||
// Create request
|
||||
const request = new UpdateTempPasswordRequest();
|
||||
request.key = encKey[1].encryptedString;
|
||||
request.newMasterPasswordHash = masterPasswordHash;
|
||||
request.masterPasswordHint = this.hint;
|
||||
|
||||
// Update user's password
|
||||
this.formPromise = this.apiService.putUpdateTempPassword(request);
|
||||
await this.formPromise;
|
||||
this.platformUtilsService.showToast(
|
||||
"success",
|
||||
null,
|
||||
this.i18nService.t("updatedMasterPassword")
|
||||
);
|
||||
|
||||
if (this.onSuccessfulChangePassword != null) {
|
||||
this.onSuccessfulChangePassword();
|
||||
} else {
|
||||
this.messagingService.send("logout");
|
||||
}
|
||||
} catch (e) {
|
||||
this.logService.error(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Directive } from "@angular/core";
|
||||
import { FormBuilder, FormControl } from "@angular/forms";
|
||||
|
||||
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
|
||||
import { ModalConfig } from "../services/modal.service";
|
||||
|
||||
import { ModalRef } from "./modal/modal.ref";
|
||||
|
||||
/**
|
||||
* Used to verify the user's identity (using their master password or email-based OTP for Key Connector users). You can customize all of the text in the modal.
|
||||
*/
|
||||
@Directive()
|
||||
export class UserVerificationPromptComponent {
|
||||
confirmDescription = this.config.data.confirmDescription;
|
||||
confirmButtonText = this.config.data.confirmButtonText;
|
||||
modalTitle = this.config.data.modalTitle;
|
||||
secret = new FormControl();
|
||||
|
||||
constructor(
|
||||
private modalRef: ModalRef,
|
||||
protected config: ModalConfig,
|
||||
protected userVerificationService: UserVerificationService,
|
||||
private formBuilder: FormBuilder,
|
||||
private platformUtilsService: PlatformUtilsService,
|
||||
private i18nService: I18nService
|
||||
) {}
|
||||
|
||||
async submit() {
|
||||
try {
|
||||
//Incorrect secret will throw an invalid password error.
|
||||
await this.userVerificationService.verifyUser(this.secret.value);
|
||||
} catch (e) {
|
||||
this.platformUtilsService.showToast(
|
||||
"error",
|
||||
this.i18nService.t("error"),
|
||||
this.i18nService.t("invalidMasterPassword")
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.modalRef.close(true);
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import { Directive, OnInit } from "@angular/core";
|
||||
import { ControlValueAccessor, FormControl } from "@angular/forms";
|
||||
|
||||
import { KeyConnectorService } from "@bitwarden/common/abstractions/keyConnector.service";
|
||||
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification/userVerification.service.abstraction";
|
||||
import { VerificationType } from "@bitwarden/common/enums/verificationType";
|
||||
import { Utils } from "@bitwarden/common/misc/utils";
|
||||
import { Verification } from "@bitwarden/common/types/verification";
|
||||
|
||||
/**
|
||||
* Used for general-purpose user verification throughout the app.
|
||||
* Collects the user's master password, or if they are using Key Connector, prompts for an OTP via email.
|
||||
* This is exposed to the parent component via the ControlValueAccessor interface (e.g. bind it to a FormControl).
|
||||
* Use UserVerificationService to verify the user's input.
|
||||
*/
|
||||
@Directive({
|
||||
selector: "app-user-verification",
|
||||
})
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
export class UserVerificationComponent implements ControlValueAccessor, OnInit {
|
||||
usesKeyConnector = false;
|
||||
disableRequestOTP = false;
|
||||
sentCode = false;
|
||||
|
||||
secret = new FormControl("");
|
||||
|
||||
private onChange: (value: Verification) => void;
|
||||
|
||||
constructor(
|
||||
private keyConnectorService: KeyConnectorService,
|
||||
private userVerificationService: UserVerificationService
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.usesKeyConnector = await this.keyConnectorService.getUsesKeyConnector();
|
||||
this.processChanges(this.secret.value);
|
||||
|
||||
// eslint-disable-next-line rxjs-angular/prefer-takeuntil
|
||||
this.secret.valueChanges.subscribe((secret: string) => this.processChanges(secret));
|
||||
}
|
||||
|
||||
async requestOTP() {
|
||||
if (this.usesKeyConnector) {
|
||||
this.disableRequestOTP = true;
|
||||
try {
|
||||
await this.userVerificationService.requestOTP();
|
||||
this.sentCode = true;
|
||||
} finally {
|
||||
this.disableRequestOTP = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeValue(obj: any): void {
|
||||
this.secret.setValue(obj);
|
||||
}
|
||||
|
||||
registerOnChange(fn: any): void {
|
||||
this.onChange = fn;
|
||||
}
|
||||
|
||||
registerOnTouched(fn: any): void {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
setDisabledState?(isDisabled: boolean): void {
|
||||
this.disableRequestOTP = isDisabled;
|
||||
if (isDisabled) {
|
||||
this.secret.disable();
|
||||
} else {
|
||||
this.secret.enable();
|
||||
}
|
||||
}
|
||||
|
||||
private processChanges(secret: string) {
|
||||
if (this.onChange == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.onChange({
|
||||
type: this.usesKeyConnector ? VerificationType.OTP : VerificationType.MasterPassword,
|
||||
secret: Utils.isNullOrWhitespace(secret) ? null : secret,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user