Merge branch 'alpha' of github.com:Tria-plc/edr-platform into alpha

This commit is contained in:
Stephanos A
2026-07-07 15:17:40 +03:00
226 changed files with 16697 additions and 1112 deletions

View File

@@ -64,6 +64,7 @@ import { TasksModule } from './modules/tasks/tasks.module';
import { AppReleasesModule } from './modules/app-releases/app-releases.module';
import { ConfigurableFareModule } from './modules/configurable-fare/configurable-fare.module';
import { SegmentFareSeeder } from './seed/segment-fare.seeder';
import { EOtpType } from "@tria-plc/iamapi-common";
@Module({
imports: [
@@ -97,6 +98,16 @@ import { SegmentFareSeeder } from './seed/segment-fare.seeder';
TriaIamModule.forRoot({
applications: [EDR_PASSENGER_APPLICATION],
permissions: EDR_PASSENGER_PERMISSIONS,
otpMessages: {
[EOtpType.MFA_LOGIN]: ({ otp }) =>
`Your EDR Passenger login code is ${otp}. It will expire in 5 minutes.`,
[EOtpType.VERIFY_PHONE_NUMBER]: ({ otp }) =>
`Your EDR Passenger phone verification code is ${otp}. It will expire in 5 minutes.`,
[EOtpType.RESET_PASSWORD]: ({ route }) =>
`Reset your EDR Passenger password using this link: ${route}`,
[EOtpType.SET_PASSWORD]: ({ route }) =>
`Set your EDR Passenger password using this link: ${route}`,
},
}),
SharedAuthModule,
PrismaModule,

View File

@@ -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<void> {
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<void> {
try {
const rows = await this.dataSource.query<{ id: string }[]>(