From 2f39ed6c5f0d72915623c54b8ba430104adc56b1 Mon Sep 17 00:00:00 2001 From: Abubeker Yasin Date: Tue, 7 Jul 2026 10:51:02 +0300 Subject: [PATCH] fix: ( iam ) allow re-registration of abandoned pending accounts --- .../modules/auth/passenger-auth.service.ts | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/apps/edr-passenger-api/src/modules/auth/passenger-auth.service.ts b/apps/edr-passenger-api/src/modules/auth/passenger-auth.service.ts index 1784261e5..2b15cdc7c 100644 --- a/apps/edr-passenger-api/src/modules/auth/passenger-auth.service.ts +++ b/apps/edr-passenger-api/src/modules/auth/passenger-auth.service.ts @@ -42,11 +42,7 @@ export class PassengerAuthService { } async register(dto: RegisterDto, req: any) { - const existing = await this.dataSource.query<{ id: string }[]>( - `SELECT id FROM iam.users WHERE email = $1 OR phone_number = $2 LIMIT 1`, - [dto.email, dto.phoneNumber], - ); - if (existing.length) throw new ConflictException('Email or phone already registered'); + await this.clearPendingOrConflict(dto.email, dto.phoneNumber); const iamAuthService = await this.resolveIamAuthService(req); @@ -101,11 +97,7 @@ export class PassengerAuthService { }, req: any, ): Promise<{ iamUserId: string; passengerId: string }> { - const existing = await this.dataSource.query<{ id: string }[]>( - `SELECT id FROM iam.users WHERE email = $1 OR phone_number = $2 LIMIT 1`, - [dto.email, dto.phoneNumber], - ); - if (existing.length) throw new ConflictException('Email or phone already registered'); + await this.clearPendingOrConflict(dto.email, dto.phoneNumber); const iamAuthService = await this.resolveIamAuthService(req); await iamAuthService.signupWithPassword({ @@ -600,6 +592,32 @@ export class PassengerAuthService { return `+${digits}`; } + /** + * Pre-signup uniqueness guard. Throws `ConflictException` only when a + * *fully-registered* account (`has_set_password = true`) already owns the + * email or phone. Abandoned PENDING signups — where the user received the OTP + * but never completed `set-password` — are deleted so this fresh attempt can + * re-create the account and re-send the code, instead of being blocked with a + * 409 forever. Matches `resendRegistrationCode`'s `has_set_password = false` + * notion of "still pending". + */ + private async clearPendingOrConflict(email: string, phoneNumber: string): Promise { + const matches = await this.dataSource.query< + { id: string; email: string; has_set_password: boolean }[] + >( + `SELECT id, email, has_set_password FROM iam.users WHERE email = $1 OR phone_number = $2`, + [email, phoneNumber], + ); + if (!matches.length) return; + if (matches.some((u) => u.has_set_password)) { + throw new ConflictException('Email or phone already registered'); + } + // Every match is an abandoned pending signup — clean it up so the caller can proceed. + for (const u of matches) { + await this.compensateIamSignup(u.email); + } + } + private async compensateIamSignup(email: string): Promise { try { const rows = await this.dataSource.query<{ id: string }[]>(