fix some other bugs of iam

This commit is contained in:
Montech
2026-10-11 16:01:15 +03:30
parent e4924a28f5
commit 025d223dac
227 changed files with 376 additions and 221 deletions
@@ -73,6 +73,13 @@ export class CustomerAuthController {
return { logout_url: this.auth.centralLogoutUrl(returnUrl) };
}
@CustomerAuth()
@Post('auth/logout')
@HttpCode(200)
logout(@CurrentCustomer() me: AuthenticatedCustomer) {
return this.auth.endSharedSessions(me.mobile);
}
@CustomerAuth()
@Get('me')
me(@CurrentCustomer() me: AuthenticatedCustomer) {
@@ -608,6 +608,25 @@ export class CustomerAuthService {
return url.toString();
}
/**
* نشست IAM همین ورود را می‌بندد تا اپ دیگر هم با رفرش خارج شود.
* شکست اینجا خروج محلی را متوقف نمی‌کند؛ پرش مرورگر به IAM هنوز کوکی را پاک می‌کند.
*/
async endSharedSessions(mobile: string): Promise<{ ended: number }> {
if (!this.iam.enabled) return { ended: 0 };
const ids = await this.sessions.activeIamSessionIds(mobile);
let ended = 0;
for (const id of ids) {
try {
await this.iam.endDelegatedSession(id);
ended += 1;
} catch (error) {
this.logger.warn(`central sign-out skipped for ${id}: ${(error as Error).message}`);
}
}
return { ended };
}
private withBootstrap<T extends { token: string; customer: { id: string; mobile: string } }>(
body: T,
identity: IamIdentity | null,
+22
View File
@@ -282,6 +282,28 @@ export class IamService {
return `${this.issuer}/auth/bootstrap?ticket=${encodeURIComponent(ticket)}`;
}
/**
* پایان همهٔ نشست‌های IAM این شخص، از جمله نشستی که اپ دیگر با آن وارد شده.
* کلاینت باید خودش در همان نشست شرکت کرده باشد.
*/
async endDelegatedSession(sessionId: string): Promise<void> {
if (!this.enabled || !this.issuer || !this.clientId) return;
const timeout = Number(envOr('IAM_HTTP_TIMEOUT_MS', '8000'));
const response = await fetch(`${this.issuer}/api/v1/auth/session/end`, {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify({
client_id: this.clientId,
client_secret: envOrNull('IAM_CLIENT_SECRET'),
session_id: sessionId,
}),
signal: AbortSignal.timeout(timeout),
});
if (!response.ok && response.status !== 403) {
throw new IamUnavailableError(`identity service refused session end (${response.status})`);
}
}
/** تبادل کد مجوز مرورگر. پاسخ از خود issuer و با راز کلاینت آمده است. */
async exchangeAuthorizationCode(
code: string,
@@ -90,6 +90,16 @@ export class AppSessionService {
return session !== null && session.revokedAt === null;
}
/** IAM session ids still tied to this mobile, so logout can end the other app too. */
async activeIamSessionIds(mobile: string): Promise<string[]> {
if (!this.enabled || !mobile) return [];
const rows = await this.prisma.appSession.findMany({
where: { mobile, revokedAt: null, iamSessionId: { not: null } },
select: { iamSessionId: true },
});
return [...new Set(rows.map((row) => row.iamSessionId).filter((id): id is string => Boolean(id)))];
}
/** Revoke by the IAM's session id (`sid` in a back-channel logout token). */
revokeByIamSession(iamSessionId: string, reason = 'iam_backchannel_logout'): Promise<number> {
return this.revokeWhere({ iamSessionId }, reason);