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
+63
View File
@@ -87090,6 +87090,28 @@ var require_iam_service = __commonJS({
return null;
return `${this.issuer}/auth/bootstrap?ticket=${encodeURIComponent(ticket)}`;
}
/**
* پایان همهٔ نشست‌های IAM این شخص، از جمله نشستی که اپ دیگر با آن وارد شده.
* کلاینت باید خودش در همان نشست شرکت کرده باشد.
*/
async endDelegatedSession(sessionId) {
if (!this.enabled || !this.issuer || !this.clientId)
return;
const timeout = Number((0, env_12.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: (0, env_12.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, codeVerifier, redirectUri) {
const timeout = Number((0, env_12.envOr)("IAM_HTTP_TIMEOUT_MS", "8000"));
@@ -87225,6 +87247,16 @@ var require_app_session_service = __commonJS({
});
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) {
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) => Boolean(id)))];
}
/** Revoke by the IAM's session id (`sid` in a back-channel logout token). */
revokeByIamSession(iamSessionId, reason = "iam_backchannel_logout") {
return this.revokeWhere({ iamSessionId }, reason);
@@ -88565,6 +88597,25 @@ var require_customer_auth_service = __commonJS({
url.searchParams.set("post_logout_redirect_uri", redirect);
return url.toString();
}
/**
* نشست IAM همین ورود را می‌بندد تا اپ دیگر هم با رفرش خارج شود.
* شکست اینجا خروج محلی را متوقف نمی‌کند؛ پرش مرورگر به IAM هنوز کوکی را پاک می‌کند.
*/
async endSharedSessions(mobile) {
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.message}`);
}
}
return { ended };
}
withBootstrap(body, identity) {
const url = this.iam.browserBootstrapUrl(identity?.browserTicket);
return url ? { ...body, iamBootstrapUrl: url } : body;
@@ -88848,6 +88899,9 @@ var require_customer_auth_controller = __commonJS({
ssoLogoutUrl(returnUrl) {
return { logout_url: this.auth.centralLogoutUrl(returnUrl) };
}
logout(me) {
return this.auth.endSharedSessions(me.mobile);
}
me(me) {
return this.customers.getOrThrow(me.id);
}
@@ -88908,6 +88962,15 @@ var require_customer_auth_controller = __commonJS({
__metadata3("design:paramtypes", [String]),
__metadata3("design:returntype", void 0)
], CustomerAuthController.prototype, "ssoLogoutUrl", null);
__decorate4([
(0, customer_guards_1.CustomerAuth)(),
(0, common_12.Post)("auth/logout"),
(0, common_12.HttpCode)(200),
__param3(0, (0, customer_guards_1.CurrentCustomer)()),
__metadata3("design:type", Function),
__metadata3("design:paramtypes", [Object]),
__metadata3("design:returntype", void 0)
], CustomerAuthController.prototype, "logout", null);
__decorate4([
(0, customer_guards_1.CustomerAuth)(),
(0, common_12.Get)("me"),
@@ -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);