1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

Merge Feature/trial initiation (#3036)

* [SG-74] Trial Initiation Component with Vertical Stepper (#2913)

* Vertical stepper PoC

* Convert stepper css to tailwind

* trial component start

* trial component params

* tailwind-ify header

* Support teams, enterprise, and families layout param and more layout ui work

* Some more theming fixes

* Rename TrialModule to TrialInitiationModule

* Stepper fixes, plus more functionality demo

* Cleanup

* layout params and placeholders

* Only allow trial route to be hit if not logged in

* fix typo

* Use background-alt2 color for header

* Move vertical stepper out of trial-initiation

* Create components for the different plan types

* Remove width on steps

* Remove content projection for label

* Tailwind style fixes

* Extract step content into a component

* Remove layout param for now

* Remove step tags

* remove pointer classes from step button

* Remove most tailwind important designations

* Update apps/web/src/app/modules/vertical-stepper/vertical-step.component.ts

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

* Tailwind and layout fixes

* Remove container

* lint & prettier fixes

* Remove extra CdkStep declaration

* Styles fixes

* Style logo directly

* Remove 0 margin on image

* Fix tiling and responsiveness

* Minor padding fixes for org pages

* Update apps/web/src/app/modules/trial-initiation/trial-initiation.component.html

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

* prettier fix

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>

* [SG-65] Reusable Registration Form (#2946)

* created reusable registration form

* fixed conflicts

* replicated reactive form changes in other clients

* removed comments

* client template cleanup

* client template cleanup

* removed comments in template file

* changed to component suffix

* switched show password to use component

* comments resolution

* comments resolution

* added toast disable functionality

* removed unused locale

* mode custom input validator generic

* fixed button

* fixed linter

* removed horizontal rule

* switched to button component

Co-authored-by: Oscar Hinton <Hinton@users.noreply.github.com>
Co-authored-by: gbubemismith <gsmithwalter@gmail.com>
This commit is contained in:
Robyn MacCallum
2022-07-05 15:25:14 -04:00
committed by GitHub
parent 9d1312f2af
commit fb70d8a2d3
38 changed files with 834 additions and 314 deletions

View File

@@ -1,10 +1,19 @@
import { Directive, OnInit } from "@angular/core";
import { FormBuilder, Validators } from "@angular/forms";
import { Router } from "@angular/router";
import {
validateInputsDoesntMatch,
validateInputsMatch,
} from "@bitwarden/angular/validators/fieldsInputCheck.validator";
import { ApiService } from "@bitwarden/common/abstractions/api.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 {
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 { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
@@ -19,22 +28,38 @@ import { CaptchaProtectedComponent } from "./captchaProtected.component";
@Directive()
export class RegisterComponent extends CaptchaProtectedComponent implements OnInit {
name = "";
email = "";
masterPassword = "";
confirmMasterPassword = "";
hint = "";
showPassword = false;
formPromise: Promise<any>;
masterPasswordScore: number;
referenceData: ReferenceEventRequest;
showTerms = true;
acceptPolicies = false;
showErrorSummary = false;
formGroup = this.formBuilder.group({
email: ["", [Validators.required, Validators.email]],
name: [""],
masterPassword: ["", [Validators.required, Validators.minLength(8)]],
confirmMasterPassword: [
"",
[
Validators.required,
Validators.minLength(8),
validateInputsMatch("masterPassword", this.i18nService.t("masterPassDoesntMatch")),
],
],
hint: [
null,
[validateInputsDoesntMatch("masterPassword", this.i18nService.t("hintEqualsPassword"))],
],
acceptPolicies: [false, [Validators.requiredTrue]],
});
protected successRoute = "login";
private masterPasswordStrengthTimeout: any;
constructor(
protected formValidationErrorService: FormValidationErrorsService,
protected formBuilder: FormBuilder,
protected authService: AuthService,
protected router: Router,
i18nService: I18nService,
@@ -84,59 +109,38 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
}
}
async submit() {
if (!this.acceptPolicies && this.showTerms) {
async submit(showToast = true) {
let email = this.formGroup.get("email")?.value;
let name = this.formGroup.get("name")?.value;
const masterPassword = this.formGroup.get("masterPassword")?.value;
const hint = this.formGroup.get("hint")?.value;
this.formGroup.markAllAsTouched();
this.showErrorSummary = true;
if (this.formGroup.get("acceptPolicies").hasError("required")) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("acceptPoliciesError")
this.i18nService.t("acceptPoliciesRequired")
);
return;
}
if (this.email == null || this.email === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("emailRequired")
);
//web
if (this.formGroup.invalid && !showToast) {
return;
}
if (this.email.indexOf("@") === -1) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("invalidEmail")
);
return;
}
if (this.masterPassword == null || this.masterPassword === "") {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassRequired")
);
return;
}
if (this.masterPassword.length < 8) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassLength")
);
return;
}
if (this.masterPassword !== this.confirmMasterPassword) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("masterPassDoesntMatch")
);
//desktop, browser
if (this.formGroup.invalid && showToast) {
const errorText = this.getErrorToastMessage();
this.platformUtilsService.showToast("error", this.i18nService.t("errorOccurred"), errorText);
return;
}
const strengthResult = this.passwordGenerationService.passwordStrength(
this.masterPassword,
masterPassword,
this.getPasswordStrengthUserInput()
);
if (strengthResult != null && strengthResult.score < 3) {
@@ -152,33 +156,19 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
}
}
if (this.hint === this.masterPassword) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
this.i18nService.t("hintEqualsPassword")
);
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,24 +194,25 @@ 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);
}
}
togglePassword(confirmField: boolean) {
togglePassword() {
this.showPassword = !this.showPassword;
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;
@@ -230,19 +221,47 @@ 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;
}
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");
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}`;
}
}

View File

@@ -17,6 +17,7 @@ import { EventService as EventServiceAbstraction } from "@bitwarden/common/abstr
import { ExportService as ExportServiceAbstraction } from "@bitwarden/common/abstractions/export.service";
import { FileUploadService as FileUploadServiceAbstraction } from "@bitwarden/common/abstractions/fileUpload.service";
import { FolderService as FolderServiceAbstraction } from "@bitwarden/common/abstractions/folder.service";
import { FormValidationErrorsService as FormValidationErrorsServiceAbstraction } from "@bitwarden/common/abstractions/formValidationErrors.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service";
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@bitwarden/common/abstractions/keyConnector.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
@@ -58,6 +59,7 @@ import { EventService } from "@bitwarden/common/services/event.service";
import { ExportService } from "@bitwarden/common/services/export.service";
import { FileUploadService } from "@bitwarden/common/services/fileUpload.service";
import { FolderService } from "@bitwarden/common/services/folder.service";
import { FormValidationErrorsService } from "@bitwarden/common/services/formValidationErrors.service";
import { KeyConnectorService } from "@bitwarden/common/services/keyConnector.service";
import { NotificationsService } from "@bitwarden/common/services/notifications.service";
import { OrganizationService } from "@bitwarden/common/services/organization.service";
@@ -444,6 +446,10 @@ export const LOG_MAC_FAILURES = new InjectionToken<string>("LOG_MAC_FAILURES");
provide: AbstractThemingService,
useClass: ThemingService,
},
{
provide: FormValidationErrorsServiceAbstraction,
useClass: FormValidationErrorsService,
},
],
})
export class JslibServicesModule {}

View File

@@ -0,0 +1,37 @@
import { AbstractControl, ValidatorFn } from "@angular/forms";
import { FormGroupControls } from "@bitwarden/common/abstractions/formValidationErrors.service";
//check to ensure two fields do not have the same value
export function validateInputsDoesntMatch(matchTo: string, errorMessage: string): ValidatorFn {
return (control: AbstractControl) => {
if (control.parent && control.parent.controls) {
return control?.value === (control?.parent?.controls as FormGroupControls)[matchTo].value
? {
inputsMatchError: {
message: errorMessage,
},
}
: null;
}
return null;
};
}
//check to ensure two fields have the same value
export function validateInputsMatch(matchTo: string, errorMessage: string): ValidatorFn {
return (control: AbstractControl) => {
if (control.parent && control.parent.controls) {
return control?.value === (control?.parent?.controls as FormGroupControls)[matchTo].value
? null
: {
inputsDoesntMatchError: {
message: errorMessage,
},
};
}
return null;
};
}