@platform-modules/civil-aviation-authority 2.3.302 → 2.3.303

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.
@@ -0,0 +1,19 @@
1
+ import type { UserSessionReader } from './user-session-validation';
2
+ export type JwtAuthFailure = {
3
+ ok: false;
4
+ statusCode: 401;
5
+ message: string;
6
+ };
7
+ export type JwtAuthSuccess = {
8
+ ok: true;
9
+ claims: Record<string, unknown>;
10
+ userId: number;
11
+ sessionId: number | null;
12
+ };
13
+ export type JwtAuthResult = JwtAuthSuccess | JwtAuthFailure;
14
+ /**
15
+ * After jwt.verify(), enforce server-side session binding (logout revocation).
16
+ */
17
+ export declare function authorizeVerifiedJwtSession(verified: Record<string, unknown>, sessionReader: UserSessionReader, options?: {
18
+ requireActiveSession?: boolean;
19
+ }): Promise<JwtAuthResult>;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authorizeVerifiedJwtSession = authorizeVerifiedJwtSession;
4
+ const jwt_token_helper_1 = require("./jwt-token.helper");
5
+ const user_session_validation_1 = require("./user-session-validation");
6
+ /**
7
+ * After jwt.verify(), enforce server-side session binding (logout revocation).
8
+ */
9
+ async function authorizeVerifiedJwtSession(verified, sessionReader, options) {
10
+ const parsed = (0, jwt_token_helper_1.parseVerifiedJwtClaims)(verified);
11
+ if (!parsed) {
12
+ return { ok: false, statusCode: 401, message: 'Invalid Token' };
13
+ }
14
+ const requireActiveSession = options?.requireActiveSession !== false;
15
+ if (requireActiveSession) {
16
+ const sessionValid = await (0, user_session_validation_1.validateUserSessionBinding)(parsed.userId, parsed.sessionId, sessionReader);
17
+ if (!sessionValid) {
18
+ return { ok: false, statusCode: 401, message: 'Session Expired please login again' };
19
+ }
20
+ }
21
+ return {
22
+ ok: true,
23
+ claims: parsed.raw,
24
+ userId: parsed.userId,
25
+ sessionId: parsed.sessionId,
26
+ };
27
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Extract CAA API JWT from standard request headers.
3
+ * Supports `jwt` header (legacy) and `Authorization: Bearer <token>`.
4
+ */
5
+ export declare function extractJwtToken(headers: Record<string, string | string[] | undefined> | undefined): string | null;
6
+ export type JwtTokenRequest = {
7
+ headers: Record<string, string | string[] | undefined>;
8
+ body?: unknown;
9
+ };
10
+ /** JWT from headers (`jwt`, `Authorization: Bearer`) or JSON body (`token`, `jwt`, `access_token`). */
11
+ export declare function extractJwtTokenFromRequest(request: JwtTokenRequest): string | null;
12
+ export type VerifiedJwtClaims = {
13
+ userId: number;
14
+ sessionId: number | null;
15
+ raw: Record<string, unknown>;
16
+ };
17
+ /** Parse verified JWT payload into userId + sessionId. */
18
+ export declare function parseVerifiedJwtClaims(verified: Record<string, unknown>): VerifiedJwtClaims | null;
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractJwtToken = extractJwtToken;
4
+ exports.extractJwtTokenFromRequest = extractJwtTokenFromRequest;
5
+ exports.parseVerifiedJwtClaims = parseVerifiedJwtClaims;
6
+ /**
7
+ * Extract CAA API JWT from standard request headers.
8
+ * Supports `jwt` header (legacy) and `Authorization: Bearer <token>`.
9
+ */
10
+ function extractJwtToken(headers) {
11
+ if (!headers)
12
+ return null;
13
+ const pick = (value) => {
14
+ const raw = Array.isArray(value) ? value[0] : value;
15
+ const trimmed = raw?.trim();
16
+ return trimmed ? trimmed : null;
17
+ };
18
+ const fromJwtHeader = pick(headers.jwt ?? headers.JWT);
19
+ if (fromJwtHeader)
20
+ return fromJwtHeader;
21
+ const authorization = pick(headers.authorization ?? headers.Authorization);
22
+ if (!authorization)
23
+ return null;
24
+ const bearerMatch = /^Bearer\s+(.+)$/i.exec(authorization);
25
+ if (bearerMatch?.[1]?.trim())
26
+ return bearerMatch[1].trim();
27
+ return authorization;
28
+ }
29
+ /** JWT from headers (`jwt`, `Authorization: Bearer`) or JSON body (`token`, `jwt`, `access_token`). */
30
+ function extractJwtTokenFromRequest(request) {
31
+ const fromHeaders = extractJwtToken(request.headers);
32
+ if (fromHeaders)
33
+ return fromHeaders;
34
+ const body = request.body;
35
+ if (!body || typeof body !== 'object')
36
+ return null;
37
+ for (const key of ['token', 'jwt', 'access_token']) {
38
+ const value = body[key];
39
+ if (typeof value === 'string' && value.trim())
40
+ return value.trim();
41
+ }
42
+ return null;
43
+ }
44
+ /** Parse verified JWT payload into userId + sessionId. */
45
+ function parseVerifiedJwtClaims(verified) {
46
+ const userId = Number(verified.userId);
47
+ if (!Number.isFinite(userId) || userId <= 0)
48
+ return null;
49
+ const sessionIdRaw = verified.sessionId;
50
+ const parsedSessionId = sessionIdRaw == null || sessionIdRaw === '' ? null : Number(sessionIdRaw);
51
+ return {
52
+ userId,
53
+ sessionId: parsedSessionId != null && Number.isFinite(parsedSessionId) && parsedSessionId > 0
54
+ ? parsedSessionId
55
+ : null,
56
+ raw: verified,
57
+ };
58
+ }
@@ -16,6 +16,10 @@ export declare function resolveJwtSessionId(sessionId: unknown): number | null;
16
16
  */
17
17
  export declare function validateUserSessionBinding(userId: number, sessionId: number | null | undefined, reader: UserSessionReader): Promise<boolean>;
18
18
  /** Shared TypeORM filters: active, not deleted, not past expires_at. */
19
+ export declare function applyActiveUserSessionFilters(qb: {
20
+ andWhere: (clause: string, params?: Record<string, unknown>) => unknown;
21
+ }, alias?: string): void;
22
+ /** @deprecated Use applyActiveUserSessionFilters — kept for backward compatibility. */
19
23
  export declare function activeUserSessionSql(alias?: string): {
20
24
  clause: string;
21
25
  params: Record<string, unknown>;
@@ -8,6 +8,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
8
8
  exports.isRequireJwtSessionId = isRequireJwtSessionId;
9
9
  exports.resolveJwtSessionId = resolveJwtSessionId;
10
10
  exports.validateUserSessionBinding = validateUserSessionBinding;
11
+ exports.applyActiveUserSessionFilters = applyActiveUserSessionFilters;
11
12
  exports.activeUserSessionSql = activeUserSessionSql;
12
13
  /** When true (default), JWT must include sessionId and that session must be active. */
13
14
  function isRequireJwtSessionId() {
@@ -41,6 +42,12 @@ async function validateUserSessionBinding(userId, sessionId, reader) {
41
42
  return !!legacy;
42
43
  }
43
44
  /** Shared TypeORM filters: active, not deleted, not past expires_at. */
45
+ function applyActiveUserSessionFilters(qb, alias = 'user_sessions') {
46
+ qb.andWhere(`${alias}.is_active = :is_active`, { is_active: true });
47
+ qb.andWhere(`${alias}.is_deleted = :is_deleted`, { is_deleted: false });
48
+ qb.andWhere(`(${alias}.expires_at IS NULL OR ${alias}.expires_at > :session_now)`, { session_now: new Date() });
49
+ }
50
+ /** @deprecated Use applyActiveUserSessionFilters — kept for backward compatibility. */
44
51
  function activeUserSessionSql(alias = 'user_sessions') {
45
52
  return {
46
53
  clause: `${alias}.is_active = :is_active AND ${alias}.is_deleted = :is_deleted AND (${alias}.expires_at IS NULL OR ${alias}.expires_at > :session_now)`,
package/dist/index.d.ts CHANGED
@@ -2,6 +2,8 @@ export * from './models/user';
2
2
  export * from './models/role';
3
3
  export * from './models/user-sessions';
4
4
  export * from './auth/user-session-validation';
5
+ export * from './auth/jwt-token.helper';
6
+ export * from './auth/authenticate-jwt-session';
5
7
  export * from './models/ITHelpDeskModel';
6
8
  export * from './models/ServiceTypeModel';
7
9
  export * from './models/CAAServices';
package/dist/index.js CHANGED
@@ -21,6 +21,8 @@ __exportStar(require("./models/user"), exports);
21
21
  __exportStar(require("./models/role"), exports);
22
22
  __exportStar(require("./models/user-sessions"), exports);
23
23
  __exportStar(require("./auth/user-session-validation"), exports);
24
+ __exportStar(require("./auth/jwt-token.helper"), exports);
25
+ __exportStar(require("./auth/authenticate-jwt-session"), exports);
24
26
  __exportStar(require("./models/ITHelpDeskModel"), exports);
25
27
  // export * from './models/ITServicesTypesSalalahModel'; // REMOVED - table no longer used
26
28
  // export * from './models/ITServicesTypesMuscatModel'; // REMOVED - table no longer used
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platform-modules/civil-aviation-authority",
3
- "version": "2.3.302",
3
+ "version": "2.3.303",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "scripts": {
@@ -0,0 +1,51 @@
1
+ import { parseVerifiedJwtClaims } from './jwt-token.helper';
2
+ import { validateUserSessionBinding } from './user-session-validation';
3
+ import type { UserSessionReader } from './user-session-validation';
4
+
5
+ export type JwtAuthFailure = {
6
+ ok: false;
7
+ statusCode: 401;
8
+ message: string;
9
+ };
10
+
11
+ export type JwtAuthSuccess = {
12
+ ok: true;
13
+ claims: Record<string, unknown>;
14
+ userId: number;
15
+ sessionId: number | null;
16
+ };
17
+
18
+ export type JwtAuthResult = JwtAuthSuccess | JwtAuthFailure;
19
+
20
+ /**
21
+ * After jwt.verify(), enforce server-side session binding (logout revocation).
22
+ */
23
+ export async function authorizeVerifiedJwtSession(
24
+ verified: Record<string, unknown>,
25
+ sessionReader: UserSessionReader,
26
+ options?: { requireActiveSession?: boolean },
27
+ ): Promise<JwtAuthResult> {
28
+ const parsed = parseVerifiedJwtClaims(verified);
29
+ if (!parsed) {
30
+ return { ok: false, statusCode: 401, message: 'Invalid Token' };
31
+ }
32
+
33
+ const requireActiveSession = options?.requireActiveSession !== false;
34
+ if (requireActiveSession) {
35
+ const sessionValid = await validateUserSessionBinding(
36
+ parsed.userId,
37
+ parsed.sessionId,
38
+ sessionReader,
39
+ );
40
+ if (!sessionValid) {
41
+ return { ok: false, statusCode: 401, message: 'Session Expired please login again' };
42
+ }
43
+ }
44
+
45
+ return {
46
+ ok: true,
47
+ claims: parsed.raw,
48
+ userId: parsed.userId,
49
+ sessionId: parsed.sessionId,
50
+ };
51
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Extract CAA API JWT from standard request headers.
3
+ * Supports `jwt` header (legacy) and `Authorization: Bearer <token>`.
4
+ */
5
+ export function extractJwtToken(
6
+ headers: Record<string, string | string[] | undefined> | undefined,
7
+ ): string | null {
8
+ if (!headers) return null;
9
+
10
+ const pick = (value: string | string[] | undefined): string | null => {
11
+ const raw = Array.isArray(value) ? value[0] : value;
12
+ const trimmed = raw?.trim();
13
+ return trimmed ? trimmed : null;
14
+ };
15
+
16
+ const fromJwtHeader = pick(headers.jwt ?? headers.JWT);
17
+ if (fromJwtHeader) return fromJwtHeader;
18
+
19
+ const authorization = pick(headers.authorization ?? headers.Authorization);
20
+ if (!authorization) return null;
21
+
22
+ const bearerMatch = /^Bearer\s+(.+)$/i.exec(authorization);
23
+ if (bearerMatch?.[1]?.trim()) return bearerMatch[1].trim();
24
+
25
+ return authorization;
26
+ }
27
+
28
+ export type JwtTokenRequest = {
29
+ headers: Record<string, string | string[] | undefined>;
30
+ body?: unknown;
31
+ };
32
+
33
+ /** JWT from headers (`jwt`, `Authorization: Bearer`) or JSON body (`token`, `jwt`, `access_token`). */
34
+ export function extractJwtTokenFromRequest(request: JwtTokenRequest): string | null {
35
+ const fromHeaders = extractJwtToken(request.headers);
36
+ if (fromHeaders) return fromHeaders;
37
+
38
+ const body = request.body as Record<string, unknown> | undefined;
39
+ if (!body || typeof body !== 'object') return null;
40
+
41
+ for (const key of ['token', 'jwt', 'access_token'] as const) {
42
+ const value = body[key];
43
+ if (typeof value === 'string' && value.trim()) return value.trim();
44
+ }
45
+ return null;
46
+ }
47
+
48
+ export type VerifiedJwtClaims = {
49
+ userId: number;
50
+ sessionId: number | null;
51
+ raw: Record<string, unknown>;
52
+ };
53
+
54
+ /** Parse verified JWT payload into userId + sessionId. */
55
+ export function parseVerifiedJwtClaims(verified: Record<string, unknown>): VerifiedJwtClaims | null {
56
+ const userId = Number(verified.userId);
57
+ if (!Number.isFinite(userId) || userId <= 0) return null;
58
+
59
+ const sessionIdRaw = verified.sessionId;
60
+ const parsedSessionId =
61
+ sessionIdRaw == null || sessionIdRaw === '' ? null : Number(sessionIdRaw);
62
+
63
+ return {
64
+ userId,
65
+ sessionId:
66
+ parsedSessionId != null && Number.isFinite(parsedSessionId) && parsedSessionId > 0
67
+ ? parsedSessionId
68
+ : null,
69
+ raw: verified,
70
+ };
71
+ }
@@ -48,6 +48,21 @@ export async function validateUserSessionBinding(
48
48
  }
49
49
 
50
50
  /** Shared TypeORM filters: active, not deleted, not past expires_at. */
51
+ export function applyActiveUserSessionFilters(
52
+ qb: {
53
+ andWhere: (clause: string, params?: Record<string, unknown>) => unknown;
54
+ },
55
+ alias = 'user_sessions',
56
+ ): void {
57
+ qb.andWhere(`${alias}.is_active = :is_active`, { is_active: true });
58
+ qb.andWhere(`${alias}.is_deleted = :is_deleted`, { is_deleted: false });
59
+ qb.andWhere(
60
+ `(${alias}.expires_at IS NULL OR ${alias}.expires_at > :session_now)`,
61
+ { session_now: new Date() },
62
+ );
63
+ }
64
+
65
+ /** @deprecated Use applyActiveUserSessionFilters — kept for backward compatibility. */
51
66
  export function activeUserSessionSql(alias = 'user_sessions'): {
52
67
  clause: string;
53
68
  params: Record<string, unknown>;
package/src/index.ts CHANGED
@@ -4,6 +4,8 @@ export * from './models/user';
4
4
  export * from './models/role';
5
5
  export * from './models/user-sessions';
6
6
  export * from './auth/user-session-validation';
7
+ export * from './auth/jwt-token.helper';
8
+ export * from './auth/authenticate-jwt-session';
7
9
  export * from './models/ITHelpDeskModel';
8
10
  // export * from './models/ITServicesTypesSalalahModel'; // REMOVED - table no longer used
9
11
  // export * from './models/ITServicesTypesMuscatModel'; // REMOVED - table no longer used