fix some other bugs of iam
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user