1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 22:03:36 +00:00

[PM-4360] Move auth owned code into auth (#6595)

This commit is contained in:
Oscar Hinton
2023-10-19 10:03:32 +02:00
committed by GitHub
parent 742e6e3b95
commit d0e72f5554
34 changed files with 41 additions and 39 deletions

View File

@@ -1,73 +0,0 @@
import { Directive, EventEmitter, Output } from "@angular/core";
import {
EnvironmentService,
Region,
} from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { ModalService } from "../services/modal.service";
@Directive()
export class EnvironmentComponent {
@Output() onSaved = new EventEmitter();
iconsUrl: string;
identityUrl: string;
apiUrl: string;
webVaultUrl: string;
notificationsUrl: string;
baseUrl: string;
showCustom = false;
constructor(
protected platformUtilsService: PlatformUtilsService,
protected environmentService: EnvironmentService,
protected i18nService: I18nService,
private modalService: ModalService
) {
const urls = this.environmentService.getUrls();
if (this.environmentService.selectedRegion != Region.SelfHosted) {
return;
}
this.baseUrl = urls.base || "";
this.webVaultUrl = urls.webVault || "";
this.apiUrl = urls.api || "";
this.identityUrl = urls.identity || "";
this.iconsUrl = urls.icons || "";
this.notificationsUrl = urls.notifications || "";
}
async submit() {
const resUrls = await this.environmentService.setUrls({
base: this.baseUrl,
api: this.apiUrl,
identity: this.identityUrl,
webVault: this.webVaultUrl,
icons: this.iconsUrl,
notifications: this.notificationsUrl,
});
// re-set urls since service can change them, ex: prefixing https://
this.baseUrl = resUrls.base;
this.apiUrl = resUrls.api;
this.identityUrl = resUrls.identity;
this.webVaultUrl = resUrls.webVault;
this.iconsUrl = resUrls.icons;
this.notificationsUrl = resUrls.notifications;
this.platformUtilsService.showToast("success", null, this.i18nService.t("environmentSaved"));
this.saved();
}
toggleCustom() {
this.showCustom = !this.showCustom;
}
protected saved() {
this.onSaved.emit();
this.modalService.closeAll();
}
}

View File

@@ -1,337 +0,0 @@
import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { AbstractControl, UntypedFormBuilder, ValidatorFn, Validators } from "@angular/forms";
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/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";
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 { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.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 { DialogService } from "@bitwarden/components";
import { CaptchaProtectedComponent } from "../auth/components/captcha-protected.component";
import {
AllValidationErrors,
FormValidationErrorsService,
} from "../platform/abstractions/form-validation-errors.service";
import { PasswordColorText } from "../shared/components/password-strength/password-strength.component";
import { InputsFieldMatch } from "../validators/inputsFieldMatch.validator";
@Directive()
export class RegisterComponent extends CaptchaProtectedComponent implements OnInit {
@Input() isInTrialFlow = false;
@Output() createdAccount = new EventEmitter<string>();
showPassword = false;
formPromise: Promise<RegisterResponse>;
referenceData: ReferenceEventRequest;
showTerms = true;
showErrorSummary = false;
passwordStrengthResult: any;
characterMinimumMessage: string;
minimumLength = Utils.minimumPasswordLength;
color: string;
text: string;
formGroup = this.formBuilder.group(
{
email: ["", [Validators.required, Validators.email]],
name: [""],
masterPassword: ["", [Validators.required, Validators.minLength(this.minimumLength)]],
confirmMasterPassword: ["", [Validators.required, Validators.minLength(this.minimumLength)]],
hint: [
null,
[
InputsFieldMatch.validateInputsDoesntMatch(
"masterPassword",
this.i18nService.t("hintEqualsPassword")
),
],
],
checkForBreaches: [true],
acceptPolicies: [false, [this.acceptPoliciesValidation()]],
},
{
validator: InputsFieldMatch.validateFormInputsMatch(
"masterPassword",
"confirmMasterPassword",
this.i18nService.t("masterPassDoesntMatch")
),
}
);
protected successRoute = "login";
protected accountCreated = false;
protected captchaBypassToken: string = null;
constructor(
protected formValidationErrorService: FormValidationErrorsService,
protected formBuilder: UntypedFormBuilder,
protected authService: AuthService,
protected router: Router,
i18nService: I18nService,
protected cryptoService: CryptoService,
protected apiService: ApiService,
protected stateService: StateService,
platformUtilsService: PlatformUtilsService,
protected passwordGenerationService: PasswordGenerationServiceAbstraction,
environmentService: EnvironmentService,
protected logService: LogService,
protected auditService: AuditService,
protected dialogService: DialogService
) {
super(environmentService, i18nService, platformUtilsService);
this.showTerms = !platformUtilsService.isSelfHost();
this.characterMinimumMessage = this.i18nService.t("characterMinimum", this.minimumLength);
}
async ngOnInit() {
this.setupCaptcha();
}
async submit(showToast = true) {
let email = this.formGroup.value.email;
email = email.trim().toLowerCase();
let name = this.formGroup.value.name;
name = name === "" ? null : name; // Why do we do this?
const masterPassword = this.formGroup.value.masterPassword;
try {
if (!this.accountCreated) {
const registerResponse = await this.registerAccount(
await this.buildRegisterRequest(email, masterPassword, name),
showToast
);
if (!registerResponse.successful) {
return;
}
this.captchaBypassToken = registerResponse.captchaBypassToken;
this.accountCreated = true;
}
if (this.isInTrialFlow) {
if (!this.accountCreated) {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("trialAccountCreated")
);
}
const loginResponse = await this.logIn(email, masterPassword, this.captchaBypassToken);
if (loginResponse.captchaRequired) {
return;
}
this.createdAccount.emit(this.formGroup.value.email);
} else {
this.platformUtilsService.showToast(
"success",
null,
this.i18nService.t("newAccountCreated")
);
this.router.navigate([this.successRoute], { queryParams: { email: email } });
}
} catch (e) {
this.logService.error(e);
}
}
togglePassword() {
this.showPassword = !this.showPassword;
}
getStrengthResult(result: any) {
this.passwordStrengthResult = result;
}
getPasswordScoreText(event: PasswordColorText) {
this.color = event.color;
this.text = event.text;
}
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 "inputsDoesntMatchError":
return this.i18nService.t("masterPassDoesntMatch");
case "inputsMatchError":
return this.i18nService.t("hintEqualsPassword");
case "minlength":
return this.i18nService.t("masterPasswordMinlength", Utils.minimumPasswordLength);
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}`;
}
//validation would be ignored on selfhosted
private acceptPoliciesValidation(): ValidatorFn {
return (control: AbstractControl) => {
const ctrlValue = control.value;
return !ctrlValue && this.showTerms ? { required: true } : null;
};
}
private async validateRegistration(showToast: boolean): Promise<{ isValid: boolean }> {
this.formGroup.markAllAsTouched();
this.showErrorSummary = true;
if (this.formGroup.get("acceptPolicies").hasError("required")) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("acceptPoliciesRequired")
);
return { isValid: false };
}
//web
if (this.formGroup.invalid && !showToast) {
return { isValid: false };
}
//desktop, browser
if (this.formGroup.invalid && showToast) {
const errorText = this.getErrorToastMessage();
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errorText);
return { isValid: false };
}
const passwordWeak =
this.passwordStrengthResult != null && this.passwordStrengthResult.score < 3;
const passwordLeak =
this.formGroup.controls.checkForBreaches.value &&
(await this.auditService.passwordLeaked(this.formGroup.controls.masterPassword.value)) > 0;
if (passwordWeak && passwordLeak) {
const result = await this.dialogService.openSimpleDialog({
title: { key: "weakAndExposedMasterPassword" },
content: { key: "weakAndBreachedMasterPasswordDesc" },
type: "warning",
});
if (!result) {
return { isValid: false };
}
} else if (passwordWeak) {
const result = await this.dialogService.openSimpleDialog({
title: { key: "weakMasterPassword" },
content: { key: "weakMasterPasswordDesc" },
type: "warning",
});
if (!result) {
return { isValid: false };
}
} else if (passwordLeak) {
const result = await this.dialogService.openSimpleDialog({
title: { key: "exposedMasterPassword" },
content: { key: "exposedMasterPasswordDesc" },
type: "warning",
});
if (!result) {
return { isValid: false };
}
}
return { isValid: true };
}
private async buildRegisterRequest(
email: string,
masterPassword: string,
name: string
): Promise<RegisterRequest> {
const hint = this.formGroup.value.hint;
const kdf = DEFAULT_KDF_TYPE;
const kdfConfig = DEFAULT_KDF_CONFIG;
const key = await this.cryptoService.makeMasterKey(masterPassword, email, kdf, kdfConfig);
const newUserKey = await this.cryptoService.makeUserKey(key);
const masterKeyHash = await this.cryptoService.hashMasterKey(masterPassword, key);
const keys = await this.cryptoService.makeKeyPair(newUserKey[0]);
const request = new RegisterRequest(
email,
name,
masterKeyHash,
hint,
newUserKey[1].encryptedString,
this.referenceData,
this.captchaToken,
kdf,
kdfConfig.iterations,
kdfConfig.memory,
kdfConfig.parallelism
);
request.keys = new KeysRequest(keys[0], keys[1].encryptedString);
const orgInvite = await this.stateService.getOrganizationInvitation();
if (orgInvite != null && orgInvite.token != null && orgInvite.organizationUserId != null) {
request.token = orgInvite.token;
request.organizationUserId = orgInvite.organizationUserId;
}
return request;
}
private async registerAccount(
request: RegisterRequest,
showToast: boolean
): Promise<{ successful: boolean; captchaBypassToken?: string }> {
if (!(await this.validateRegistration(showToast)).isValid) {
return { successful: false };
}
this.formPromise = this.apiService.postRegister(request);
try {
const response = await this.formPromise;
return { successful: true, captchaBypassToken: response.captchaBypassToken };
} catch (e) {
if (this.handleCaptchaRequired(e)) {
return { successful: false };
} else {
throw e;
}
}
}
private async logIn(
email: string,
masterPassword: string,
captchaBypassToken: string
): Promise<{ captchaRequired: boolean }> {
const credentials = new PasswordLogInCredentials(
email,
masterPassword,
captchaBypassToken,
null
);
const loginResponse = await this.authService.logIn(credentials);
if (this.handleCaptchaRequired(loginResponse)) {
return { captchaRequired: true };
}
return { captchaRequired: false };
}
}

View File

@@ -1,189 +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 { OrganizationUserService } from "@bitwarden/common/abstractions/organization-user/organization-user.service";
import { OrganizationUserResetPasswordEnrollmentRequest } from "@bitwarden/common/abstractions/organization-user/requests";
import { OrganizationApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/organization/organization-api.service.abstraction";
import { PolicyApiServiceAbstraction } from "@bitwarden/common/admin-console/abstractions/policy/policy-api.service.abstraction";
import { PolicyService } from "@bitwarden/common/admin-console/abstractions/policy/policy.service.abstraction";
import { SetPasswordRequest } from "@bitwarden/common/auth/models/request/set-password.request";
import { HashPurpose, DEFAULT_KDF_TYPE, DEFAULT_KDF_CONFIG } from "@bitwarden/common/enums";
import { KeysRequest } from "@bitwarden/common/models/request/keys.request";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.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 { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { MasterKey, UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
import { DialogService } from "@bitwarden/components";
import { ChangePasswordComponent as BaseChangePasswordComponent } from "../auth/components/change-password.component";
@Directive()
export class SetPasswordComponent extends BaseChangePasswordComponent {
syncLoading = true;
showPassword = false;
hint = "";
identifier: string = null;
orgId: string;
resetPasswordAutoEnroll = false;
onSuccessfulChangePassword: () => Promise<void>;
successRoute = "vault";
constructor(
i18nService: I18nService,
cryptoService: CryptoService,
messagingService: MessagingService,
passwordGenerationService: PasswordGenerationServiceAbstraction,
platformUtilsService: PlatformUtilsService,
private policyApiService: PolicyApiServiceAbstraction,
policyService: PolicyService,
protected router: Router,
private apiService: ApiService,
private syncService: SyncService,
private route: ActivatedRoute,
stateService: StateService,
private organizationApiService: OrganizationApiServiceAbstraction,
private organizationUserService: OrganizationUserService,
dialogService: DialogService
) {
super(
i18nService,
cryptoService,
messagingService,
passwordGenerationService,
platformUtilsService,
policyService,
stateService,
dialogService
);
}
async ngOnInit() {
await this.syncService.fullSync(true);
this.syncLoading = false;
// eslint-disable-next-line rxjs/no-async-subscribe
this.route.queryParams.pipe(first()).subscribe(async (qParams) => {
if (qParams.identifier != null) {
this.identifier = qParams.identifier;
}
});
// Automatic Enrollment Detection
if (this.identifier != null) {
try {
const response = await this.organizationApiService.getAutoEnrollStatus(this.identifier);
this.orgId = response.id;
this.resetPasswordAutoEnroll = response.resetPasswordEnabled;
this.enforcedPolicyOptions =
await this.policyApiService.getMasterPasswordPoliciesForInvitedUsers(this.orgId);
} catch {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}
}
super.ngOnInit();
}
async setupSubmitActions() {
this.kdf = DEFAULT_KDF_TYPE;
this.kdfConfig = DEFAULT_KDF_CONFIG;
return true;
}
async performSubmitActions(
masterPasswordHash: string,
masterKey: MasterKey,
userKey: [UserKey, EncString]
) {
const newKeyPair = await this.cryptoService.makeKeyPair(userKey[0]);
const request = new SetPasswordRequest(
masterPasswordHash,
userKey[1].encryptedString,
this.hint,
this.identifier,
new KeysRequest(newKeyPair[0], newKeyPair[1].encryptedString),
this.kdf,
this.kdfConfig.iterations,
this.kdfConfig.memory,
this.kdfConfig.parallelism
);
try {
if (this.resetPasswordAutoEnroll) {
this.formPromise = this.apiService
.setPassword(request)
.then(async () => {
await this.onSetPasswordSuccess(masterKey, userKey, newKeyPair);
return this.organizationApiService.getKeys(this.orgId);
})
.then(async (response) => {
if (response == null) {
throw new Error(this.i18nService.t("resetPasswordOrgKeysError"));
}
const userId = await this.stateService.getUserId();
const publicKey = Utils.fromB64ToArray(response.publicKey);
// RSA Encrypt user key with organization public key
const userKey = await this.cryptoService.getUserKey();
const encryptedUserKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey);
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.masterPasswordHash = masterPasswordHash;
resetRequest.resetPasswordKey = encryptedUserKey.encryptedString;
return this.organizationUserService.putOrganizationUserResetPasswordEnrollment(
this.orgId,
userId,
resetRequest
);
});
} else {
this.formPromise = this.apiService.setPassword(request).then(async () => {
await this.onSetPasswordSuccess(masterKey, userKey, newKeyPair);
});
}
await this.formPromise;
if (this.onSuccessfulChangePassword != null) {
this.onSuccessfulChangePassword();
} else {
this.router.navigate([this.successRoute]);
}
} catch {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}
}
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
}
private async onSetPasswordSuccess(
masterKey: MasterKey,
userKey: [UserKey, EncString],
keyPair: [string, EncString]
) {
await this.stateService.setKdfType(this.kdf);
await this.stateService.setKdfConfig(this.kdfConfig);
await this.cryptoService.setMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey[0]);
await this.cryptoService.setPrivateKey(keyPair[1].encryptedString);
const localMasterKeyHash = await this.cryptoService.hashMasterKey(
this.masterPassword,
masterKey,
HashPurpose.LocalAuthorization
);
await this.cryptoService.setMasterKeyHash(localMasterKeyHash);
}
}

View File

@@ -1,58 +0,0 @@
import { Directive, OnInit } from "@angular/core";
import { UserVerificationService } from "@bitwarden/common/auth/abstractions/user-verification/user-verification.service.abstraction";
import { KeySuffixOptions } from "@bitwarden/common/enums/key-suffix-options.enum";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { ModalRef } from "./modal/modal.ref";
@Directive()
export class SetPinComponent implements OnInit {
pin = "";
showPin = false;
masterPassOnRestart = true;
showMasterPassOnRestart = true;
constructor(
private modalRef: ModalRef,
private cryptoService: CryptoService,
private userVerificationService: UserVerificationService,
private stateService: StateService
) {}
async ngOnInit() {
this.showMasterPassOnRestart = this.masterPassOnRestart =
await this.userVerificationService.hasMasterPassword();
}
toggleVisibility() {
this.showPin = !this.showPin;
}
async submit() {
if (Utils.isNullOrWhitespace(this.pin)) {
this.modalRef.close(false);
}
const pinKey = await this.cryptoService.makePinKey(
this.pin,
await this.stateService.getEmail(),
await this.stateService.getKdfType(),
await this.stateService.getKdfConfig()
);
const userKey = await this.cryptoService.getUserKey();
const pinProtectedKey = await this.cryptoService.encrypt(userKey.key, pinKey);
const encPin = await this.cryptoService.encrypt(this.pin, userKey);
await this.stateService.setProtectedPin(encPin.encryptedString);
if (this.masterPassOnRestart) {
await this.stateService.setPinKeyEncryptedUserKeyEphemeral(pinProtectedKey);
} else {
await this.stateService.setPinKeyEncryptedUserKey(pinProtectedKey);
}
await this.cryptoService.clearDeprecatedKeys(KeySuffixOptions.Pin);
this.modalRef.close(true);
}
}