@flink-app/oidc-plugin 1.0.0

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.
Files changed (112) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +846 -0
  4. package/dist/OidcInternalContext.d.ts +15 -0
  5. package/dist/OidcInternalContext.d.ts.map +1 -0
  6. package/dist/OidcInternalContext.js +2 -0
  7. package/dist/OidcPlugin.d.ts +77 -0
  8. package/dist/OidcPlugin.d.ts.map +1 -0
  9. package/dist/OidcPlugin.js +274 -0
  10. package/dist/OidcPluginContext.d.ts +73 -0
  11. package/dist/OidcPluginContext.d.ts.map +1 -0
  12. package/dist/OidcPluginContext.js +2 -0
  13. package/dist/OidcPluginOptions.d.ts +267 -0
  14. package/dist/OidcPluginOptions.d.ts.map +1 -0
  15. package/dist/OidcPluginOptions.js +2 -0
  16. package/dist/OidcProviderConfig.d.ts +77 -0
  17. package/dist/OidcProviderConfig.d.ts.map +1 -0
  18. package/dist/OidcProviderConfig.js +2 -0
  19. package/dist/handlers/CallbackOidc.d.ts +38 -0
  20. package/dist/handlers/CallbackOidc.d.ts.map +1 -0
  21. package/dist/handlers/CallbackOidc.js +219 -0
  22. package/dist/handlers/InitiateOidc.d.ts +35 -0
  23. package/dist/handlers/InitiateOidc.d.ts.map +1 -0
  24. package/dist/handlers/InitiateOidc.js +91 -0
  25. package/dist/index.d.ts +27 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +40 -0
  28. package/dist/providers/OidcProvider.d.ts +90 -0
  29. package/dist/providers/OidcProvider.d.ts.map +1 -0
  30. package/dist/providers/OidcProvider.js +208 -0
  31. package/dist/providers/ProviderRegistry.d.ts +55 -0
  32. package/dist/providers/ProviderRegistry.d.ts.map +1 -0
  33. package/dist/providers/ProviderRegistry.js +94 -0
  34. package/dist/repos/OidcConnectionRepo.d.ts +75 -0
  35. package/dist/repos/OidcConnectionRepo.d.ts.map +1 -0
  36. package/dist/repos/OidcConnectionRepo.js +122 -0
  37. package/dist/repos/OidcSessionRepo.d.ts +57 -0
  38. package/dist/repos/OidcSessionRepo.d.ts.map +1 -0
  39. package/dist/repos/OidcSessionRepo.js +91 -0
  40. package/dist/schemas/CallbackRequest.d.ts +37 -0
  41. package/dist/schemas/CallbackRequest.d.ts.map +1 -0
  42. package/dist/schemas/CallbackRequest.js +2 -0
  43. package/dist/schemas/InitiateRequest.d.ts +17 -0
  44. package/dist/schemas/InitiateRequest.d.ts.map +1 -0
  45. package/dist/schemas/InitiateRequest.js +2 -0
  46. package/dist/schemas/OidcConnection.d.ts +69 -0
  47. package/dist/schemas/OidcConnection.d.ts.map +1 -0
  48. package/dist/schemas/OidcConnection.js +2 -0
  49. package/dist/schemas/OidcProfile.d.ts +69 -0
  50. package/dist/schemas/OidcProfile.d.ts.map +1 -0
  51. package/dist/schemas/OidcProfile.js +2 -0
  52. package/dist/schemas/OidcSession.d.ts +46 -0
  53. package/dist/schemas/OidcSession.d.ts.map +1 -0
  54. package/dist/schemas/OidcSession.js +2 -0
  55. package/dist/schemas/OidcTokenSet.d.ts +42 -0
  56. package/dist/schemas/OidcTokenSet.d.ts.map +1 -0
  57. package/dist/schemas/OidcTokenSet.js +2 -0
  58. package/dist/utils/claims-mapper.d.ts +46 -0
  59. package/dist/utils/claims-mapper.d.ts.map +1 -0
  60. package/dist/utils/claims-mapper.js +104 -0
  61. package/dist/utils/encryption-utils.d.ts +32 -0
  62. package/dist/utils/encryption-utils.d.ts.map +1 -0
  63. package/dist/utils/encryption-utils.js +82 -0
  64. package/dist/utils/error-utils.d.ts +65 -0
  65. package/dist/utils/error-utils.d.ts.map +1 -0
  66. package/dist/utils/error-utils.js +150 -0
  67. package/dist/utils/response-utils.d.ts +18 -0
  68. package/dist/utils/response-utils.d.ts.map +1 -0
  69. package/dist/utils/response-utils.js +42 -0
  70. package/dist/utils/state-utils.d.ts +36 -0
  71. package/dist/utils/state-utils.d.ts.map +1 -0
  72. package/dist/utils/state-utils.js +66 -0
  73. package/examples/basic-oidc.ts +151 -0
  74. package/examples/multi-provider.ts +146 -0
  75. package/package.json +44 -0
  76. package/spec/handlers/InitiateOidc.spec.ts +62 -0
  77. package/spec/helpers/reporter.ts +34 -0
  78. package/spec/helpers/test-helpers.ts +108 -0
  79. package/spec/plugin/OidcPlugin.spec.ts +126 -0
  80. package/spec/providers/ProviderRegistry.spec.ts +197 -0
  81. package/spec/repos/OidcConnectionRepo.spec.ts +257 -0
  82. package/spec/repos/OidcSessionRepo.spec.ts +196 -0
  83. package/spec/support/jasmine.json +7 -0
  84. package/spec/utils/claims-mapper.spec.ts +257 -0
  85. package/spec/utils/encryption-utils.spec.ts +126 -0
  86. package/spec/utils/error-utils.spec.ts +107 -0
  87. package/spec/utils/state-utils.spec.ts +102 -0
  88. package/src/OidcInternalContext.ts +15 -0
  89. package/src/OidcPlugin.ts +290 -0
  90. package/src/OidcPluginContext.ts +76 -0
  91. package/src/OidcPluginOptions.ts +286 -0
  92. package/src/OidcProviderConfig.ts +87 -0
  93. package/src/handlers/CallbackOidc.ts +257 -0
  94. package/src/handlers/InitiateOidc.ts +110 -0
  95. package/src/index.ts +38 -0
  96. package/src/providers/OidcProvider.ts +237 -0
  97. package/src/providers/ProviderRegistry.ts +107 -0
  98. package/src/repos/OidcConnectionRepo.ts +132 -0
  99. package/src/repos/OidcSessionRepo.ts +99 -0
  100. package/src/schemas/CallbackRequest.ts +41 -0
  101. package/src/schemas/InitiateRequest.ts +17 -0
  102. package/src/schemas/OidcConnection.ts +80 -0
  103. package/src/schemas/OidcProfile.ts +79 -0
  104. package/src/schemas/OidcSession.ts +52 -0
  105. package/src/schemas/OidcTokenSet.ts +47 -0
  106. package/src/utils/claims-mapper.ts +114 -0
  107. package/src/utils/encryption-utils.ts +92 -0
  108. package/src/utils/error-utils.ts +167 -0
  109. package/src/utils/response-utils.ts +41 -0
  110. package/src/utils/state-utils.ts +66 -0
  111. package/tsconfig.dist.json +9 -0
  112. package/tsconfig.json +20 -0
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,46 @@
1
+ import OidcProfile from "../schemas/OidcProfile";
2
+ /**
3
+ * Map OIDC claims to normalized profile
4
+ *
5
+ * Extracts standard OIDC claims from the ID token and UserInfo response
6
+ * and maps them to a consistent profile structure.
7
+ *
8
+ * Standard OIDC claims reference:
9
+ * https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
10
+ *
11
+ * @param claims - OIDC claims from ID token and/or UserInfo endpoint
12
+ * @returns Normalized user profile
13
+ */
14
+ export declare function mapClaimsToProfile(claims: Record<string, any>): OidcProfile;
15
+ /**
16
+ * Extract custom claims using claim mapping configuration
17
+ *
18
+ * Allows extracting custom claims from the ID token using dot notation.
19
+ * Supports nested claim paths.
20
+ *
21
+ * @param claims - OIDC claims from ID token
22
+ * @param claimMapping - Map of field names to claim paths
23
+ * @returns Object with extracted custom claims
24
+ *
25
+ * Example:
26
+ * ```typescript
27
+ * const claims = {
28
+ * sub: '123',
29
+ * email: 'user@example.com',
30
+ * 'custom:department': 'Engineering',
31
+ * 'custom:role': 'Admin',
32
+ * groups: ['engineers', 'admins']
33
+ * };
34
+ *
35
+ * const mapping = {
36
+ * department: 'custom:department',
37
+ * role: 'custom:role',
38
+ * groups: 'groups'
39
+ * };
40
+ *
41
+ * const custom = extractCustomClaims(claims, mapping);
42
+ * // { department: 'Engineering', role: 'Admin', groups: ['engineers', 'admins'] }
43
+ * ```
44
+ */
45
+ export declare function extractCustomClaims(claims: Record<string, any>, claimMapping: Record<string, string>): Record<string, any>;
46
+ //# sourceMappingURL=claims-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claims-mapper.d.ts","sourceRoot":"","sources":["../../src/utils/claims-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,wBAAwB,CAAC;AAEjD;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,WAAW,CA4B3E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAW1H"}
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mapClaimsToProfile = mapClaimsToProfile;
4
+ exports.extractCustomClaims = extractCustomClaims;
5
+ /**
6
+ * Map OIDC claims to normalized profile
7
+ *
8
+ * Extracts standard OIDC claims from the ID token and UserInfo response
9
+ * and maps them to a consistent profile structure.
10
+ *
11
+ * Standard OIDC claims reference:
12
+ * https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
13
+ *
14
+ * @param claims - OIDC claims from ID token and/or UserInfo endpoint
15
+ * @returns Normalized user profile
16
+ */
17
+ function mapClaimsToProfile(claims) {
18
+ return {
19
+ // Required - subject identifier (unique user ID)
20
+ id: claims.sub,
21
+ // Email
22
+ email: claims.email,
23
+ emailVerified: claims.email_verified,
24
+ // Name fields
25
+ name: claims.name,
26
+ givenName: claims.given_name,
27
+ familyName: claims.family_name,
28
+ middleName: claims.middle_name,
29
+ // Username
30
+ username: claims.preferred_username || claims.username,
31
+ // Picture
32
+ picture: claims.picture,
33
+ // Phone
34
+ phoneNumber: claims.phone_number,
35
+ phoneNumberVerified: claims.phone_number_verified,
36
+ // Keep all raw claims for custom access
37
+ raw: claims,
38
+ };
39
+ }
40
+ /**
41
+ * Extract custom claims using claim mapping configuration
42
+ *
43
+ * Allows extracting custom claims from the ID token using dot notation.
44
+ * Supports nested claim paths.
45
+ *
46
+ * @param claims - OIDC claims from ID token
47
+ * @param claimMapping - Map of field names to claim paths
48
+ * @returns Object with extracted custom claims
49
+ *
50
+ * Example:
51
+ * ```typescript
52
+ * const claims = {
53
+ * sub: '123',
54
+ * email: 'user@example.com',
55
+ * 'custom:department': 'Engineering',
56
+ * 'custom:role': 'Admin',
57
+ * groups: ['engineers', 'admins']
58
+ * };
59
+ *
60
+ * const mapping = {
61
+ * department: 'custom:department',
62
+ * role: 'custom:role',
63
+ * groups: 'groups'
64
+ * };
65
+ *
66
+ * const custom = extractCustomClaims(claims, mapping);
67
+ * // { department: 'Engineering', role: 'Admin', groups: ['engineers', 'admins'] }
68
+ * ```
69
+ */
70
+ function extractCustomClaims(claims, claimMapping) {
71
+ const customClaims = {};
72
+ for (const [fieldName, claimPath] of Object.entries(claimMapping)) {
73
+ const value = getClaimByPath(claims, claimPath);
74
+ if (value !== undefined) {
75
+ customClaims[fieldName] = value;
76
+ }
77
+ }
78
+ return customClaims;
79
+ }
80
+ /**
81
+ * Get claim value by path (supports dot notation)
82
+ *
83
+ * @param claims - Claims object
84
+ * @param path - Claim path (e.g., "custom:department" or "address.street_address")
85
+ * @returns Claim value or undefined if not found
86
+ */
87
+ function getClaimByPath(claims, path) {
88
+ // Handle direct access first (for paths with colons like "custom:department")
89
+ if (path in claims) {
90
+ return claims[path];
91
+ }
92
+ // Handle nested paths with dot notation
93
+ const parts = path.split(".");
94
+ let value = claims;
95
+ for (const part of parts) {
96
+ if (value && typeof value === "object" && part in value) {
97
+ value = value[part];
98
+ }
99
+ else {
100
+ return undefined;
101
+ }
102
+ }
103
+ return value;
104
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Validate encryption secret meets minimum requirements
3
+ *
4
+ * @param secret - Secret to validate
5
+ * @throws Error if secret is invalid
6
+ */
7
+ export declare function validateEncryptionSecret(secret: string): void;
8
+ /**
9
+ * Encrypt a token using AES-256-GCM
10
+ *
11
+ * Uses Galois/Counter Mode (GCM) which provides both confidentiality
12
+ * and authenticity (prevents tampering).
13
+ *
14
+ * Format: iv:authTag:encryptedData (all hex-encoded)
15
+ *
16
+ * @param token - Plain text token to encrypt
17
+ * @param secret - Encryption secret (at least 32 characters)
18
+ * @returns Encrypted token with IV and auth tag
19
+ */
20
+ export declare function encryptToken(token: string, secret: string): string;
21
+ /**
22
+ * Decrypt a token using AES-256-GCM
23
+ *
24
+ * Validates the auth tag to ensure the data hasn't been tampered with.
25
+ *
26
+ * @param encryptedToken - Encrypted token from encryptToken()
27
+ * @param secret - Encryption secret used during encryption
28
+ * @returns Decrypted plain text token
29
+ * @throws Error if decryption fails or auth tag is invalid
30
+ */
31
+ export declare function decryptToken(encryptedToken: string, secret: string): string;
32
+ //# sourceMappingURL=encryption-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryption-utils.d.ts","sourceRoot":"","sources":["../../src/utils/encryption-utils.ts"],"names":[],"mappings":"AAmBA;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAQ7D;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAYlE;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAoB3E"}
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateEncryptionSecret = validateEncryptionSecret;
4
+ exports.encryptToken = encryptToken;
5
+ exports.decryptToken = decryptToken;
6
+ const crypto_1 = require("crypto");
7
+ const ALGORITHM = "aes-256-gcm";
8
+ const IV_LENGTH = 16;
9
+ const AUTH_TAG_LENGTH = 16;
10
+ const SALT_LENGTH = 32;
11
+ /**
12
+ * Derive a 32-byte encryption key from a secret
13
+ *
14
+ * Uses SHA-256 to create a consistent key length from any secret.
15
+ *
16
+ * @param secret - Secret string (e.g., client secret)
17
+ * @returns 32-byte key suitable for AES-256
18
+ */
19
+ function deriveKey(secret) {
20
+ return (0, crypto_1.createHash)("sha256").update(secret).digest();
21
+ }
22
+ /**
23
+ * Validate encryption secret meets minimum requirements
24
+ *
25
+ * @param secret - Secret to validate
26
+ * @throws Error if secret is invalid
27
+ */
28
+ function validateEncryptionSecret(secret) {
29
+ if (!secret) {
30
+ throw new Error("Encryption secret is required");
31
+ }
32
+ if (secret.length < 32) {
33
+ throw new Error("Encryption secret must be at least 32 characters");
34
+ }
35
+ }
36
+ /**
37
+ * Encrypt a token using AES-256-GCM
38
+ *
39
+ * Uses Galois/Counter Mode (GCM) which provides both confidentiality
40
+ * and authenticity (prevents tampering).
41
+ *
42
+ * Format: iv:authTag:encryptedData (all hex-encoded)
43
+ *
44
+ * @param token - Plain text token to encrypt
45
+ * @param secret - Encryption secret (at least 32 characters)
46
+ * @returns Encrypted token with IV and auth tag
47
+ */
48
+ function encryptToken(token, secret) {
49
+ const key = deriveKey(secret);
50
+ const iv = (0, crypto_1.randomBytes)(IV_LENGTH);
51
+ const cipher = (0, crypto_1.createCipheriv)(ALGORITHM, key, iv);
52
+ let encrypted = cipher.update(token, "utf8", "hex");
53
+ encrypted += cipher.final("hex");
54
+ const authTag = cipher.getAuthTag();
55
+ // Format: iv:authTag:encryptedData
56
+ return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
57
+ }
58
+ /**
59
+ * Decrypt a token using AES-256-GCM
60
+ *
61
+ * Validates the auth tag to ensure the data hasn't been tampered with.
62
+ *
63
+ * @param encryptedToken - Encrypted token from encryptToken()
64
+ * @param secret - Encryption secret used during encryption
65
+ * @returns Decrypted plain text token
66
+ * @throws Error if decryption fails or auth tag is invalid
67
+ */
68
+ function decryptToken(encryptedToken, secret) {
69
+ const parts = encryptedToken.split(":");
70
+ if (parts.length !== 3) {
71
+ throw new Error("Invalid encrypted token format");
72
+ }
73
+ const [ivHex, authTagHex, encryptedData] = parts;
74
+ const key = deriveKey(secret);
75
+ const iv = Buffer.from(ivHex, "hex");
76
+ const authTag = Buffer.from(authTagHex, "hex");
77
+ const decipher = (0, crypto_1.createDecipheriv)(ALGORITHM, key, iv);
78
+ decipher.setAuthTag(authTag);
79
+ let decrypted = decipher.update(encryptedData, "hex", "utf8");
80
+ decrypted += decipher.final("utf8");
81
+ return decrypted;
82
+ }
@@ -0,0 +1,65 @@
1
+ import { OidcError } from "../OidcPluginOptions";
2
+ /**
3
+ * Standard OIDC error codes
4
+ */
5
+ export declare const OidcErrorCodes: {
6
+ readonly INVALID_PROVIDER: "invalid_provider";
7
+ readonly PROVIDER_NOT_CONFIGURED: "provider_not_configured";
8
+ readonly DISCOVERY_FAILED: "discovery_failed";
9
+ readonly MISSING_CODE: "missing_code";
10
+ readonly MISSING_STATE: "missing_state";
11
+ readonly INVALID_STATE: "invalid_state";
12
+ readonly INVALID_RESPONSE_TYPE: "invalid_response_type";
13
+ readonly SESSION_NOT_FOUND: "session_not_found";
14
+ readonly SESSION_EXPIRED: "session_expired";
15
+ readonly TOKEN_EXCHANGE_FAILED: "token_exchange_failed";
16
+ readonly INVALID_TOKEN: "invalid_token";
17
+ readonly ID_TOKEN_VALIDATION_FAILED: "id_token_validation_failed";
18
+ readonly USERINFO_FAILED: "userinfo_failed";
19
+ readonly PROFILE_EXTRACTION_FAILED: "profile_extraction_failed";
20
+ readonly JWT_GENERATION_FAILED: "jwt_generation_failed";
21
+ readonly ACCESS_DENIED: "access_denied";
22
+ readonly UNAUTHORIZED_CLIENT: "unauthorized_client";
23
+ readonly INVALID_REQUEST: "invalid_request";
24
+ readonly UNSUPPORTED_RESPONSE_TYPE: "unsupported_response_type";
25
+ readonly INVALID_SCOPE: "invalid_scope";
26
+ readonly SERVER_ERROR: "server_error";
27
+ readonly TEMPORARILY_UNAVAILABLE: "temporarily_unavailable";
28
+ };
29
+ /**
30
+ * Create a standardized OIDC error object
31
+ *
32
+ * @param code - Error code from OidcErrorCodes
33
+ * @param message - Human-readable error message
34
+ * @param details - Additional error details for debugging
35
+ * @returns OIDC error object
36
+ */
37
+ export declare function createOidcError(code: string, message: string, details?: any): OidcError;
38
+ /**
39
+ * Validate provider name format
40
+ *
41
+ * Provider names must be alphanumeric with optional hyphens/underscores
42
+ * to ensure they work correctly in URLs.
43
+ *
44
+ * @param provider - Provider name to validate
45
+ * @throws OidcError if invalid
46
+ */
47
+ export declare function validateProvider(provider: string): void;
48
+ /**
49
+ * Validate response type parameter
50
+ *
51
+ * @param responseType - Response type from query parameter
52
+ * @throws OidcError if invalid
53
+ */
54
+ export declare function validateResponseType(responseType?: string): void;
55
+ /**
56
+ * Map IdP error codes to user-friendly messages
57
+ *
58
+ * Maps OAuth 2.0 / OIDC error codes from the IdP to our standardized
59
+ * error format with helpful messages.
60
+ *
61
+ * @param error - Error object or string from IdP
62
+ * @returns Standardized OIDC error
63
+ */
64
+ export declare function handleProviderError(error: any): OidcError;
65
+ //# sourceMappingURL=error-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-utils.d.ts","sourceRoot":"","sources":["../../src/utils/error-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEjD;;GAEG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;CAoCjB,CAAC;AAEX;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,GAAG,SAAS,CAgBvF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CASvD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAMhE;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,GAAG,GAAG,SAAS,CAsDzD"}
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.OidcErrorCodes = void 0;
4
+ exports.createOidcError = createOidcError;
5
+ exports.validateProvider = validateProvider;
6
+ exports.validateResponseType = validateResponseType;
7
+ exports.handleProviderError = handleProviderError;
8
+ /**
9
+ * Standard OIDC error codes
10
+ */
11
+ exports.OidcErrorCodes = {
12
+ // Configuration errors
13
+ INVALID_PROVIDER: "invalid_provider",
14
+ PROVIDER_NOT_CONFIGURED: "provider_not_configured",
15
+ DISCOVERY_FAILED: "discovery_failed",
16
+ // Request validation errors
17
+ MISSING_CODE: "missing_code",
18
+ MISSING_STATE: "missing_state",
19
+ INVALID_STATE: "invalid_state",
20
+ INVALID_RESPONSE_TYPE: "invalid_response_type",
21
+ // Session errors
22
+ SESSION_NOT_FOUND: "session_not_found",
23
+ SESSION_EXPIRED: "session_expired",
24
+ // Token exchange errors
25
+ TOKEN_EXCHANGE_FAILED: "token_exchange_failed",
26
+ INVALID_TOKEN: "invalid_token",
27
+ ID_TOKEN_VALIDATION_FAILED: "id_token_validation_failed",
28
+ // User/Profile errors
29
+ USERINFO_FAILED: "userinfo_failed",
30
+ PROFILE_EXTRACTION_FAILED: "profile_extraction_failed",
31
+ // JWT generation errors
32
+ JWT_GENERATION_FAILED: "jwt_generation_failed",
33
+ // IdP errors (from authorization endpoint)
34
+ ACCESS_DENIED: "access_denied",
35
+ UNAUTHORIZED_CLIENT: "unauthorized_client",
36
+ INVALID_REQUEST: "invalid_request",
37
+ UNSUPPORTED_RESPONSE_TYPE: "unsupported_response_type",
38
+ INVALID_SCOPE: "invalid_scope",
39
+ SERVER_ERROR: "server_error",
40
+ TEMPORARILY_UNAVAILABLE: "temporarily_unavailable",
41
+ };
42
+ /**
43
+ * Create a standardized OIDC error object
44
+ *
45
+ * @param code - Error code from OidcErrorCodes
46
+ * @param message - Human-readable error message
47
+ * @param details - Additional error details for debugging
48
+ * @returns OIDC error object
49
+ */
50
+ function createOidcError(code, message, details) {
51
+ const error = {
52
+ code,
53
+ message,
54
+ };
55
+ if (details) {
56
+ error.details = details;
57
+ }
58
+ // Make it throwable
59
+ const throwableError = new Error(message);
60
+ throwableError.code = code;
61
+ throwableError.details = details;
62
+ return throwableError;
63
+ }
64
+ /**
65
+ * Validate provider name format
66
+ *
67
+ * Provider names must be alphanumeric with optional hyphens/underscores
68
+ * to ensure they work correctly in URLs.
69
+ *
70
+ * @param provider - Provider name to validate
71
+ * @throws OidcError if invalid
72
+ */
73
+ function validateProvider(provider) {
74
+ if (!provider) {
75
+ throw createOidcError(exports.OidcErrorCodes.INVALID_PROVIDER, "Provider name is required", { provider });
76
+ }
77
+ // Allow alphanumeric, hyphens, underscores
78
+ if (!/^[a-zA-Z0-9_-]+$/.test(provider)) {
79
+ throw createOidcError(exports.OidcErrorCodes.INVALID_PROVIDER, "Provider name must be alphanumeric (hyphens and underscores allowed)", { provider });
80
+ }
81
+ }
82
+ /**
83
+ * Validate response type parameter
84
+ *
85
+ * @param responseType - Response type from query parameter
86
+ * @throws OidcError if invalid
87
+ */
88
+ function validateResponseType(responseType) {
89
+ if (responseType && responseType !== "json") {
90
+ throw createOidcError(exports.OidcErrorCodes.INVALID_RESPONSE_TYPE, 'Invalid response_type. Must be "json" or omitted for redirect', {
91
+ responseType,
92
+ });
93
+ }
94
+ }
95
+ /**
96
+ * Map IdP error codes to user-friendly messages
97
+ *
98
+ * Maps OAuth 2.0 / OIDC error codes from the IdP to our standardized
99
+ * error format with helpful messages.
100
+ *
101
+ * @param error - Error object or string from IdP
102
+ * @returns Standardized OIDC error
103
+ */
104
+ function handleProviderError(error) {
105
+ const errorCode = typeof error === "string" ? error : error.error || error.code;
106
+ const errorDescription = error.error_description || error.message;
107
+ // Map common OAuth/OIDC errors
108
+ switch (errorCode) {
109
+ case "access_denied":
110
+ return createOidcError(exports.OidcErrorCodes.ACCESS_DENIED, "User denied authorization", {
111
+ originalError: errorCode,
112
+ description: errorDescription,
113
+ });
114
+ case "unauthorized_client":
115
+ return createOidcError(exports.OidcErrorCodes.UNAUTHORIZED_CLIENT, "Client not authorized for this request", {
116
+ originalError: errorCode,
117
+ description: errorDescription,
118
+ });
119
+ case "invalid_request":
120
+ return createOidcError(exports.OidcErrorCodes.INVALID_REQUEST, "Invalid authorization request", {
121
+ originalError: errorCode,
122
+ description: errorDescription,
123
+ });
124
+ case "unsupported_response_type":
125
+ return createOidcError(exports.OidcErrorCodes.UNSUPPORTED_RESPONSE_TYPE, "Response type not supported by IdP", {
126
+ originalError: errorCode,
127
+ description: errorDescription,
128
+ });
129
+ case "invalid_scope":
130
+ return createOidcError(exports.OidcErrorCodes.INVALID_SCOPE, "Invalid or unsupported scope", {
131
+ originalError: errorCode,
132
+ description: errorDescription,
133
+ });
134
+ case "server_error":
135
+ return createOidcError(exports.OidcErrorCodes.SERVER_ERROR, "IdP server error", {
136
+ originalError: errorCode,
137
+ description: errorDescription,
138
+ });
139
+ case "temporarily_unavailable":
140
+ return createOidcError(exports.OidcErrorCodes.TEMPORARILY_UNAVAILABLE, "IdP temporarily unavailable", {
141
+ originalError: errorCode,
142
+ description: errorDescription,
143
+ });
144
+ default:
145
+ return createOidcError(exports.OidcErrorCodes.SERVER_ERROR, errorDescription || "Unknown IdP error", {
146
+ originalError: errorCode,
147
+ description: errorDescription,
148
+ });
149
+ }
150
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Format token response for the client
3
+ *
4
+ * Supports two response formats:
5
+ * 1. JSON response (for API clients)
6
+ * 2. Redirect with token in URL fragment (for web browsers)
7
+ *
8
+ * URL fragments are used for security - they are NOT sent to the server
9
+ * in HTTP requests and are only accessible to client-side JavaScript.
10
+ *
11
+ * @param token - JWT token for the application
12
+ * @param user - User object
13
+ * @param redirectUrl - URL to redirect to
14
+ * @param responseType - "json" or undefined (redirect)
15
+ * @returns Flink response object
16
+ */
17
+ export declare function formatTokenResponse(token: string, user: any, redirectUrl: string, responseType?: "json"): any;
18
+ //# sourceMappingURL=response-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-utils.d.ts","sourceRoot":"","sources":["../../src/utils/response-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,GAAG,CAwB7G"}
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatTokenResponse = formatTokenResponse;
4
+ /**
5
+ * Format token response for the client
6
+ *
7
+ * Supports two response formats:
8
+ * 1. JSON response (for API clients)
9
+ * 2. Redirect with token in URL fragment (for web browsers)
10
+ *
11
+ * URL fragments are used for security - they are NOT sent to the server
12
+ * in HTTP requests and are only accessible to client-side JavaScript.
13
+ *
14
+ * @param token - JWT token for the application
15
+ * @param user - User object
16
+ * @param redirectUrl - URL to redirect to
17
+ * @param responseType - "json" or undefined (redirect)
18
+ * @returns Flink response object
19
+ */
20
+ function formatTokenResponse(token, user, redirectUrl, responseType) {
21
+ // JSON response for API clients
22
+ if (responseType === "json") {
23
+ return {
24
+ data: {
25
+ user,
26
+ token,
27
+ },
28
+ };
29
+ }
30
+ // Redirect response for web browsers
31
+ // Token is in URL fragment (#token=...) for security
32
+ const separator = redirectUrl.includes("#") ? "&" : "#";
33
+ const tokenFragment = `token=${encodeURIComponent(token)}`;
34
+ const finalUrl = `${redirectUrl}${separator}${tokenFragment}`;
35
+ return {
36
+ status: 302,
37
+ headers: {
38
+ Location: finalUrl,
39
+ },
40
+ data: {},
41
+ };
42
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Generate cryptographically secure state parameter for CSRF protection
3
+ *
4
+ * The state parameter is used to prevent CSRF attacks by ensuring the
5
+ * callback request originated from our initiate request.
6
+ *
7
+ * @returns Random 32-byte hex string
8
+ */
9
+ export declare function generateState(): string;
10
+ /**
11
+ * Generate cryptographically secure session ID
12
+ *
13
+ * @returns Random 16-byte hex string
14
+ */
15
+ export declare function generateSessionId(): string;
16
+ /**
17
+ * Generate cryptographically secure nonce for ID token replay protection
18
+ *
19
+ * The nonce is included in the authorization request and must be present
20
+ * in the ID token claims to prevent replay attacks.
21
+ *
22
+ * @returns Random 16-byte hex string
23
+ */
24
+ export declare function generateNonce(): string;
25
+ /**
26
+ * Validate state parameter using constant-time comparison
27
+ *
28
+ * Uses timing-safe comparison to prevent timing attacks that could
29
+ * reveal information about the expected state value.
30
+ *
31
+ * @param providedState - State from callback request
32
+ * @param expectedState - State from session
33
+ * @returns true if states match, false otherwise
34
+ */
35
+ export declare function validateState(providedState: string, expectedState: string): boolean;
36
+ //# sourceMappingURL=state-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-utils.d.ts","sourceRoot":"","sources":["../../src/utils/state-utils.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAoBnF"}
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateState = generateState;
4
+ exports.generateSessionId = generateSessionId;
5
+ exports.generateNonce = generateNonce;
6
+ exports.validateState = validateState;
7
+ const crypto_1 = require("crypto");
8
+ /**
9
+ * Generate cryptographically secure state parameter for CSRF protection
10
+ *
11
+ * The state parameter is used to prevent CSRF attacks by ensuring the
12
+ * callback request originated from our initiate request.
13
+ *
14
+ * @returns Random 32-byte hex string
15
+ */
16
+ function generateState() {
17
+ return (0, crypto_1.randomBytes)(32).toString("hex");
18
+ }
19
+ /**
20
+ * Generate cryptographically secure session ID
21
+ *
22
+ * @returns Random 16-byte hex string
23
+ */
24
+ function generateSessionId() {
25
+ return (0, crypto_1.randomBytes)(16).toString("hex");
26
+ }
27
+ /**
28
+ * Generate cryptographically secure nonce for ID token replay protection
29
+ *
30
+ * The nonce is included in the authorization request and must be present
31
+ * in the ID token claims to prevent replay attacks.
32
+ *
33
+ * @returns Random 16-byte hex string
34
+ */
35
+ function generateNonce() {
36
+ return (0, crypto_1.randomBytes)(16).toString("hex");
37
+ }
38
+ /**
39
+ * Validate state parameter using constant-time comparison
40
+ *
41
+ * Uses timing-safe comparison to prevent timing attacks that could
42
+ * reveal information about the expected state value.
43
+ *
44
+ * @param providedState - State from callback request
45
+ * @param expectedState - State from session
46
+ * @returns true if states match, false otherwise
47
+ */
48
+ function validateState(providedState, expectedState) {
49
+ if (!providedState || !expectedState) {
50
+ return false;
51
+ }
52
+ // Convert to buffers for constant-time comparison
53
+ const providedBuffer = Buffer.from(providedState, "utf8");
54
+ const expectedBuffer = Buffer.from(expectedState, "utf8");
55
+ // Buffers must be same length for timingSafeEqual
56
+ if (providedBuffer.length !== expectedBuffer.length) {
57
+ return false;
58
+ }
59
+ try {
60
+ return (0, crypto_1.timingSafeEqual)(providedBuffer, expectedBuffer);
61
+ }
62
+ catch (error) {
63
+ // timingSafeEqual throws if lengths differ (shouldn't happen due to check above)
64
+ return false;
65
+ }
66
+ }