1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 08:13:42 +00:00

Auth/PM-6198 - Registration with Email Verification - Call email clicked endpoint (#10139)

* PM-6198 - Majority of client work done; WIP on registration finish comp

* PM-6198 - Registration Finish - Add registerVerificationEmailClicked logic

* PM-6198 - RegistrationLinkExpired component; added translations on other clients just in case we use the component on other clients in the future.

* PM-6198 - Clean up comment
This commit is contained in:
Jared Snider
2024-07-18 17:37:22 -04:00
committed by GitHub
parent 158da35008
commit 56f5dba444
12 changed files with 264 additions and 17 deletions

View File

@@ -1,10 +1,14 @@
import { CommonModule } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute, Params, Router, RouterModule } from "@angular/router";
import { Subject, takeUntil } from "rxjs";
import { Subject, from, switchMap, takeUntil, tap } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { AccountApiService } from "@bitwarden/common/auth/abstractions/account-api.service";
import { RegisterVerificationEmailClickedRequest } from "@bitwarden/common/auth/models/request/registration/register-verification-email-clicked.request";
import { HttpStatusCode } from "@bitwarden/common/enums";
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { ToastService } from "@bitwarden/components";
@@ -41,33 +45,43 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {
private i18nService: I18nService,
private registrationFinishService: RegistrationFinishService,
private validationService: ValidationService,
private accountApiService: AccountApiService,
) {}
async ngOnInit() {
this.listenForQueryParamChanges();
this.masterPasswordPolicyOptions =
await this.registrationFinishService.getMasterPasswordPolicyOptsFromOrgInvite();
this.loading = false;
}
private listenForQueryParamChanges() {
this.activatedRoute.queryParams.pipe(takeUntil(this.destroy$)).subscribe((qParams: Params) => {
if (qParams.email != null && qParams.email.indexOf("@") > -1) {
this.email = qParams.email;
}
this.activatedRoute.queryParams
.pipe(
tap((qParams: Params) => {
if (qParams.email != null && qParams.email.indexOf("@") > -1) {
this.email = qParams.email;
}
if (qParams.token != null) {
this.emailVerificationToken = qParams.token;
}
if (qParams.token != null) {
this.emailVerificationToken = qParams.token;
}
}),
switchMap((qParams: Params) => {
if (
qParams.fromEmail &&
qParams.fromEmail === "true" &&
this.email &&
this.emailVerificationToken
) {
return from(
this.registerVerificationEmailClicked(this.email, this.emailVerificationToken),
);
}
}),
if (qParams.fromEmail && qParams.fromEmail === "true") {
this.toastService.showToast({
title: null,
message: this.i18nService.t("emailVerifiedV2"),
variant: "success",
});
}
});
takeUntil(this.destroy$),
)
.subscribe();
}
async handlePasswordFormSubmit(passwordInputResult: PasswordInputResult) {
@@ -94,6 +108,48 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy {
await this.router.navigate(["/login"], { queryParams: { email: this.email } });
}
private async registerVerificationEmailClicked(email: string, emailVerificationToken: string) {
const request = new RegisterVerificationEmailClickedRequest(email, emailVerificationToken);
try {
const result = await this.accountApiService.registerVerificationEmailClicked(request);
if (result == null) {
this.toastService.showToast({
title: null,
message: this.i18nService.t("emailVerifiedV2"),
variant: "success",
});
this.loading = false;
}
} catch (e) {
await this.handleRegisterVerificationEmailClickedError(e);
this.loading = false;
}
}
private async handleRegisterVerificationEmailClickedError(e: unknown) {
if (e instanceof ErrorResponse) {
const errorResponse = e as ErrorResponse;
switch (errorResponse.statusCode) {
case HttpStatusCode.BadRequest: {
if (errorResponse.message.includes("Expired link")) {
await this.router.navigate(["/signup-link-expired"]);
} else {
this.validationService.showError(errorResponse);
}
break;
}
default:
this.validationService.showError(errorResponse);
break;
}
} else {
this.validationService.showError(e);
}
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();

View File

@@ -0,0 +1,27 @@
<div class="tw-flex tw-flex-col tw-items-center tw-justify-center">
<bit-icon [icon]="Icons.RegistrationExpiredLinkIcon" class="tw-mb-6"></bit-icon>
<p
bitTypography="body1"
class="tw-text-center tw-mb-3 tw-text-main"
id="restart_registration_body"
>
{{ "pleaseRestartRegistrationOrTryLoggingIn" | i18n }}<br />
{{ "youMayAlreadyHaveAnAccount" | i18n }}
</p>
<a
[block]="true"
type="button"
buttonType="primary"
bitButton
class="tw-mb-3"
routerLink="/signup"
>
{{ "restartRegistration" | i18n }}
</a>
<a [block]="true" type="button" buttonType="secondary" bitButton [routerLink]="loginRoute">
{{ "logIn" | i18n }}
</a>
</div>

View File

@@ -0,0 +1,44 @@
import { CommonModule } from "@angular/common";
import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute, RouterModule } from "@angular/router";
import { Subject, firstValueFrom } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { ButtonModule, IconModule } from "@bitwarden/components";
import { RegistrationExpiredLinkIcon } from "../../icons/registration-expired-link.icon";
/**
* RegistrationLinkExpiredComponentData
* @loginRoute: string - The client specific route to the login page - configured at the app-routing.module level.
*/
export interface RegistrationLinkExpiredComponentData {
loginRoute: string;
}
@Component({
standalone: true,
selector: "auth-registration-link-expired",
templateUrl: "./registration-link-expired.component.html",
imports: [CommonModule, JslibModule, RouterModule, IconModule, ButtonModule],
})
export class RegistrationLinkExpiredComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
loginRoute: string;
readonly Icons = { RegistrationExpiredLinkIcon };
constructor(private activatedRoute: ActivatedRoute) {}
async ngOnInit() {
const routeData = await firstValueFrom(this.activatedRoute.data);
this.loginRoute = routeData["loginRoute"];
}
ngOnDestroy(): void {
this.destroy$.next();
this.destroy$.complete();
}
}