From bd3863c313602c521f537f5fabd8f1c8cc41204f Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Thu, 12 Sep 2024 20:14:37 -0400 Subject: [PATCH] Auth/PM-11969 - Registration with Email Verification - Accept Emergency Access Invite Flow (#11018) * PM-11969 - Registration with Email Verification - Accept Emergency Access Invite Fixed * PM-11969 - Fix PR feedback * PM-11969 - AcceptEmergencyComponent - remove prop --- .../web-registration-finish.service.ts | 7 ++++ .../accept/accept-emergency.component.html | 10 ++---- .../accept/accept-emergency.component.ts | 34 +++++++++++++++++++ .../default-registration-finish.service.ts | 6 ++++ .../registration-finish.component.ts | 11 ++++++ .../registration-finish.service.ts | 4 +++ .../registration/register-finish.request.ts | 2 ++ 7 files changed, 66 insertions(+), 8 deletions(-) diff --git a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.ts b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.ts index cf54c5a535..5239601bbc 100644 --- a/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.ts +++ b/apps/web/src/app/auth/core/services/registration/web-registration-finish.service.ts @@ -71,6 +71,8 @@ export class WebRegistrationFinishService userAsymmetricKeys: [string, EncString], emailVerificationToken?: string, orgSponsoredFreeFamilyPlanToken?: string, + acceptEmergencyAccessInviteToken?: string, + emergencyAccessId?: string, ): Promise { const registerRequest = await super.buildRegisterRequest( email, @@ -94,6 +96,11 @@ export class WebRegistrationFinishService registerRequest.orgSponsoredFreeFamilyPlanToken = orgSponsoredFreeFamilyPlanToken; } + if (acceptEmergencyAccessInviteToken && emergencyAccessId) { + registerRequest.acceptEmergencyAccessInviteToken = acceptEmergencyAccessInviteToken; + registerRequest.acceptEmergencyAccessId = emergencyAccessId; + } + return registerRequest; } } diff --git a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.html b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.html index 315df6f2c8..3fa795db15 100644 --- a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.html +++ b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.html @@ -26,14 +26,8 @@ > {{ "logIn" | i18n }} - + diff --git a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts index d5ca41c42c..cd11bc72f3 100644 --- a/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts +++ b/apps/web/src/app/auth/emergency-access/accept/accept-emergency.component.ts @@ -1,5 +1,6 @@ import { Component } from "@angular/core"; import { ActivatedRoute, Params, Router } from "@angular/router"; +import { firstValueFrom } from "rxjs"; import { RegisterRouteService } from "@bitwarden/auth/common"; import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service"; @@ -18,6 +19,8 @@ import { EmergencyAccessService } from "../services/emergency-access.service"; }) export class AcceptEmergencyComponent extends BaseAcceptComponent { name: string; + emergencyAccessId: string; + acceptEmergencyAccessInviteToken: string; protected requiredParameters: string[] = ["id", "name", "email", "token"]; protected failedShortMessage = "emergencyInviteAcceptFailedShort"; @@ -55,5 +58,36 @@ export class AcceptEmergencyComponent extends BaseAcceptComponent { // Fix URL encoding of space issue with Angular this.name = this.name.replace(/\+/g, " "); } + + if (qParams.id) { + this.emergencyAccessId = qParams.id; + } + + if (qParams.token) { + this.acceptEmergencyAccessInviteToken = qParams.token; + } + } + + async register() { + let queryParams: Params; + let registerRoute = await firstValueFrom(this.registerRoute$); + if (registerRoute === "/register") { + queryParams = { + email: this.email, + }; + } else if (registerRoute === "/signup") { + // We have to override the base component route as we don't need users to + // complete email verification if they are coming directly an emailed invite. + registerRoute = "/finish-signup"; + queryParams = { + email: this.email, + acceptEmergencyAccessInviteToken: this.acceptEmergencyAccessInviteToken, + emergencyAccessId: this.emergencyAccessId, + }; + } + + await this.router.navigate([registerRoute], { + queryParams: queryParams, + }); } } diff --git a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts index 882dd7b76c..63b01be995 100644 --- a/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts +++ b/libs/auth/src/angular/registration/registration-finish/default-registration-finish.service.ts @@ -24,6 +24,8 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi passwordInputResult: PasswordInputResult, emailVerificationToken?: string, orgSponsoredFreeFamilyPlanToken?: string, + acceptEmergencyAccessInviteToken?: string, + emergencyAccessId?: string, ): Promise { const [newUserKey, newEncUserKey] = await this.cryptoService.makeUserKey( passwordInputResult.masterKey, @@ -41,6 +43,8 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi userAsymmetricKeys, emailVerificationToken, orgSponsoredFreeFamilyPlanToken, + acceptEmergencyAccessInviteToken, + emergencyAccessId, ); const capchaBypassToken = await this.accountApiService.registerFinish(registerRequest); @@ -55,6 +59,8 @@ export class DefaultRegistrationFinishService implements RegistrationFinishServi userAsymmetricKeys: [string, EncString], emailVerificationToken?: string, orgSponsoredFreeFamilyPlanToken?: string, // web only + acceptEmergencyAccessInviteToken?: string, // web only + emergencyAccessId?: string, // web only ): Promise { const userAsymmetricKeysRequest = new KeysRequest( userAsymmetricKeys[0], diff --git a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts index e9703c6085..ef40d95dce 100644 --- a/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts +++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.component.ts @@ -44,6 +44,10 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { // setup a free family plan sponsored by an organization but they don't have an account yet. orgSponsoredFreeFamilyPlanToken: string; + // this token is provided when the user is coming from an emailed invite to accept an emergency access invite + acceptEmergencyAccessInviteToken: string; + emergencyAccessId: string; + masterPasswordPolicyOptions: MasterPasswordPolicyOptions | null = null; constructor( @@ -79,6 +83,11 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { if (qParams.orgSponsoredFreeFamilyPlanToken != null) { this.orgSponsoredFreeFamilyPlanToken = qParams.orgSponsoredFreeFamilyPlanToken; } + + if (qParams.acceptEmergencyAccessInviteToken != null && qParams.emergencyAccessId) { + this.acceptEmergencyAccessInviteToken = qParams.acceptEmergencyAccessInviteToken; + this.emergencyAccessId = qParams.emergencyAccessId; + } }), switchMap((qParams: Params) => { if ( @@ -111,6 +120,8 @@ export class RegistrationFinishComponent implements OnInit, OnDestroy { passwordInputResult, this.emailVerificationToken, this.orgSponsoredFreeFamilyPlanToken, + this.acceptEmergencyAccessInviteToken, + this.emergencyAccessId, ); } catch (e) { this.validationService.showError(e); diff --git a/libs/auth/src/angular/registration/registration-finish/registration-finish.service.ts b/libs/auth/src/angular/registration/registration-finish/registration-finish.service.ts index 0c674d566c..b585aa78ed 100644 --- a/libs/auth/src/angular/registration/registration-finish/registration-finish.service.ts +++ b/libs/auth/src/angular/registration/registration-finish/registration-finish.service.ts @@ -16,6 +16,8 @@ export abstract class RegistrationFinishService { * @param passwordInputResult The password input result. * @param emailVerificationToken The optional email verification token. Not present in emailed invite scenarios (ex: org invite). * @param orgSponsoredFreeFamilyPlanToken The optional org sponsored free family plan token. + * @param acceptEmergencyAccessInviteToken The optional accept emergency access invite token. + * @param emergencyAccessId The optional emergency access id which is required to validate the emergency access invite token. * Returns a promise which resolves to the captcha bypass token string upon a successful account creation. */ abstract finishRegistration( @@ -23,5 +25,7 @@ export abstract class RegistrationFinishService { passwordInputResult: PasswordInputResult, emailVerificationToken?: string, orgSponsoredFreeFamilyPlanToken?: string, + acceptEmergencyAccessInviteToken?: string, + emergencyAccessId?: string, ): Promise; } diff --git a/libs/common/src/auth/models/request/registration/register-finish.request.ts b/libs/common/src/auth/models/request/registration/register-finish.request.ts index 5e412aa4e6..6a36bf8213 100644 --- a/libs/common/src/auth/models/request/registration/register-finish.request.ts +++ b/libs/common/src/auth/models/request/registration/register-finish.request.ts @@ -19,6 +19,8 @@ export class RegisterFinishRequest { public emailVerificationToken?: string, public orgSponsoredFreeFamilyPlanToken?: string, + public acceptEmergencyAccessInviteToken?: string, + public acceptEmergencyAccessId?: string, // Org Invite data (only applies on web) public organizationUserId?: string,