-
@@ -128,6 +134,7 @@
type="submit"
bitButton
buttonType="primary"
+ class="btn-submit"
[disabled]="form.loading"
>
{{ "createAccount" | i18n }}
diff --git a/apps/web/src/app/modules/register-form/register-form.component.ts b/apps/web/src/app/modules/register-form/register-form.component.ts
index 3590470a2e..c2e8183c42 100644
--- a/apps/web/src/app/modules/register-form/register-form.component.ts
+++ b/apps/web/src/app/modules/register-form/register-form.component.ts
@@ -1,9 +1,9 @@
-import { Component, Input } from "@angular/core";
-import { FormBuilder, Validators } from "@angular/forms";
+import { Component, ElementRef, Input, OnInit, ViewChild } from "@angular/core";
+import { FormBuilder, FormGroup, Validators } from "@angular/forms";
import { ActivatedRoute, Router } from "@angular/router";
import { first } from "rxjs/operators";
-import { RegisterComponent as BaseRegisterComponent } from "@bitwarden/angular/components/register.component";
+import { CaptchaProtectedComponent } from "@bitwarden/angular/components/captchaProtected.component";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
@@ -14,10 +14,13 @@ import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwo
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { PolicyService } from "@bitwarden/common/abstractions/policy.service";
import { StateService } from "@bitwarden/common/abstractions/state.service";
+import { DEFAULT_KDF_ITERATIONS, DEFAULT_KDF_TYPE } from "@bitwarden/common/enums/kdfType";
import { PolicyData } from "@bitwarden/common/models/data/policyData";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/models/domain/masterPasswordPolicyOptions";
import { Policy } from "@bitwarden/common/models/domain/policy";
+import { KeysRequest } from "@bitwarden/common/models/request/keysRequest";
import { ReferenceEventRequest } from "@bitwarden/common/models/request/referenceEventRequest";
+import { RegisterRequest } from "@bitwarden/common/models/request/registerRequest";
import { RouterService } from "src/app/services/router.service";
@@ -25,57 +28,52 @@ import { RouterService } from "src/app/services/router.service";
selector: "app-register-form",
templateUrl: "./register-form.component.html",
})
-export class RegisterFormComponent extends BaseRegisterComponent {
+export class RegisterFormComponent extends CaptchaProtectedComponent implements OnInit {
+ @ViewChild("masterPassword") masterPasswordRef: ElementRef;
+ @ViewChild("masterPasswordRetype") masterPasswordRetypeRef: ElementRef;
+
+ showTerms = true;
showCreateOrgMessage = false;
- @Input() layout = "";
+ masterPasswordScore: number;
+ showPassword = false;
+ referenceData: ReferenceEventRequest;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
+ formGroup: FormGroup;
+ formPromise: Promise
;
+ showErrorSummary = false;
private policies: Policy[];
- formData = this.formBuilder.group({
- email: ["", [Validators.required, Validators.email]],
- name: ["", [Validators.required]],
- masterPassword: ["", [Validators.required]],
- confirmMasterPassword: ["", [Validators.required]],
- hint: [],
- acceptPolicies: [false, [Validators.requiredTrue]],
- });
+ protected successRoute = "login";
+ private masterPasswordStrengthTimeout: any;
constructor(
private formBuilder: FormBuilder,
authService: AuthService,
- router: Router,
+ private router: Router,
i18nService: I18nService,
- cryptoService: CryptoService,
- apiService: ApiService,
+ private cryptoService: CryptoService,
+ private apiService: ApiService,
private route: ActivatedRoute,
- stateService: StateService,
+ private stateService: StateService,
platformUtilsService: PlatformUtilsService,
- passwordGenerationService: PasswordGenerationService,
+ private passwordGenerationService: PasswordGenerationService,
private policyService: PolicyService,
environmentService: EnvironmentService,
- logService: LogService,
+ private logService: LogService,
private routerService: RouterService
) {
- super(
- authService,
- router,
- i18nService,
- cryptoService,
- apiService,
- stateService,
- platformUtilsService,
- passwordGenerationService,
- environmentService,
- logService
- );
+ super(environmentService, i18nService, platformUtilsService);
+ this.showTerms = !platformUtilsService.isSelfHost();
}
async ngOnInit() {
+ this.createRegisterForm();
+
this.route.queryParams.pipe(first()).subscribe((qParams) => {
this.referenceData = new ReferenceEventRequest();
if (qParams.email != null && qParams.email.indexOf("@") > -1) {
- this.email = qParams.email;
+ this.formGroup.get("email")?.setValue(qParams.email);
}
if (qParams.premium != null) {
this.routerService.setPreviousUrl("/settings/premium");
@@ -87,9 +85,7 @@ export class RegisterFormComponent extends BaseRegisterComponent {
});
this.routerService.setPreviousUrl(route.toString());
}
- if (qParams.layout != null) {
- this.layout = this.referenceData.layout = qParams.layout;
- }
+
if (qParams.reference != null) {
this.referenceData.id = qParams.reference;
} else {
@@ -135,15 +131,68 @@ export class RegisterFormComponent extends BaseRegisterComponent {
);
}
- await super.ngOnInit();
+ this.setupCaptcha();
+ }
+
+ get masterPasswordScoreWidth() {
+ return this.masterPasswordScore == null ? 0 : (this.masterPasswordScore + 1) * 20;
+ }
+
+ get masterPasswordScoreColor() {
+ switch (this.masterPasswordScore) {
+ case 4:
+ return "success";
+ case 3:
+ return "primary";
+ case 2:
+ return "warning";
+ default:
+ return "danger";
+ }
+ }
+
+ get masterPasswordScoreText() {
+ switch (this.masterPasswordScore) {
+ case 4:
+ return this.i18nService.t("strong");
+ case 3:
+ return this.i18nService.t("good");
+ case 2:
+ return this.i18nService.t("weak");
+ default:
+ return this.masterPasswordScore != null ? this.i18nService.t("weak") : null;
+ }
}
async submit() {
+ let email = this.formGroup.get("email")?.value;
+ let name = this.formGroup.get("name")?.value;
+ const masterPassword = this.formGroup.get("masterPassword")?.value;
+ const confirmMasterPassword = this.formGroup.get("confirmMasterPassword")?.value;
+ const hint = this.formGroup.get("hint")?.value;
+ const acceptPolicies = this.formGroup.get("acceptPolicies")?.value;
+
+ // if (!acceptPolicies && this.showTerms) {
+ // this.platformUtilsService.showToast(
+ // "error",
+ // this.i18nService.t("errorOccurred"),
+ // this.i18nService.t("acceptPoliciesError")
+ // );
+ // return;
+ // }
+
+ this.formGroup.markAllAsTouched();
+ this.showErrorSummary = true;
+
+ if (!this.formGroup.valid) {
+ return;
+ }
+
if (
this.enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword(
this.masterPasswordScore,
- this.masterPassword,
+ masterPassword,
this.enforcedPolicyOptions
)
) {
@@ -155,6 +204,138 @@ export class RegisterFormComponent extends BaseRegisterComponent {
return;
}
- await super.submit();
+ console.log("Here::", acceptPolicies, this.showTerms);
+
+ if (masterPassword !== confirmMasterPassword) {
+ this.platformUtilsService.showToast(
+ "error",
+ this.i18nService.t("errorOccurred"),
+ this.i18nService.t("masterPassDoesntMatch")
+ );
+ return;
+ }
+
+ const strengthResult = this.passwordGenerationService.passwordStrength(
+ masterPassword,
+ this.getPasswordStrengthUserInput()
+ );
+ 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;
+ }
+ }
+
+ if (hint === masterPassword) {
+ this.platformUtilsService.showToast(
+ "error",
+ this.i18nService.t("errorOccurred"),
+ this.i18nService.t("hintEqualsPassword")
+ );
+ return;
+ }
+
+ name = name === "" ? null : name;
+ email = email.trim().toLowerCase();
+ const kdf = DEFAULT_KDF_TYPE;
+ const kdfIterations = DEFAULT_KDF_ITERATIONS;
+ const key = await this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations);
+ const encKey = await this.cryptoService.makeEncKey(key);
+ const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
+ const keys = await this.cryptoService.makeKeyPair(encKey[0]);
+ const request = new RegisterRequest(
+ email,
+ name,
+ hashedPassword,
+ hint,
+ encKey[1].encryptedString,
+ kdf,
+ kdfIterations,
+ this.referenceData,
+ this.captchaToken
+ );
+ 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;
+ }
+
+ try {
+ this.formPromise = this.apiService.postRegister(request);
+ try {
+ await this.formPromise;
+ } catch (e) {
+ if (this.handleCaptchaRequired(e)) {
+ return;
+ } else {
+ throw e;
+ }
+ }
+ this.platformUtilsService.showToast("success", null, this.i18nService.t("newAccountCreated"));
+ this.router.navigate([this.successRoute], { queryParams: { email: email } });
+ } catch (e) {
+ this.logService.error(e);
+ }
+ }
+
+ togglePassword(confirmField: boolean) {
+ this.showPassword = !this.showPassword;
+ confirmField
+ ? this.masterPasswordRetypeRef.nativeElement.focus()
+ : this.masterPasswordRef.nativeElement.focus();
+ // document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
+ }
+
+ updatePasswordStrength() {
+ const masterPassword = this.formGroup.get("masterPassword")?.value;
+
+ if (this.masterPasswordStrengthTimeout != null) {
+ clearTimeout(this.masterPasswordStrengthTimeout);
+ }
+ this.masterPasswordStrengthTimeout = setTimeout(() => {
+ const strengthResult = this.passwordGenerationService.passwordStrength(
+ masterPassword,
+ this.getPasswordStrengthUserInput()
+ );
+ this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
+ }, 300);
+ }
+
+ private getPasswordStrengthUserInput() {
+ let userInput: string[] = [];
+ const email = this.formGroup.get("email")?.value;
+ const name = this.formGroup.get("name").value;
+ const atPosition = email.indexOf("@");
+ if (atPosition > -1) {
+ userInput = userInput.concat(
+ email
+ .substr(0, atPosition)
+ .trim()
+ .toLowerCase()
+ .split(/[^A-Za-z0-9]/)
+ );
+ }
+ if (name != null && name !== "") {
+ userInput = userInput.concat(name.trim().toLowerCase().split(" "));
+ }
+ return userInput;
+ }
+
+ private createRegisterForm() {
+ this.formGroup = this.formBuilder.group({
+ email: ["", [Validators.required, Validators.email]],
+ name: [""],
+ masterPassword: ["", [Validators.required]],
+ confirmMasterPassword: ["", [Validators.required]],
+ hint: [],
+ acceptPolicies: [false, [Validators.requiredTrue]],
+ });
}
}
diff --git a/apps/web/src/locales/en/messages.json b/apps/web/src/locales/en/messages.json
index a930e76fbf..fab52c9fba 100644
--- a/apps/web/src/locales/en/messages.json
+++ b/apps/web/src/locales/en/messages.json
@@ -593,6 +593,9 @@
"masterPassDesc": {
"message": "The master password is the password you use to access your vault. It is very important that you do not forget your master password. There is no way to recover the password in the event that you forget it."
},
+ "masterPassImportant": {
+ "message": "Master passwords cannot be recovered if you forget it!"
+ },
"masterPassHintDesc": {
"message": "A master password hint can help you remember your password if you forget it."
},
diff --git a/libs/angular/src/components/register.component.ts b/libs/angular/src/components/register.component.ts
index 5873c08e8f..ee48921c06 100644
--- a/libs/angular/src/components/register.component.ts
+++ b/libs/angular/src/components/register.component.ts
@@ -1,4 +1,5 @@
-import { Directive, OnInit } from "@angular/core";
+import { Directive, ElementRef, OnInit, ViewChild } from "@angular/core";
+import { FormBuilder, Validators } from "@angular/forms";
import { Router } from "@angular/router";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
@@ -19,6 +20,8 @@ import { CaptchaProtectedComponent } from "./captchaProtected.component";
@Directive()
export class RegisterComponent extends CaptchaProtectedComponent implements OnInit {
+ @ViewChild("masterPassword") masterPasswordRef: ElementRef;
+ @ViewChild("masterPasswordRetype") masterPasswordRetypeRef: ElementRef;
name = "";
email = "";
masterPassword = "";
@@ -31,10 +34,20 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
showTerms = true;
acceptPolicies = false;
+ formGroup = this.formBuilder.group({
+ email: ["", [Validators.required, Validators.email]],
+ name: [""],
+ masterPassword: ["", [Validators.required]],
+ confirmMasterPassword: ["", [Validators.required]],
+ hint: [],
+ acceptPolicies: [false, [Validators.requiredTrue]],
+ });
+
protected successRoute = "login";
private masterPasswordStrengthTimeout: any;
constructor(
+ protected formBuilder: FormBuilder,
protected authService: AuthService,
protected router: Router,
i18nService: I18nService,
@@ -85,7 +98,20 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
}
async submit() {
- if (!this.acceptPolicies && this.showTerms) {
+ let email = this.formGroup.get("email")?.value;
+ let name = this.formGroup.get("name")?.value;
+ const masterPassword = this.formGroup.get("masterPassword")?.value;
+ const confirmMasterPassword = this.formGroup.get("confirmMasterPassword")?.value;
+ const hint = this.formGroup.get("hint")?.value;
+ const acceptPolicies = this.formGroup.get("acceptPolicies")?.value;
+
+ this.formGroup.markAllAsTouched();
+
+ if (!this.formGroup.valid) {
+ return;
+ }
+
+ if (!acceptPolicies && this.showTerms) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
@@ -94,7 +120,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
return;
}
- if (this.email == null || this.email === "") {
+ if (email == null || email === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
@@ -102,7 +128,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
);
return;
}
- if (this.email.indexOf("@") === -1) {
+ if (email.indexOf("@") === -1) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
@@ -110,7 +136,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
);
return;
}
- if (this.masterPassword == null || this.masterPassword === "") {
+ if (masterPassword == null || masterPassword === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
@@ -118,7 +144,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
);
return;
}
- if (this.masterPassword.length < 8) {
+ if (masterPassword.length < 8) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
@@ -126,7 +152,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
);
return;
}
- if (this.masterPassword !== this.confirmMasterPassword) {
+ if (masterPassword !== confirmMasterPassword) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
@@ -136,7 +162,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
}
const strengthResult = this.passwordGenerationService.passwordStrength(
- this.masterPassword,
+ masterPassword,
this.getPasswordStrengthUserInput()
);
if (strengthResult != null && strengthResult.score < 3) {
@@ -152,7 +178,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
}
}
- if (this.hint === this.masterPassword) {
+ if (hint === masterPassword) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
@@ -161,24 +187,19 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
return;
}
- this.name = this.name === "" ? null : this.name;
- this.email = this.email.trim().toLowerCase();
+ name = name === "" ? null : name;
+ email = email.trim().toLowerCase();
const kdf = DEFAULT_KDF_TYPE;
const kdfIterations = DEFAULT_KDF_ITERATIONS;
- const key = await this.cryptoService.makeKey(
- this.masterPassword,
- this.email,
- kdf,
- kdfIterations
- );
+ const key = await this.cryptoService.makeKey(masterPassword, email, kdf, kdfIterations);
const encKey = await this.cryptoService.makeEncKey(key);
- const hashedPassword = await this.cryptoService.hashPassword(this.masterPassword, key);
+ const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
const request = new RegisterRequest(
- this.email,
- this.name,
+ email,
+ name,
hashedPassword,
- this.hint,
+ hint,
encKey[1].encryptedString,
kdf,
kdfIterations,
@@ -204,7 +225,7 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
}
}
this.platformUtilsService.showToast("success", null, this.i18nService.t("newAccountCreated"));
- this.router.navigate([this.successRoute], { queryParams: { email: this.email } });
+ this.router.navigate([this.successRoute], { queryParams: { email: email } });
} catch (e) {
this.logService.error(e);
}
@@ -212,18 +233,21 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
togglePassword(confirmField: boolean) {
this.showPassword = !this.showPassword;
- console.log("Here::", document.getElementById("masterPasswordRetype"));
- console.log("Here2::", document.getElementById("masterPassword"));
- document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
+ confirmField
+ ? this.masterPasswordRetypeRef.nativeElement.focus()
+ : this.masterPasswordRef.nativeElement.focus();
+ // document.getElementById(confirmField ? "masterPasswordRetype" : "masterPassword").focus();
}
updatePasswordStrength() {
+ const masterPassword = this.formGroup.get("masterPassword")?.value;
+
if (this.masterPasswordStrengthTimeout != null) {
clearTimeout(this.masterPasswordStrengthTimeout);
}
this.masterPasswordStrengthTimeout = setTimeout(() => {
const strengthResult = this.passwordGenerationService.passwordStrength(
- this.masterPassword,
+ masterPassword,
this.getPasswordStrengthUserInput()
);
this.masterPasswordScore = strengthResult == null ? null : strengthResult.score;
@@ -232,18 +256,20 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
private getPasswordStrengthUserInput() {
let userInput: string[] = [];
- const atPosition = this.email.indexOf("@");
+ const email = this.formGroup.get("email")?.value;
+ const name = this.formGroup.get("name").value;
+ const atPosition = email.indexOf("@");
if (atPosition > -1) {
userInput = userInput.concat(
- this.email
+ email
.substr(0, atPosition)
.trim()
.toLowerCase()
.split(/[^A-Za-z0-9]/)
);
}
- if (this.name != null && this.name !== "") {
- userInput = userInput.concat(this.name.trim().toLowerCase().split(" "));
+ if (name != null && name !== "") {
+ userInput = userInput.concat(name.trim().toLowerCase().split(" "));
}
return userInput;
}