fix: add user email fall back

This commit is contained in:
Nathnael
2026-08-08 15:38:00 +00:00
parent 7a32bb95ff
commit 352c873961
2 changed files with 46 additions and 11 deletions

View File

@@ -421,7 +421,10 @@ export class CompaniesController {
@CurrentUser() user: CurrentIamUser,
@Body() dto: CompleteIdentityVerificationDto,
): Promise<CompanyIdentityStateDto> {
return this.companiesService.completeIdentityVerification(user.id, dto);
return this.companiesService.completeIdentityVerification(user.id, dto, {
email: user.email,
phoneNumber: user.phoneNumber,
});
}
@Post("identity/gm/same-as-owner")
@@ -434,7 +437,10 @@ export class CompaniesController {
async setGmSameAsOwner(
@CurrentUser() user: CurrentIamUser,
): Promise<CompanyIdentityStateDto> {
return this.companiesService.setGmSameAsOwner(user.id);
return this.companiesService.setGmSameAsOwner(user.id, {
email: user.email,
phoneNumber: user.phoneNumber,
});
}
@Delete("identity/gm")

View File

@@ -2749,6 +2749,12 @@ export class CompaniesService {
async completeIdentityVerification(
userId: string,
dto: CompleteIdentityVerificationDto,
/**
* The signed-in account, used as the owner's fallback contact details.
* Optional so the callers that only have a user id keep compiling — they
* simply get no fallback.
*/
account?: { email?: string; phoneNumber?: string },
): Promise<CompanyIdentityStateDto> {
const { company } = await this.getCompanyInfoByUserId(userId);
const prefix = IDENTITY_PREFIX[dto.subject];
@@ -2780,6 +2786,22 @@ export class CompaniesService {
}
const now = new Date().toISOString();
// Fayda's email and phone claims are optional and routinely come back empty.
// For the owner that leaves the company with no contact details at all: the
// step renders no input for them (they are the verification's output), and
// "same as owner" then copies those blanks onto `generalManagerEmail` /
// `generalManagerPhone`, which `REQUIRED_COMPANY_INFO` demands at submit —
// an unfixable dead end. The account doing the onboarding is the one contact
// we always have, and it is already OTP-proven, so it stands in.
//
// Owner only: the PoA and the GM are other people, and the registering
// account's address is not theirs to wear.
const isOwner = dto.subject === "owner";
const email = result.email || (isOwner ? account?.email : undefined);
const phone =
result.phoneNumber || (isOwner ? account?.phoneNumber : undefined);
const identity: VerifiedIdentityAttributes = {
[`${prefix}FaydaSub`]: result.sub,
[`${prefix}FaydaVerifiedAt`]: now,
@@ -2787,14 +2809,12 @@ export class CompaniesService {
[`${prefix}Gender`]: result.gender ?? null,
// The verified payload owns the person's details from here on.
...(result.fullName ? { [`${prefix}Name`]: result.fullName } : {}),
...(result.email ? { [`${prefix}Email`]: result.email } : {}),
...(email ? { [`${prefix}Email`]: email } : {}),
// Fayda returns whatever the national registry holds, which is routinely a
// local number ("0911223344"). Every typed phone in this service is stored
// E.164, and `@IsValidPhone()` rejects anything else — so a raw claim here
// becomes a value the portal reads back and cannot resubmit.
...(result.phoneNumber
? { [`${prefix}Phone`]: normalizeE164(result.phoneNumber) }
: {}),
...(phone ? { [`${prefix}Phone`]: normalizeE164(phone) } : {}),
...(result.address ? { [`${prefix}Address`]: result.address } : {}),
};
@@ -2844,7 +2864,13 @@ export class CompaniesService {
* proven identity to copy, only typed text that would arrive wearing a
* verified badge.
*/
async setGmSameAsOwner(userId: string): Promise<CompanyIdentityStateDto> {
async setGmSameAsOwner(
userId: string,
/** Same fallback as {@link completeIdentityVerification}, for owners
* verified before that fallback existed — their stored contact details are
* blank, and copying blanks here would block the submit. */
account?: { email?: string; phoneNumber?: string },
): Promise<CompanyIdentityStateDto> {
const { company } = await this.getCompanyInfoByUserId(userId);
const attrs = company.attributes ?? {};
const ownerSub = attrs.ownerFaydaSub as string | undefined;
@@ -2854,20 +2880,23 @@ export class CompaniesService {
);
}
const ownerEmail = (attrs.ownerEmail as string | undefined) || account?.email || null;
const ownerPhone = (attrs.ownerPhone as string | undefined) || account?.phoneNumber || null;
const copied: Record<string, unknown> = {
gmSameAsOwner: true,
gmFaydaSub: ownerSub,
gmFaydaVerifiedAt: attrs.ownerFaydaVerifiedAt ?? new Date().toISOString(),
gmName: attrs.ownerName ?? null,
gmEmail: attrs.ownerEmail ?? null,
gmPhone: attrs.ownerPhone ?? null,
gmEmail: ownerEmail,
gmPhone: ownerPhone ? normalizeE164(ownerPhone) : null,
gmAddress: attrs.ownerAddress ?? null,
gmBirthdate: attrs.ownerBirthdate ?? null,
gmGender: attrs.ownerGender ?? null,
// Kept in step for the notifiers, same as a GM verification does.
generalManagerName: attrs.ownerName ?? null,
generalManagerEmail: attrs.ownerEmail ?? null,
generalManagerPhone: attrs.ownerPhone ?? null,
generalManagerEmail: ownerEmail,
generalManagerPhone: ownerPhone ? normalizeE164(ownerPhone) : null,
};
const updated = await this.companiesRepo.update(company.id, {