@acorex-platform/framework-server 0.0.33 → 0.0.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/background-job/src/lib/schedule/background-job-schedule-scanner.service.d.ts.map +1 -1
- package/background-job/src/lib/schedule/background-job-schedule-scanner.service.js +29 -2
- package/background-job/src/lib/schedule/background-job-schedule-scanner.service.js.map +1 -1
- package/core/src/lib/runtime/runtime-authz.guard.d.ts +3 -2
- package/core/src/lib/runtime/runtime-authz.guard.d.ts.map +1 -1
- package/core/src/lib/runtime/runtime-authz.guard.js +7 -6
- package/core/src/lib/runtime/runtime-authz.guard.js.map +1 -1
- package/domain/seed/src/lib/category-companion-entity.builder.d.ts +1 -1
- package/domain/seed/src/lib/category-companion-entity.builder.d.ts.map +1 -1
- package/domain/seed/src/lib/category-companion-entity.builder.js +64 -0
- package/domain/seed/src/lib/category-companion-entity.builder.js.map +1 -1
- package/jql/schema/src/lib/generated/widget-data-types.bootstrap.js +1 -1
- package/jql/schema/src/lib/generated/widget-data-types.bootstrap.js.map +1 -1
- package/package.json +2 -2
- package/realtime/src/lib/auth/auth-security-alert-realtime.listener.d.ts +18 -0
- package/realtime/src/lib/auth/auth-security-alert-realtime.listener.d.ts.map +1 -0
- package/realtime/src/lib/auth/auth-security-alert-realtime.listener.js +90 -0
- package/realtime/src/lib/auth/auth-security-alert-realtime.listener.js.map +1 -0
- package/realtime/src/lib/delivery/socket-io-realtime.delivery.d.ts.map +1 -1
- package/realtime/src/lib/delivery/socket-io-realtime.delivery.js +9 -4
- package/realtime/src/lib/delivery/socket-io-realtime.delivery.js.map +1 -1
- package/realtime/src/lib/gateway/realtime-ws-auth.d.ts +1 -1
- package/realtime/src/lib/gateway/realtime-ws-auth.d.ts.map +1 -1
- package/realtime/src/lib/gateway/realtime-ws-auth.js +4 -2
- package/realtime/src/lib/gateway/realtime-ws-auth.js.map +1 -1
- package/realtime/src/lib/gateway/realtime.gateway.d.ts +1 -0
- package/realtime/src/lib/gateway/realtime.gateway.d.ts.map +1 -1
- package/realtime/src/lib/gateway/realtime.gateway.js +9 -2
- package/realtime/src/lib/gateway/realtime.gateway.js.map +1 -1
- package/realtime/src/lib/realtime.module.d.ts.map +1 -1
- package/realtime/src/lib/realtime.module.js +2 -0
- package/realtime/src/lib/realtime.module.js.map +1 -1
- package/realtime/src/lib/rooms/realtime-rooms.util.d.ts +8 -0
- package/realtime/src/lib/rooms/realtime-rooms.util.d.ts.map +1 -1
- package/realtime/src/lib/rooms/realtime-rooms.util.js +22 -0
- package/realtime/src/lib/rooms/realtime-rooms.util.js.map +1 -1
- package/realtime/src/lib/services/realtime-publisher.service.d.ts.map +1 -1
- package/realtime/src/lib/services/realtime-publisher.service.js +7 -1
- package/realtime/src/lib/services/realtime-publisher.service.js.map +1 -1
- package/realtime/src/lib/topics/realtime-topics.d.ts +1 -0
- package/realtime/src/lib/topics/realtime-topics.d.ts.map +1 -1
- package/realtime/src/lib/topics/realtime-topics.js +2 -1
- package/realtime/src/lib/topics/realtime-topics.js.map +1 -1
- package/realtime/src/lib/types/realtime.types.d.ts +19 -0
- package/realtime/src/lib/types/realtime.types.d.ts.map +1 -1
- package/realtime/src/lib/types/realtime.types.js.map +1 -1
- package/security/src/index.d.ts +4 -0
- package/security/src/index.d.ts.map +1 -1
- package/security/src/index.js +5 -0
- package/security/src/index.js.map +1 -1
- package/security/src/lib/auth/auth-cookie-response.util.d.ts.map +1 -1
- package/security/src/lib/auth/auth-cookie-response.util.js +4 -1
- package/security/src/lib/auth/auth-cookie-response.util.js.map +1 -1
- package/security/src/lib/auth/auth-cookie.service.d.ts +2 -0
- package/security/src/lib/auth/auth-cookie.service.d.ts.map +1 -1
- package/security/src/lib/auth/auth-cookie.service.js +17 -0
- package/security/src/lib/auth/auth-cookie.service.js.map +1 -1
- package/security/src/lib/auth/auth-csrf.middleware.d.ts +14 -0
- package/security/src/lib/auth/auth-csrf.middleware.d.ts.map +1 -0
- package/security/src/lib/auth/auth-csrf.middleware.js +60 -0
- package/security/src/lib/auth/auth-csrf.middleware.js.map +1 -0
- package/security/src/lib/auth/auth-identity-provider.source.d.ts +11 -0
- package/security/src/lib/auth/auth-identity-provider.source.d.ts.map +1 -1
- package/security/src/lib/auth/auth-identity-provider.source.js +6 -1
- package/security/src/lib/auth/auth-identity-provider.source.js.map +1 -1
- package/security/src/lib/auth/auth-local-credentials.service.d.ts +8 -0
- package/security/src/lib/auth/auth-local-credentials.service.d.ts.map +1 -1
- package/security/src/lib/auth/auth-local-credentials.service.js +41 -16
- package/security/src/lib/auth/auth-local-credentials.service.js.map +1 -1
- package/security/src/lib/auth/auth-max-device-sessions-policy.d.ts +14 -0
- package/security/src/lib/auth/auth-max-device-sessions-policy.d.ts.map +1 -0
- package/security/src/lib/auth/auth-max-device-sessions-policy.js +48 -0
- package/security/src/lib/auth/auth-max-device-sessions-policy.js.map +1 -0
- package/security/src/lib/auth/auth-runtime.handlers.d.ts +15 -3
- package/security/src/lib/auth/auth-runtime.handlers.d.ts.map +1 -1
- package/security/src/lib/auth/auth-runtime.handlers.js +51 -3
- package/security/src/lib/auth/auth-runtime.handlers.js.map +1 -1
- package/security/src/lib/auth/auth.config.d.ts +7 -0
- package/security/src/lib/auth/auth.config.d.ts.map +1 -1
- package/security/src/lib/auth/auth.config.js +8 -1
- package/security/src/lib/auth/auth.config.js.map +1 -1
- package/security/src/lib/auth/auth.events.d.ts +22 -0
- package/security/src/lib/auth/auth.events.d.ts.map +1 -1
- package/security/src/lib/auth/auth.events.js +3 -1
- package/security/src/lib/auth/auth.events.js.map +1 -1
- package/security/src/lib/auth/auth.service.d.ts +65 -5
- package/security/src/lib/auth/auth.service.d.ts.map +1 -1
- package/security/src/lib/auth/auth.service.js +311 -10
- package/security/src/lib/auth/auth.service.js.map +1 -1
- package/security/src/lib/auth/auth.types.d.ts +36 -0
- package/security/src/lib/auth/auth.types.d.ts.map +1 -1
- package/security/src/lib/auth/auth.types.js.map +1 -1
- package/security/src/lib/auth/device-session.service.d.ts +59 -1
- package/security/src/lib/auth/device-session.service.d.ts.map +1 -1
- package/security/src/lib/auth/device-session.service.js +299 -28
- package/security/src/lib/auth/device-session.service.js.map +1 -1
- package/security/src/lib/auth/jwt-auth.guard.d.ts +2 -1
- package/security/src/lib/auth/jwt-auth.guard.d.ts.map +1 -1
- package/security/src/lib/auth/jwt-auth.guard.js +4 -2
- package/security/src/lib/auth/jwt-auth.guard.js.map +1 -1
- package/security/src/lib/auth/login-attempt.service.d.ts +14 -3
- package/security/src/lib/auth/login-attempt.service.d.ts.map +1 -1
- package/security/src/lib/auth/login-attempt.service.js +35 -10
- package/security/src/lib/auth/login-attempt.service.js.map +1 -1
- package/security/src/lib/auth/request-meta.util.js +64 -11
- package/security/src/lib/auth/request-meta.util.js.map +1 -1
- package/security/src/lib/auth/token-revocation.constants.d.ts +9 -0
- package/security/src/lib/auth/token-revocation.constants.d.ts.map +1 -0
- package/security/src/lib/auth/token-revocation.constants.js +14 -0
- package/security/src/lib/auth/token-revocation.constants.js.map +1 -0
- package/security/src/lib/auth/token.service.d.ts +26 -1
- package/security/src/lib/auth/token.service.d.ts.map +1 -1
- package/security/src/lib/auth/token.service.js +105 -4
- package/security/src/lib/auth/token.service.js.map +1 -1
- package/security/src/lib/oauth/connect.controller.d.ts +81 -7
- package/security/src/lib/oauth/connect.controller.d.ts.map +1 -1
- package/security/src/lib/oauth/connect.controller.js +264 -36
- package/security/src/lib/oauth/connect.controller.js.map +1 -1
- package/security/src/lib/oauth/oauth-bff.util.d.ts +9 -0
- package/security/src/lib/oauth/oauth-bff.util.d.ts.map +1 -0
- package/security/src/lib/oauth/oauth-bff.util.js +30 -0
- package/security/src/lib/oauth/oauth-bff.util.js.map +1 -0
- package/security/src/lib/oauth/oauth-claims.util.d.ts +6 -0
- package/security/src/lib/oauth/oauth-claims.util.d.ts.map +1 -0
- package/security/src/lib/oauth/oauth-claims.util.js +36 -0
- package/security/src/lib/oauth/oauth-claims.util.js.map +1 -0
- package/security/src/lib/oauth/oauth-client-registry.service.d.ts +25 -0
- package/security/src/lib/oauth/oauth-client-registry.service.d.ts.map +1 -0
- package/security/src/lib/oauth/oauth-client-registry.service.js +201 -0
- package/security/src/lib/oauth/oauth-client-registry.service.js.map +1 -0
- package/security/src/lib/oauth/oauth-connect.exception-filter.d.ts +10 -0
- package/security/src/lib/oauth/oauth-connect.exception-filter.d.ts.map +1 -0
- package/security/src/lib/oauth/oauth-connect.exception-filter.js +59 -0
- package/security/src/lib/oauth/oauth-connect.exception-filter.js.map +1 -0
- package/security/src/lib/oauth/oauth-error.util.d.ts +23 -0
- package/security/src/lib/oauth/oauth-error.util.d.ts.map +1 -0
- package/security/src/lib/oauth/oauth-error.util.js +114 -0
- package/security/src/lib/oauth/oauth-error.util.js.map +1 -0
- package/security/src/lib/oauth/oauth-grant-store.interface.d.ts +16 -0
- package/security/src/lib/oauth/oauth-grant-store.interface.d.ts.map +1 -0
- package/security/src/lib/oauth/oauth-grant-store.interface.js +14 -0
- package/security/src/lib/oauth/oauth-grant-store.interface.js.map +1 -0
- package/security/src/lib/oauth/oauth-issuer.util.d.ts +7 -0
- package/security/src/lib/oauth/oauth-issuer.util.d.ts.map +1 -0
- package/security/src/lib/oauth/oauth-issuer.util.js +25 -0
- package/security/src/lib/oauth/oauth-issuer.util.js.map +1 -0
- package/security/src/lib/oauth/oauth-op.service.d.ts +35 -0
- package/security/src/lib/oauth/oauth-op.service.d.ts.map +1 -0
- package/security/src/lib/oauth/oauth-op.service.js +152 -0
- package/security/src/lib/oauth/oauth-op.service.js.map +1 -0
- package/security/src/lib/oauth/oauth-op.types.d.ts +73 -0
- package/security/src/lib/oauth/oauth-op.types.d.ts.map +1 -0
- package/security/src/lib/oauth/oauth-op.types.js +5 -0
- package/security/src/lib/oauth/oauth-op.types.js.map +1 -0
- package/security/src/lib/oauth/oauth-provider-hint.util.d.ts +8 -0
- package/security/src/lib/oauth/oauth-provider-hint.util.d.ts.map +1 -0
- package/security/src/lib/oauth/oauth-provider-hint.util.js +24 -0
- package/security/src/lib/oauth/oauth-provider-hint.util.js.map +1 -0
- package/security/src/lib/oauth/oauth-redis-oidc-adapter.d.ts +27 -0
- package/security/src/lib/oauth/oauth-redis-oidc-adapter.d.ts.map +1 -0
- package/security/src/lib/oauth/oauth-redis-oidc-adapter.js +159 -0
- package/security/src/lib/oauth/oauth-redis-oidc-adapter.js.map +1 -0
- package/security/src/lib/oauth/oauth-rp.service.d.ts +53 -0
- package/security/src/lib/oauth/oauth-rp.service.d.ts.map +1 -0
- package/security/src/lib/oauth/oauth-rp.service.js +371 -0
- package/security/src/lib/oauth/oauth-rp.service.js.map +1 -0
- package/security/src/lib/oauth/oauth-rp.types.d.ts +25 -0
- package/security/src/lib/oauth/oauth-rp.types.d.ts.map +1 -0
- package/security/src/lib/oauth/oauth-rp.types.js +5 -0
- package/security/src/lib/oauth/oauth-rp.types.js.map +1 -0
- package/security/src/lib/oauth/oidc-provider-engine.service.d.ts +25 -0
- package/security/src/lib/oauth/oidc-provider-engine.service.d.ts.map +1 -0
- package/security/src/lib/oauth/oidc-provider-engine.service.js +208 -0
- package/security/src/lib/oauth/oidc-provider-engine.service.js.map +1 -0
- package/security/src/lib/oauth/platform-oauth.service.d.ts +43 -14
- package/security/src/lib/oauth/platform-oauth.service.d.ts.map +1 -1
- package/security/src/lib/oauth/platform-oauth.service.js +177 -170
- package/security/src/lib/oauth/platform-oauth.service.js.map +1 -1
- package/security/src/lib/oauth/platform-oauth.types.d.ts +11 -3
- package/security/src/lib/oauth/platform-oauth.types.d.ts.map +1 -1
- package/security/src/lib/oauth/platform-oauth.types.js.map +1 -1
- package/security/src/lib/oauth/redis-oauth-grant-store.service.d.ts +22 -0
- package/security/src/lib/oauth/redis-oauth-grant-store.service.d.ts.map +1 -0
- package/security/src/lib/oauth/redis-oauth-grant-store.service.js +132 -0
- package/security/src/lib/oauth/redis-oauth-grant-store.service.js.map +1 -0
- package/security/src/lib/oauth/testing/in-memory-oauth-grant-store.d.ts +14 -0
- package/security/src/lib/oauth/testing/in-memory-oauth-grant-store.d.ts.map +1 -0
- package/security/src/lib/oauth/testing/in-memory-oauth-grant-store.js +46 -0
- package/security/src/lib/oauth/testing/in-memory-oauth-grant-store.js.map +1 -0
- package/security/src/lib/oauth/testing/jest-empty-module.d.ts +4 -0
- package/security/src/lib/oauth/testing/jest-empty-module.d.ts.map +1 -0
- package/security/src/lib/oauth/testing/jest-empty-module.js +11 -0
- package/security/src/lib/oauth/testing/jest-empty-module.js.map +1 -0
- package/security/src/lib/oauth/testing/mock-oidc-provider.d.ts +49 -0
- package/security/src/lib/oauth/testing/mock-oidc-provider.d.ts.map +1 -0
- package/security/src/lib/oauth/testing/mock-oidc-provider.js +158 -0
- package/security/src/lib/oauth/testing/mock-oidc-provider.js.map +1 -0
- package/security/src/lib/security.module.d.ts.map +1 -1
- package/security/src/lib/security.module.js +31 -0
- package/security/src/lib/security.module.js.map +1 -1
- package/security/src/lib/session/backend-session.types.d.ts +2 -0
- package/security/src/lib/session/backend-session.types.d.ts.map +1 -1
- package/security/src/lib/session/backend-session.types.js.map +1 -1
- package/security/src/lib/session/optional-jwt-session.middleware.d.ts +6 -4
- package/security/src/lib/session/optional-jwt-session.middleware.d.ts.map +1 -1
- package/security/src/lib/session/optional-jwt-session.middleware.js +18 -7
- package/security/src/lib/session/optional-jwt-session.middleware.js.map +1 -1
|
@@ -4,4 +4,26 @@ export interface AuthLogoutEventPayload {
|
|
|
4
4
|
tenantId?: string;
|
|
5
5
|
at: string;
|
|
6
6
|
}
|
|
7
|
+
/** Cross-session security signal (login success/failure) → realtime fan-out. */
|
|
8
|
+
export declare const AUTH_SECURITY_ALERT_EVENT = "auth.security-alert";
|
|
9
|
+
export type AuthSecurityAlertKind = 'login_success' | 'login_failed' | 'account_locked' | 'logout'
|
|
10
|
+
/** Remote DeviceSessions terminate — force the target browser to leave after OK. */
|
|
11
|
+
| 'session_terminated';
|
|
12
|
+
export interface AuthSecurityAlertEventPayload {
|
|
13
|
+
kind: AuthSecurityAlertKind;
|
|
14
|
+
userId: string;
|
|
15
|
+
at: string;
|
|
16
|
+
/** Login `sid` for this attempt (success only) — other sessions should toast. */
|
|
17
|
+
actorSessionId?: string;
|
|
18
|
+
/**
|
|
19
|
+
* JWT `sid` of the session that was revoked.
|
|
20
|
+
* For `session_terminated`, realtime delivers only to that session’s socket room.
|
|
21
|
+
*/
|
|
22
|
+
targetSessionId?: string;
|
|
23
|
+
deviceSummary?: string;
|
|
24
|
+
ipAddress?: string;
|
|
25
|
+
browser?: string;
|
|
26
|
+
deviceName?: string;
|
|
27
|
+
operatingSystem?: string;
|
|
28
|
+
}
|
|
7
29
|
//# sourceMappingURL=auth.events.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.events.d.ts","sourceRoot":"","sources":["../../../../../../../../libs/framework/server/security/src/lib/auth/auth.events.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,gBAAgB,CAAC;AAE/C,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ"}
|
|
1
|
+
{"version":3,"file":"auth.events.d.ts","sourceRoot":"","sources":["../../../../../../../../libs/framework/server/security/src/lib/auth/auth.events.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,iBAAiB,gBAAgB,CAAC;AAE/C,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,gFAAgF;AAChF,eAAO,MAAM,yBAAyB,wBAAwB,CAAC;AAE/D,MAAM,MAAM,qBAAqB,GAC7B,eAAe,GACf,cAAc,GACd,gBAAgB,GAChB,QAAQ;AACV,oFAAoF;GAClF,oBAAoB,CAAC;AAEzB,MAAM,WAAW,6BAA6B;IAC5C,IAAI,EAAE,qBAAqB,CAAC;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,iFAAiF;IACjF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B"}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
//#region ----- Auth lifecycle events -----
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.AUTH_LOGOUT_EVENT = void 0;
|
|
4
|
+
exports.AUTH_SECURITY_ALERT_EVENT = exports.AUTH_LOGOUT_EVENT = void 0;
|
|
5
5
|
exports.AUTH_LOGOUT_EVENT = 'auth.logout';
|
|
6
|
+
/** Cross-session security signal (login success/failure) → realtime fan-out. */
|
|
7
|
+
exports.AUTH_SECURITY_ALERT_EVENT = 'auth.security-alert';
|
|
6
8
|
//#endregion
|
|
7
9
|
//# sourceMappingURL=auth.events.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.events.js","sourceRoot":"","sources":["../../../../../../../../libs/framework/server/security/src/lib/auth/auth.events.ts"],"names":[],"mappings":";AAAA,2CAA2C;;;AAE9B,QAAA,iBAAiB,GAAG,aAAa,CAAC;AAQ/C,YAAY"}
|
|
1
|
+
{"version":3,"file":"auth.events.js","sourceRoot":"","sources":["../../../../../../../../libs/framework/server/security/src/lib/auth/auth.events.ts"],"names":[],"mappings":";AAAA,2CAA2C;;;AAE9B,QAAA,iBAAiB,GAAG,aAAa,CAAC;AAQ/C,gFAAgF;AACnE,QAAA,yBAAyB,GAAG,qBAAqB,CAAC;AA4B/D,YAAY"}
|
|
@@ -1,34 +1,83 @@
|
|
|
1
|
+
import { OnModuleDestroy } from '@nestjs/common';
|
|
1
2
|
import { EntityStorageMiddlewareService } from '@acorex-platform/framework-server/core';
|
|
2
3
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
|
3
4
|
import { AuthLocalCredentialsService } from './auth-local-credentials.service';
|
|
4
5
|
import { DeviceSessionService } from './device-session.service';
|
|
5
6
|
import type { AuthRequestMeta } from './request-meta.util';
|
|
6
7
|
import { TokenService } from './token.service';
|
|
7
|
-
import type { AuthTokenResponseDto, RegisterRequestDto, SecurityManagementUserRecord, UpdateSessionRequestDto } from './auth.types';
|
|
8
|
+
import type { AccessTokenPayload, AuthOwnDeviceSessionDto, AuthTokenResponseDto, RegisterRequestDto, SecurityManagementUserRecord, UpdateSessionRequestDto } from './auth.types';
|
|
8
9
|
/** Stable client i18n mapping key — not shown raw in UI. */
|
|
9
10
|
export declare const SESSION_MEMBERSHIP_DENIED_CODE = "SESSION_MEMBERSHIP_DENIED";
|
|
10
|
-
|
|
11
|
+
/** Stable client i18n mapping key — tenant max active device sessions exceeded. */
|
|
12
|
+
export declare const MAX_ACTIVE_DEVICE_SESSIONS_CODE = "MAX_ACTIVE_DEVICE_SESSIONS";
|
|
13
|
+
export declare class AuthService implements OnModuleDestroy {
|
|
11
14
|
private readonly entityStorage;
|
|
12
15
|
private readonly tokenService;
|
|
13
16
|
private readonly localCredentials;
|
|
14
17
|
private readonly deviceSessions;
|
|
15
18
|
private readonly events?;
|
|
16
19
|
private readonly logger;
|
|
20
|
+
private readonly refreshReplayRedis;
|
|
17
21
|
constructor(entityStorage: EntityStorageMiddlewareService, tokenService: TokenService, localCredentials: AuthLocalCredentialsService, deviceSessions: DeviceSessionService, events?: EventEmitter2 | undefined);
|
|
18
|
-
|
|
22
|
+
onModuleDestroy(): Promise<void>;
|
|
23
|
+
completeSignIn(user: SecurityManagementUserRecord, meta: AuthRequestMeta, options?: {
|
|
24
|
+
audience?: string;
|
|
25
|
+
persist?: boolean;
|
|
26
|
+
}): Promise<AuthTokenResponseDto>;
|
|
19
27
|
assertSupportedSignInStrategy(strategy?: string): void;
|
|
20
28
|
register(dto: RegisterRequestDto, meta: AuthRequestMeta): Promise<AuthTokenResponseDto>;
|
|
21
|
-
refreshSession(refreshToken: string, meta: AuthRequestMeta
|
|
29
|
+
refreshSession(refreshToken: string, meta: AuthRequestMeta, options?: {
|
|
30
|
+
audience?: string;
|
|
31
|
+
}): Promise<AuthTokenResponseDto>;
|
|
22
32
|
logout(refreshToken?: string | null): Promise<{
|
|
23
33
|
success: true;
|
|
24
34
|
}>;
|
|
25
35
|
updateSession(currentAccessToken: string, dto: UpdateSessionRequestDto, meta: AuthRequestMeta): Promise<AuthTokenResponseDto>;
|
|
26
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Cryptographic verify only (signature / exp / type / aud).
|
|
38
|
+
* Prefer {@link validateAccessToken} for request auth (OD-09 sid/jti checks).
|
|
39
|
+
*/
|
|
40
|
+
verifyAccessToken(token: string, expectedAudience?: string): AccessTokenPayload;
|
|
41
|
+
/**
|
|
42
|
+
* OD-09 access-token validation: signature → expiration → `sid` → `jti`.
|
|
43
|
+
*/
|
|
44
|
+
validateAccessToken(token: string, expectedAudience?: string): Promise<AccessTokenPayload>;
|
|
27
45
|
verifyRefreshToken(token: string): import("./auth.types").RefreshTokenPayload;
|
|
28
46
|
accessTokenMaxAgeMs(): number;
|
|
29
47
|
refreshTokenMaxAgeMs(): number;
|
|
48
|
+
revokeSessionById(sessionId: string): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Projects DeviceSessions revoke (SoT) onto Redis for the JWT `sid` family.
|
|
51
|
+
* Request paths check Redis only — this stamp is required for terminate to take effect.
|
|
52
|
+
*/
|
|
53
|
+
invalidateLoginSession(sessionId: string): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Realtime kick for a remote DeviceSessions terminate (Active Sessions UI, etc.).
|
|
56
|
+
* Does not change Redis revoke — call after {@link invalidateLoginSession}.
|
|
57
|
+
*/
|
|
58
|
+
notifyRemoteSessionTerminated(userId: string, sessionId: string): void;
|
|
59
|
+
decodeIdTokenHint(token: string): {
|
|
60
|
+
sub?: string;
|
|
61
|
+
sid?: string;
|
|
62
|
+
} | null;
|
|
30
63
|
private completeAuthentication;
|
|
64
|
+
/**
|
|
65
|
+
* Tenant setting MaxActiveDeviceSessions — per user + tenant.
|
|
66
|
+
* Enforced when a real tenant is known (tenant chooser bind / auto-bind, or create with tid).
|
|
67
|
+
* Same device upsert (create) and re-bind of the current session to the same tenant are allowed.
|
|
68
|
+
*/
|
|
69
|
+
private assertMaxActiveDeviceSessionsAllowed;
|
|
70
|
+
/**
|
|
71
|
+
* Active DeviceSessions for the authenticated user in a tenant (session-limit chooser).
|
|
72
|
+
*/
|
|
73
|
+
listOwnActiveDeviceSessions(accessToken: string, tenantId: string): Promise<AuthOwnDeviceSessionDto[]>;
|
|
74
|
+
/**
|
|
75
|
+
* Terminate one of the caller's DeviceSessions (by entity id) during session-limit recovery.
|
|
76
|
+
*/
|
|
77
|
+
terminateOwnDeviceSession(accessToken: string, deviceSessionId: string): Promise<void>;
|
|
31
78
|
private emitLogoutEvent;
|
|
79
|
+
private emitSecurityAlert;
|
|
80
|
+
private securityAlertDeviceFields;
|
|
32
81
|
private toAuthUser;
|
|
33
82
|
private normalizeAvatar;
|
|
34
83
|
/**
|
|
@@ -39,5 +88,16 @@ export declare class AuthService {
|
|
|
39
88
|
private assertActiveMembership;
|
|
40
89
|
private resolveTenantDto;
|
|
41
90
|
private resolveApplicationDto;
|
|
91
|
+
private assertRefreshNotRevoked;
|
|
92
|
+
private markRefreshRevoked;
|
|
93
|
+
private markSessionRevoked;
|
|
94
|
+
/** Invalidates prior login families when the same device row is reused (per-device upsert). */
|
|
95
|
+
private revokeReplacedDeviceSessions;
|
|
96
|
+
/**
|
|
97
|
+
* OD-09 hot path: Redis projection only. DeviceSessions is SoT on write;
|
|
98
|
+
* do not query Mongo here (see device-session-revocation.mdc).
|
|
99
|
+
*/
|
|
100
|
+
private assertSessionAllowed;
|
|
101
|
+
private assertAccessJtiNotRevoked;
|
|
42
102
|
}
|
|
43
103
|
//# sourceMappingURL=auth.service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../../../../../../../libs/framework/server/security/src/lib/auth/auth.service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.service.d.ts","sourceRoot":"","sources":["../../../../../../../../libs/framework/server/security/src/lib/auth/auth.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var AuthService_1;
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.AuthService = exports.SESSION_MEMBERSHIP_DENIED_CODE = void 0;
|
|
4
|
+
exports.AuthService = exports.MAX_ACTIVE_DEVICE_SESSIONS_CODE = exports.SESSION_MEMBERSHIP_DENIED_CODE = void 0;
|
|
5
5
|
const tslib_1 = require("tslib");
|
|
6
6
|
const common_1 = require("@nestjs/common");
|
|
7
7
|
const core_1 = require("@acorex-platform/framework-server/core");
|
|
@@ -9,12 +9,16 @@ const abstract_1 = require("@acorex-platform/framework-server/jql/abstract");
|
|
|
9
9
|
const core_2 = require("@acorex-platform/framework-shared/core");
|
|
10
10
|
const lodash_es_1 = require("lodash-es");
|
|
11
11
|
const crypto_1 = require("crypto");
|
|
12
|
+
const ioredis_1 = tslib_1.__importDefault(require("ioredis"));
|
|
12
13
|
const event_emitter_1 = require("@nestjs/event-emitter");
|
|
13
14
|
const auth_config_1 = require("./auth.config");
|
|
14
15
|
const auth_events_1 = require("./auth.events");
|
|
15
16
|
const auth_local_credentials_service_1 = require("./auth-local-credentials.service");
|
|
17
|
+
const auth_max_device_sessions_policy_1 = require("./auth-max-device-sessions-policy");
|
|
16
18
|
const device_session_service_1 = require("./device-session.service");
|
|
19
|
+
const request_meta_util_1 = require("./request-meta.util");
|
|
17
20
|
const token_service_1 = require("./token.service");
|
|
21
|
+
const token_revocation_constants_1 = require("./token-revocation.constants");
|
|
18
22
|
//#region ---- Auth service ----
|
|
19
23
|
const USER_ENTITY = 'SecurityManagement.User';
|
|
20
24
|
const TENANT_ENTITY = 'TenantManagement.Tenant';
|
|
@@ -24,6 +28,26 @@ const ALLOWED_ACCESS_STATE = 'allowed';
|
|
|
24
28
|
const ACTIVE_MEMBERSHIP_STATUS = 'active';
|
|
25
29
|
/** Stable client i18n mapping key — not shown raw in UI. */
|
|
26
30
|
exports.SESSION_MEMBERSHIP_DENIED_CODE = 'SESSION_MEMBERSHIP_DENIED';
|
|
31
|
+
/** Stable client i18n mapping key — tenant max active device sessions exceeded. */
|
|
32
|
+
exports.MAX_ACTIVE_DEVICE_SESSIONS_CODE = 'MAX_ACTIVE_DEVICE_SESSIONS';
|
|
33
|
+
function resolveRefreshReplayRedisConnection() {
|
|
34
|
+
const url = process.env['REDIS_URL']?.trim();
|
|
35
|
+
if (url) {
|
|
36
|
+
return { url };
|
|
37
|
+
}
|
|
38
|
+
const host = process.env['REDIS_HOST']?.trim();
|
|
39
|
+
const portRaw = Number(process.env['REDIS_PORT'] ?? 6379);
|
|
40
|
+
if (host) {
|
|
41
|
+
return { host, port: Number.isFinite(portRaw) ? portRaw : 6379 };
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function createRefreshReplayRedisClient(connection) {
|
|
46
|
+
if ('url' in connection) {
|
|
47
|
+
return new ioredis_1.default(connection.url, { maxRetriesPerRequest: 2, lazyConnect: true });
|
|
48
|
+
}
|
|
49
|
+
return new ioredis_1.default({ ...connection, maxRetriesPerRequest: 2, lazyConnect: true });
|
|
50
|
+
}
|
|
27
51
|
let AuthService = AuthService_1 = class AuthService {
|
|
28
52
|
constructor(entityStorage, tokenService, localCredentials, deviceSessions, events) {
|
|
29
53
|
this.entityStorage = entityStorage;
|
|
@@ -32,9 +56,17 @@ let AuthService = AuthService_1 = class AuthService {
|
|
|
32
56
|
this.deviceSessions = deviceSessions;
|
|
33
57
|
this.events = events;
|
|
34
58
|
this.logger = new common_1.Logger(AuthService_1.name);
|
|
59
|
+
const connection = resolveRefreshReplayRedisConnection();
|
|
60
|
+
this.refreshReplayRedis = connection ? createRefreshReplayRedisClient(connection) : null;
|
|
35
61
|
}
|
|
36
|
-
async
|
|
37
|
-
|
|
62
|
+
async onModuleDestroy() {
|
|
63
|
+
await this.refreshReplayRedis?.quit().catch(() => this.refreshReplayRedis?.disconnect());
|
|
64
|
+
}
|
|
65
|
+
async completeSignIn(user, meta, options) {
|
|
66
|
+
return this.completeAuthentication(this.toAuthUser(user), meta, {
|
|
67
|
+
audience: options?.audience,
|
|
68
|
+
...(options?.persist !== undefined ? { persist: options.persist } : {}),
|
|
69
|
+
});
|
|
38
70
|
}
|
|
39
71
|
assertSupportedSignInStrategy(strategy) {
|
|
40
72
|
const resolved = strategy ?? auth_config_1.AUTH_STRATEGY_USER_PASS;
|
|
@@ -69,13 +101,35 @@ let AuthService = AuthService_1 = class AuthService {
|
|
|
69
101
|
});
|
|
70
102
|
return this.completeAuthentication({ id, name: username, title: displayName, avatar: null }, meta, {});
|
|
71
103
|
}
|
|
72
|
-
async refreshSession(refreshToken, meta) {
|
|
104
|
+
async refreshSession(refreshToken, meta, options) {
|
|
73
105
|
const payload = this.verifyRefreshToken(refreshToken);
|
|
106
|
+
await this.assertRefreshNotRevoked(payload.jti);
|
|
107
|
+
// Refresh-token reuse detection:
|
|
108
|
+
// If the same refresh token `jti` is replayed, it has already been consumed once.
|
|
109
|
+
// In that case revoke the whole refresh chain (same `sid`) and reject.
|
|
110
|
+
const reuseKey = `${token_revocation_constants_1.OAUTH_REFRESH_REPLAY_KEY_PREFIX}${payload.jti}`;
|
|
111
|
+
const ttlMs = this.refreshTokenMaxAgeMs();
|
|
112
|
+
if (this.refreshReplayRedis) {
|
|
113
|
+
const firstUse = await this.refreshReplayRedis.set(reuseKey, '1', 'PX', ttlMs, 'NX');
|
|
114
|
+
if (firstUse === null) {
|
|
115
|
+
// DB SoT first, then Redis projection (middleware + explicit stamp).
|
|
116
|
+
await this.deviceSessions.revokeBySessionId(payload.sid);
|
|
117
|
+
await this.deviceSessions.revokeByAuthorizationId(payload.jti);
|
|
118
|
+
await this.markSessionRevoked(payload.sid, { required: true });
|
|
119
|
+
throw new common_1.UnauthorizedException('Refresh token reuse detected.');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
74
122
|
const user = await this.localCredentials.findUserById(payload.sub);
|
|
75
123
|
if (!user) {
|
|
76
124
|
throw new common_1.UnauthorizedException('User not found');
|
|
77
125
|
}
|
|
78
|
-
await this.
|
|
126
|
+
await this.assertSessionAllowed(payload.sid);
|
|
127
|
+
// Do NOT revoke the DeviceSessions row on refresh rotation.
|
|
128
|
+
// revokeByAuthorizationId → status:revoked projects Redis session-revoked:{sid}
|
|
129
|
+
// (device-sessions-token-revoke middleware). Tokens keep the same sid, so the next
|
|
130
|
+
// request would 401 ("logged out after refresh"). Old refresh jti is consumed via
|
|
131
|
+
// the replay key above; real terminates (logout / Active Sessions / reuse) still
|
|
132
|
+
// revoke the row + sid. update-binding below only rotates authorizationId (new jti).
|
|
79
133
|
await this.assertActiveMembership(user.id, payload.tid, payload.aid);
|
|
80
134
|
const [tenant, application] = await Promise.all([
|
|
81
135
|
this.resolveTenantDto(payload.tid),
|
|
@@ -87,6 +141,9 @@ let AuthService = AuthService_1 = class AuthService {
|
|
|
87
141
|
tenant,
|
|
88
142
|
application,
|
|
89
143
|
sessionId: payload.sid,
|
|
144
|
+
audience: options?.audience,
|
|
145
|
+
// Legacy refresh JWTs omit `persist` → keep persistent cookies (prior default).
|
|
146
|
+
persist: payload.persist !== false,
|
|
90
147
|
}, 'update-binding');
|
|
91
148
|
}
|
|
92
149
|
async logout(refreshToken) {
|
|
@@ -95,12 +152,22 @@ let AuthService = AuthService_1 = class AuthService {
|
|
|
95
152
|
}
|
|
96
153
|
try {
|
|
97
154
|
const payload = this.verifyRefreshToken(refreshToken);
|
|
155
|
+
await this.markRefreshRevoked(payload.jti);
|
|
156
|
+
// DeviceSessions SoT, then Redis sid projection.
|
|
98
157
|
await this.deviceSessions.revokeByAuthorizationId(payload.jti);
|
|
158
|
+
await this.deviceSessions.revokeBySessionId(payload.sid);
|
|
159
|
+
await this.markSessionRevoked(payload.sid, { required: true });
|
|
99
160
|
this.emitLogoutEvent({
|
|
100
161
|
userId: payload.sub,
|
|
101
162
|
tenantId: payload.tid,
|
|
102
163
|
at: new Date().toISOString(),
|
|
103
164
|
});
|
|
165
|
+
this.emitSecurityAlert({
|
|
166
|
+
kind: 'logout',
|
|
167
|
+
userId: payload.sub,
|
|
168
|
+
at: new Date().toISOString(),
|
|
169
|
+
actorSessionId: payload.sid,
|
|
170
|
+
});
|
|
104
171
|
}
|
|
105
172
|
catch (error) {
|
|
106
173
|
this.logger.debug(`Logout ignored invalid refresh token: ${String(error)}`);
|
|
@@ -108,7 +175,7 @@ let AuthService = AuthService_1 = class AuthService {
|
|
|
108
175
|
return { success: true };
|
|
109
176
|
}
|
|
110
177
|
async updateSession(currentAccessToken, dto, meta) {
|
|
111
|
-
const current = this.
|
|
178
|
+
const current = await this.validateAccessToken(currentAccessToken);
|
|
112
179
|
const user = await this.localCredentials.findUserById(current.sub);
|
|
113
180
|
if (!user) {
|
|
114
181
|
throw new common_1.UnauthorizedException('User not found');
|
|
@@ -140,14 +207,27 @@ let AuthService = AuthService_1 = class AuthService {
|
|
|
140
207
|
sessionId: current.sid,
|
|
141
208
|
}, 'update-binding');
|
|
142
209
|
}
|
|
143
|
-
|
|
210
|
+
/**
|
|
211
|
+
* Cryptographic verify only (signature / exp / type / aud).
|
|
212
|
+
* Prefer {@link validateAccessToken} for request auth (OD-09 sid/jti checks).
|
|
213
|
+
*/
|
|
214
|
+
verifyAccessToken(token, expectedAudience) {
|
|
144
215
|
try {
|
|
145
|
-
return this.tokenService.verifyAccessToken(token);
|
|
216
|
+
return this.tokenService.verifyAccessToken(token, expectedAudience);
|
|
146
217
|
}
|
|
147
218
|
catch {
|
|
148
219
|
throw new common_1.UnauthorizedException('Invalid or expired access token');
|
|
149
220
|
}
|
|
150
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* OD-09 access-token validation: signature → expiration → `sid` → `jti`.
|
|
224
|
+
*/
|
|
225
|
+
async validateAccessToken(token, expectedAudience) {
|
|
226
|
+
const payload = this.verifyAccessToken(token, expectedAudience);
|
|
227
|
+
await this.assertSessionAllowed(payload.sid);
|
|
228
|
+
await this.assertAccessJtiNotRevoked(payload.jti);
|
|
229
|
+
return payload;
|
|
230
|
+
}
|
|
151
231
|
verifyRefreshToken(token) {
|
|
152
232
|
try {
|
|
153
233
|
return this.tokenService.verifyRefreshToken(token);
|
|
@@ -162,7 +242,50 @@ let AuthService = AuthService_1 = class AuthService {
|
|
|
162
242
|
refreshTokenMaxAgeMs() {
|
|
163
243
|
return this.tokenService.refreshTokenMaxAgeMs();
|
|
164
244
|
}
|
|
245
|
+
async revokeSessionById(sessionId) {
|
|
246
|
+
// DB SoT first; entity middleware projects Redis. Explicit stamp covers missing rows.
|
|
247
|
+
await this.deviceSessions.revokeBySessionId(sessionId);
|
|
248
|
+
await this.markSessionRevoked(sessionId, { required: true });
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Projects DeviceSessions revoke (SoT) onto Redis for the JWT `sid` family.
|
|
252
|
+
* Request paths check Redis only — this stamp is required for terminate to take effect.
|
|
253
|
+
*/
|
|
254
|
+
async invalidateLoginSession(sessionId) {
|
|
255
|
+
await this.markSessionRevoked(sessionId, { required: true });
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Realtime kick for a remote DeviceSessions terminate (Active Sessions UI, etc.).
|
|
259
|
+
* Does not change Redis revoke — call after {@link invalidateLoginSession}.
|
|
260
|
+
*/
|
|
261
|
+
notifyRemoteSessionTerminated(userId, sessionId) {
|
|
262
|
+
const uid = userId.trim();
|
|
263
|
+
const sid = sessionId.trim();
|
|
264
|
+
if (!uid || !sid) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
this.emitSecurityAlert({
|
|
268
|
+
kind: 'session_terminated',
|
|
269
|
+
userId: uid,
|
|
270
|
+
at: new Date().toISOString(),
|
|
271
|
+
targetSessionId: sid,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
decodeIdTokenHint(token) {
|
|
275
|
+
return this.tokenService.decodeIdTokenHint(token);
|
|
276
|
+
}
|
|
165
277
|
async completeAuthentication(user, meta, context, deviceSessionMode = 'create') {
|
|
278
|
+
if (deviceSessionMode === 'create') {
|
|
279
|
+
await this.assertMaxActiveDeviceSessionsAllowed(user.id, meta, context.tenantId, {
|
|
280
|
+
mode: 'create',
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
await this.assertMaxActiveDeviceSessionsAllowed(user.id, meta, context.tenantId, {
|
|
285
|
+
mode: 'update-binding',
|
|
286
|
+
sessionId: context.sessionId,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
166
289
|
const tokens = this.tokenService.issueTokenResponse(user, context);
|
|
167
290
|
const refreshPayload = this.tokenService.verifyRefreshToken(tokens.refreshToken);
|
|
168
291
|
const sessionId = tokens.sessionId ?? refreshPayload.sid;
|
|
@@ -174,7 +297,7 @@ let AuthService = AuthService_1 = class AuthService {
|
|
|
174
297
|
applicationId: context.applicationId,
|
|
175
298
|
});
|
|
176
299
|
if (!updated) {
|
|
177
|
-
await this.deviceSessions.createSession({
|
|
300
|
+
const created = await this.deviceSessions.createSession({
|
|
178
301
|
user,
|
|
179
302
|
authorizationId: refreshPayload.jti,
|
|
180
303
|
sessionId,
|
|
@@ -182,10 +305,11 @@ let AuthService = AuthService_1 = class AuthService {
|
|
|
182
305
|
tenantId: context.tenantId,
|
|
183
306
|
applicationId: context.applicationId,
|
|
184
307
|
});
|
|
308
|
+
await this.revokeReplacedDeviceSessions(created.replacedSessionIds);
|
|
185
309
|
}
|
|
186
310
|
}
|
|
187
311
|
else {
|
|
188
|
-
await this.deviceSessions.createSession({
|
|
312
|
+
const created = await this.deviceSessions.createSession({
|
|
189
313
|
user,
|
|
190
314
|
authorizationId: refreshPayload.jti,
|
|
191
315
|
sessionId,
|
|
@@ -193,6 +317,14 @@ let AuthService = AuthService_1 = class AuthService {
|
|
|
193
317
|
tenantId: context.tenantId,
|
|
194
318
|
applicationId: context.applicationId,
|
|
195
319
|
});
|
|
320
|
+
await this.revokeReplacedDeviceSessions(created.replacedSessionIds);
|
|
321
|
+
this.emitSecurityAlert({
|
|
322
|
+
kind: 'login_success',
|
|
323
|
+
userId: user.id,
|
|
324
|
+
at: new Date().toISOString(),
|
|
325
|
+
actorSessionId: sessionId,
|
|
326
|
+
...this.securityAlertDeviceFields(meta),
|
|
327
|
+
});
|
|
196
328
|
}
|
|
197
329
|
try {
|
|
198
330
|
await this.entityStorage.updateOneAsEngine(USER_ENTITY, user.id, {
|
|
@@ -204,6 +336,87 @@ let AuthService = AuthService_1 = class AuthService {
|
|
|
204
336
|
}
|
|
205
337
|
return tokens;
|
|
206
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* Tenant setting MaxActiveDeviceSessions — per user + tenant.
|
|
341
|
+
* Enforced when a real tenant is known (tenant chooser bind / auto-bind, or create with tid).
|
|
342
|
+
* Same device upsert (create) and re-bind of the current session to the same tenant are allowed.
|
|
343
|
+
*/
|
|
344
|
+
async assertMaxActiveDeviceSessionsAllowed(userId, meta, tenantId, options) {
|
|
345
|
+
const tid = tenantId?.trim();
|
|
346
|
+
if (!tid || (0, device_session_service_1.isUnboundDeviceSessionTenantId)(tid)) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const max = await (0, auth_max_device_sessions_policy_1.resolveAuthMaxDeviceSessions)(tid);
|
|
350
|
+
if (max <= 0) {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (options.mode === 'update-binding' && options.sessionId) {
|
|
354
|
+
const boundTenantId = await this.deviceSessions.getActiveTenantIdForSession(options.sessionId);
|
|
355
|
+
if (boundTenantId === tid) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
const activeCount = await this.deviceSessions.countActiveForUserTenant(userId, tid, {
|
|
359
|
+
excludeSessionIds: [options.sessionId],
|
|
360
|
+
});
|
|
361
|
+
if (activeCount >= max) {
|
|
362
|
+
throw new common_1.ForbiddenException({
|
|
363
|
+
code: exports.MAX_ACTIVE_DEVICE_SESSIONS_CODE,
|
|
364
|
+
message: exports.MAX_ACTIVE_DEVICE_SESSIONS_CODE,
|
|
365
|
+
error: exports.MAX_ACTIVE_DEVICE_SESSIONS_CODE,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (await this.deviceSessions.hasActiveDeviceForUser(userId, meta)) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
const activeCount = await this.deviceSessions.countActiveForUserTenant(userId, tid);
|
|
374
|
+
if (activeCount >= max) {
|
|
375
|
+
throw new common_1.ForbiddenException({
|
|
376
|
+
code: exports.MAX_ACTIVE_DEVICE_SESSIONS_CODE,
|
|
377
|
+
message: exports.MAX_ACTIVE_DEVICE_SESSIONS_CODE,
|
|
378
|
+
error: exports.MAX_ACTIVE_DEVICE_SESSIONS_CODE,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Active DeviceSessions for the authenticated user in a tenant (session-limit chooser).
|
|
384
|
+
*/
|
|
385
|
+
async listOwnActiveDeviceSessions(accessToken, tenantId) {
|
|
386
|
+
const current = await this.validateAccessToken(accessToken);
|
|
387
|
+
const tid = tenantId.trim();
|
|
388
|
+
if (!tid || (0, device_session_service_1.isUnboundDeviceSessionTenantId)(tid)) {
|
|
389
|
+
return [];
|
|
390
|
+
}
|
|
391
|
+
const rows = await this.deviceSessions.listActiveSessionsForUserTenant(current.sub, tid);
|
|
392
|
+
return rows
|
|
393
|
+
.filter((row) => row.sessionId !== current.sid)
|
|
394
|
+
.map((row) => ({
|
|
395
|
+
id: row.id,
|
|
396
|
+
sessionId: row.sessionId,
|
|
397
|
+
tenantId: row.tenantId,
|
|
398
|
+
deviceName: row.deviceName,
|
|
399
|
+
browser: row.browser,
|
|
400
|
+
operatingSystem: row.operatingSystem,
|
|
401
|
+
ipAddress: row.ipAddress,
|
|
402
|
+
lastLoginAt: row.lastLoginAt,
|
|
403
|
+
}));
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Terminate one of the caller's DeviceSessions (by entity id) during session-limit recovery.
|
|
407
|
+
*/
|
|
408
|
+
async terminateOwnDeviceSession(accessToken, deviceSessionId) {
|
|
409
|
+
const current = await this.validateAccessToken(accessToken);
|
|
410
|
+
const revokedSid = await this.deviceSessions.revokeOwnedSessionById(current.sub, deviceSessionId);
|
|
411
|
+
if (revokedSid == null) {
|
|
412
|
+
throw new common_1.BadRequestException('Device session not found');
|
|
413
|
+
}
|
|
414
|
+
// Engine update skips DeviceSessionsTokenRevokeMiddleware — stamp Redis here.
|
|
415
|
+
if (revokedSid !== '' && revokedSid !== current.sid) {
|
|
416
|
+
await this.markSessionRevoked(revokedSid, { required: true });
|
|
417
|
+
this.notifyRemoteSessionTerminated(current.sub, revokedSid);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
207
420
|
emitLogoutEvent(payload) {
|
|
208
421
|
try {
|
|
209
422
|
this.events?.emit(auth_events_1.AUTH_LOGOUT_EVENT, payload);
|
|
@@ -212,6 +425,24 @@ let AuthService = AuthService_1 = class AuthService {
|
|
|
212
425
|
this.logger.debug(`Auth logout event emit failed: ${String(error)}`);
|
|
213
426
|
}
|
|
214
427
|
}
|
|
428
|
+
emitSecurityAlert(payload) {
|
|
429
|
+
try {
|
|
430
|
+
this.events?.emit(auth_events_1.AUTH_SECURITY_ALERT_EVENT, payload);
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
this.logger.debug(`Auth security alert emit failed: ${String(error)}`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
securityAlertDeviceFields(meta) {
|
|
437
|
+
const parsed = (0, request_meta_util_1.parseUserAgent)(meta.userAgent);
|
|
438
|
+
return {
|
|
439
|
+
deviceSummary: `${parsed.deviceName} · ${parsed.browser}`,
|
|
440
|
+
ipAddress: meta.ipAddress,
|
|
441
|
+
browser: parsed.browser,
|
|
442
|
+
deviceName: parsed.deviceName,
|
|
443
|
+
operatingSystem: parsed.operatingSystem,
|
|
444
|
+
};
|
|
445
|
+
}
|
|
215
446
|
toAuthUser(user) {
|
|
216
447
|
return {
|
|
217
448
|
id: user.id,
|
|
@@ -377,6 +608,76 @@ let AuthService = AuthService_1 = class AuthService {
|
|
|
377
608
|
'default-name';
|
|
378
609
|
return { id: applicationId, name: routeName, title };
|
|
379
610
|
}
|
|
611
|
+
async assertRefreshNotRevoked(jti) {
|
|
612
|
+
if (!this.refreshReplayRedis || !jti.trim()) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const revoked = await this.refreshReplayRedis.get(`${token_revocation_constants_1.OAUTH_REFRESH_REVOKED_KEY_PREFIX}${jti}`);
|
|
616
|
+
if (revoked) {
|
|
617
|
+
throw new common_1.UnauthorizedException('Refresh token has been revoked.');
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
async markRefreshRevoked(jti) {
|
|
621
|
+
if (!this.refreshReplayRedis || !jti.trim()) {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
const ttlMs = this.refreshTokenMaxAgeMs();
|
|
625
|
+
await this.refreshReplayRedis.set(`${token_revocation_constants_1.OAUTH_REFRESH_REVOKED_KEY_PREFIX}${jti}`, '1', 'PX', ttlMs);
|
|
626
|
+
}
|
|
627
|
+
async markSessionRevoked(sessionId, options) {
|
|
628
|
+
const sid = sessionId?.trim();
|
|
629
|
+
if (!sid) {
|
|
630
|
+
if (options?.required) {
|
|
631
|
+
throw new common_1.BadRequestException('sessionId is required to invalidate a login session.');
|
|
632
|
+
}
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
if (!this.refreshReplayRedis) {
|
|
636
|
+
const message = 'Session revoke store (Redis) is unavailable; cannot project DeviceSessions revoke.';
|
|
637
|
+
if (options?.required) {
|
|
638
|
+
throw new common_1.ServiceUnavailableException(message);
|
|
639
|
+
}
|
|
640
|
+
this.logger.warn(message);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
// Cover remaining access-token TTL after logout (refresh TTL is an upper bound).
|
|
644
|
+
const ttlMs = Math.max(this.refreshTokenMaxAgeMs(), this.accessTokenMaxAgeMs());
|
|
645
|
+
await this.refreshReplayRedis.set(`${token_revocation_constants_1.OAUTH_SESSION_REVOKED_KEY_PREFIX}${sid}`, '1', 'PX', ttlMs);
|
|
646
|
+
}
|
|
647
|
+
/** Invalidates prior login families when the same device row is reused (per-device upsert). */
|
|
648
|
+
async revokeReplacedDeviceSessions(sessionIds) {
|
|
649
|
+
for (const sid of sessionIds) {
|
|
650
|
+
await this.markSessionRevoked(sid, { required: true });
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* OD-09 hot path: Redis projection only. DeviceSessions is SoT on write;
|
|
655
|
+
* do not query Mongo here (see device-session-revocation.mdc).
|
|
656
|
+
*/
|
|
657
|
+
async assertSessionAllowed(sessionId) {
|
|
658
|
+
const sid = sessionId?.trim();
|
|
659
|
+
if (!sid) {
|
|
660
|
+
// Legacy access tokens without `sid` — signature/exp only.
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
if (!this.refreshReplayRedis) {
|
|
664
|
+
throw new common_1.ServiceUnavailableException('Session store (Redis) is unavailable.');
|
|
665
|
+
}
|
|
666
|
+
const revoked = await this.refreshReplayRedis.get(`${token_revocation_constants_1.OAUTH_SESSION_REVOKED_KEY_PREFIX}${sid}`);
|
|
667
|
+
if (revoked) {
|
|
668
|
+
throw new common_1.UnauthorizedException('Session has been revoked.');
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
async assertAccessJtiNotRevoked(jti) {
|
|
672
|
+
const id = jti?.trim();
|
|
673
|
+
if (!this.refreshReplayRedis || !id) {
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
const revoked = await this.refreshReplayRedis.get(`${token_revocation_constants_1.OAUTH_ACCESS_REVOKED_KEY_PREFIX}${id}`);
|
|
677
|
+
if (revoked) {
|
|
678
|
+
throw new common_1.UnauthorizedException('Access token has been revoked.');
|
|
679
|
+
}
|
|
680
|
+
}
|
|
380
681
|
};
|
|
381
682
|
exports.AuthService = AuthService;
|
|
382
683
|
exports.AuthService = AuthService = AuthService_1 = tslib_1.__decorate([
|