@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,267 @@
1
+ import OidcProfile from "./schemas/OidcProfile";
2
+ import OidcTokenSet from "./schemas/OidcTokenSet";
3
+ import { OidcProviderConfig } from "./OidcProviderConfig";
4
+ /**
5
+ * OIDC error information passed to onAuthError callback
6
+ */
7
+ export interface OidcError {
8
+ /**
9
+ * Error code (e.g., "invalid_state", "access_denied", "token_exchange_failed")
10
+ */
11
+ code: string;
12
+ /**
13
+ * Human-readable error message
14
+ */
15
+ message: string;
16
+ /**
17
+ * Additional error details for debugging
18
+ */
19
+ details?: any;
20
+ }
21
+ /**
22
+ * Response from onAuthSuccess callback
23
+ * Must include JWT token generated by the application via jwt-auth-plugin
24
+ */
25
+ export interface AuthSuccessCallbackResponse {
26
+ /**
27
+ * User object from your application
28
+ * Should include _id field for connection storage
29
+ */
30
+ user: any;
31
+ /**
32
+ * JWT token for your application (not the OIDC tokens!)
33
+ * Generated using ctx.plugins.jwtAuth.createToken()
34
+ */
35
+ token: string;
36
+ /**
37
+ * Optional redirect URL after authentication
38
+ * Plugin will append #token=... to this URL
39
+ */
40
+ redirectUrl?: string;
41
+ }
42
+ /**
43
+ * Response from onAuthError callback
44
+ */
45
+ export interface AuthErrorCallbackResponse {
46
+ /**
47
+ * Optional redirect URL for error page
48
+ * e.g., "/login?error=authentication_failed"
49
+ */
50
+ redirectUrl?: string;
51
+ }
52
+ /**
53
+ * Configuration options for OIDC Plugin with JWT integration
54
+ *
55
+ * The plugin depends on @flink-app/jwt-auth-plugin being installed and configured.
56
+ * The onAuthSuccess callback receives the Flink context as a second parameter,
57
+ * allowing the application to generate JWT tokens using ctx.plugins.jwtAuth.createToken().
58
+ */
59
+ export interface OidcPluginOptions {
60
+ /**
61
+ * OIDC provider configurations
62
+ * Key = provider name (used in URLs: /oidc/{provider}/initiate)
63
+ * Value = provider configuration
64
+ *
65
+ * At least one provider must be configured.
66
+ *
67
+ * Example:
68
+ * {
69
+ * acme: {
70
+ * issuer: "https://idp.acme.com",
71
+ * clientId: "...",
72
+ * clientSecret: "...",
73
+ * callbackUrl: "https://myapp.com/oidc/acme/callback",
74
+ * discoveryUrl: "https://idp.acme.com/.well-known/openid-configuration"
75
+ * },
76
+ * contoso: {
77
+ * issuer: "https://login.contoso.com",
78
+ * clientId: "...",
79
+ * clientSecret: "...",
80
+ * callbackUrl: "https://myapp.com/oidc/contoso/callback"
81
+ * }
82
+ * }
83
+ */
84
+ providers: Record<string, OidcProviderConfig>;
85
+ /**
86
+ * Whether to store OIDC tokens for future API access
87
+ * If false, tokens are discarded after authentication (auth-only mode)
88
+ * If true, encrypted tokens are stored in MongoDB for later use
89
+ *
90
+ * Default: false
91
+ *
92
+ * Set to true if you need to:
93
+ * - Call IdP APIs on behalf of users
94
+ * - Access user's resources at the IdP
95
+ * - Use refresh tokens to maintain long-term access
96
+ */
97
+ storeTokens?: boolean;
98
+ /**
99
+ * Callback invoked after successful OIDC authentication
100
+ *
101
+ * Application responsibilities:
102
+ * 1. Find or create user based on OIDC profile (JIT provisioning)
103
+ * 2. Link OIDC provider to user account
104
+ * 3. Generate JWT token using ctx.plugins.jwtAuth.createToken(payload, roles)
105
+ * 4. Return user object, JWT token, and optional redirect URL
106
+ *
107
+ * @param params - OIDC profile, claims, provider name, and tokens (if storeTokens enabled)
108
+ * @param ctx - Flink context with access to repos and plugins (including jwtAuth)
109
+ * @returns User object, JWT token, and optional redirect URL
110
+ *
111
+ * Example:
112
+ * ```typescript
113
+ * onAuthSuccess: async ({ profile, claims, provider }, ctx) => {
114
+ * // Find user by OIDC subject + issuer
115
+ * let user = await ctx.repos.userRepo.getOne({
116
+ * 'oidcConnections.subject': claims.sub,
117
+ * 'oidcConnections.issuer': claims.iss
118
+ * });
119
+ *
120
+ * if (!user) {
121
+ * // JIT provisioning - create new user
122
+ * user = await ctx.repos.userRepo.create({
123
+ * email: claims.email,
124
+ * name: claims.name,
125
+ * oidcConnections: [{
126
+ * issuer: claims.iss,
127
+ * subject: claims.sub,
128
+ * provider
129
+ * }]
130
+ * });
131
+ * }
132
+ *
133
+ * // Generate JWT token
134
+ * const token = await ctx.plugins.jwtAuth.createToken(
135
+ * { userId: user._id, email: user.email },
136
+ * ['user']
137
+ * );
138
+ *
139
+ * return {
140
+ * user,
141
+ * token,
142
+ * redirectUrl: '/dashboard'
143
+ * };
144
+ * }
145
+ * ```
146
+ */
147
+ onAuthSuccess: (params: {
148
+ /**
149
+ * Normalized user profile from OIDC claims
150
+ */
151
+ profile: OidcProfile;
152
+ /**
153
+ * Raw OIDC claims from ID token
154
+ * Contains all standard and custom claims
155
+ */
156
+ claims: Record<string, any>;
157
+ /**
158
+ * Provider name (e.g., "acme", "contoso")
159
+ */
160
+ provider: string;
161
+ /**
162
+ * OIDC tokens (only if storeTokens: true)
163
+ * Includes accessToken, idToken, refreshToken
164
+ */
165
+ tokens?: OidcTokenSet;
166
+ }, ctx: any) => Promise<AuthSuccessCallbackResponse>;
167
+ /**
168
+ * Callback invoked on OIDC authentication errors
169
+ *
170
+ * Application responsibilities:
171
+ * - Log error for debugging
172
+ * - Optionally provide redirect URL for error page
173
+ *
174
+ * @param params - Error information and provider name
175
+ * @returns Optional redirect URL for error page
176
+ *
177
+ * Example:
178
+ * ```typescript
179
+ * onAuthError: async ({ error, provider }) => {
180
+ * console.error(`OIDC error for ${provider}:`, error);
181
+ *
182
+ * if (error.code === 'access_denied') {
183
+ * return {
184
+ * redirectUrl: '/login?error=user_cancelled'
185
+ * };
186
+ * }
187
+ *
188
+ * return {
189
+ * redirectUrl: '/login?error=authentication_failed'
190
+ * };
191
+ * }
192
+ * ```
193
+ */
194
+ onAuthError?: (params: {
195
+ error: OidcError;
196
+ provider: string;
197
+ }) => Promise<AuthErrorCallbackResponse>;
198
+ /**
199
+ * Dynamic provider loader callback
200
+ *
201
+ * Called when a provider is accessed but not in the static providers config.
202
+ * Allows loading provider configurations from database at runtime.
203
+ *
204
+ * Use this for multi-tenant scenarios where each organization has different IdPs.
205
+ *
206
+ * @param providerName - Name of the provider to load
207
+ * @returns Provider configuration or null if not found
208
+ *
209
+ * Example:
210
+ * ```typescript
211
+ * providerLoader: async (providerName) => {
212
+ * const config = await ctx.repos.oidcProviderRepo.getByName(providerName);
213
+ * if (!config || !config.enabled) {
214
+ * return null;
215
+ * }
216
+ * return {
217
+ * issuer: config.issuer,
218
+ * clientId: config.clientId,
219
+ * clientSecret: decryptSecret(config.clientSecret),
220
+ * callbackUrl: config.callbackUrl,
221
+ * discoveryUrl: config.discoveryUrl,
222
+ * scope: config.scope
223
+ * };
224
+ * }
225
+ * ```
226
+ */
227
+ providerLoader?: (providerName: string) => Promise<OidcProviderConfig | null>;
228
+ /**
229
+ * Custom collection name for OIDC sessions
230
+ * Default: 'oidc_sessions'
231
+ */
232
+ sessionsCollectionName?: string;
233
+ /**
234
+ * Custom collection name for OIDC connections (if storeTokens enabled)
235
+ * Default: 'oidc_connections'
236
+ */
237
+ connectionsCollectionName?: string;
238
+ /**
239
+ * Session TTL in seconds
240
+ * Sessions are automatically cleaned up after this duration
241
+ * Default: 600 (10 minutes)
242
+ *
243
+ * This is the maximum time a user has to complete the OIDC flow
244
+ * (from /initiate to /callback). Increase if users need more time.
245
+ */
246
+ sessionTTL?: number;
247
+ /**
248
+ * Encryption key for encrypting stored OIDC tokens
249
+ * If not provided, will be derived from first configured provider's client secret
250
+ *
251
+ * Recommended: Use a dedicated encryption key from environment variables
252
+ * Must be at least 32 characters
253
+ *
254
+ * Example: process.env.OIDC_ENCRYPTION_KEY
255
+ */
256
+ encryptionKey?: string;
257
+ /**
258
+ * Whether to register OIDC routes automatically
259
+ * If false, you must manually handle OIDC flow
260
+ * Default: true
261
+ *
262
+ * Set to false if you want to implement custom route handling
263
+ * or use a custom URL structure.
264
+ */
265
+ registerRoutes?: boolean;
266
+ }
267
+ //# sourceMappingURL=OidcPluginOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OidcPluginOptions.d.ts","sourceRoot":"","sources":["../src/OidcPluginOptions.ts"],"names":[],"mappings":"AAAA,OAAO,WAAW,MAAM,uBAAuB,CAAC;AAChD,OAAO,YAAY,MAAM,wBAAwB,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,SAAS;IACtB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAEhB;;OAEG;IACH,OAAO,CAAC,EAAE,GAAG,CAAC;CACjB;AAED;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IACxC;;;OAGG;IACH,IAAI,EAAE,GAAG,CAAC;IAEV;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACtC;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IAC9B;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAE9C;;;;;;;;;;;OAWG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAgDG;IACH,aAAa,EAAE,CACX,MAAM,EAAE;QACJ;;WAEG;QACH,OAAO,EAAE,WAAW,CAAC;QAErB;;;WAGG;QACH,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAE5B;;WAEG;QACH,QAAQ,EAAE,MAAM,CAAC;QAEjB;;;WAGG;QACH,MAAM,CAAC,EAAE,YAAY,CAAC;KACzB,EACD,GAAG,EAAE,GAAG,KACP,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,SAAS,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAErG;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IAE9E;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAEhC;;;OAGG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAC;IAEnC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Configuration for a single OIDC provider
3
+ *
4
+ * Supports both OIDC discovery and manual endpoint configuration.
5
+ */
6
+ export interface OidcProviderConfig {
7
+ /**
8
+ * OIDC issuer URL
9
+ * e.g., "https://idp.acme.com" or "https://login.microsoftonline.com/{tenant}/v2.0"
10
+ * Used to validate the 'iss' claim in ID tokens
11
+ */
12
+ issuer: string;
13
+ /**
14
+ * OAuth 2.0 client ID
15
+ * Provided by the IdP when you register your application
16
+ */
17
+ clientId: string;
18
+ /**
19
+ * OAuth 2.0 client secret
20
+ * Provided by the IdP - keep this secure!
21
+ */
22
+ clientSecret: string;
23
+ /**
24
+ * Callback URL for OAuth redirect
25
+ * Must match the redirect URI registered with the IdP
26
+ * e.g., "https://myapp.com/oidc/acme/callback"
27
+ */
28
+ callbackUrl: string;
29
+ /**
30
+ * OAuth 2.0 scopes to request
31
+ * Default: ["openid", "email", "profile"]
32
+ * "openid" is required for OIDC
33
+ */
34
+ scope?: string[];
35
+ /**
36
+ * OIDC discovery URL for automatic configuration
37
+ * e.g., "https://idp.acme.com/.well-known/openid-configuration"
38
+ * If provided, endpoints will be auto-discovered
39
+ * If not provided, must specify manual endpoints below
40
+ */
41
+ discoveryUrl?: string;
42
+ /**
43
+ * Manual endpoint configuration (if not using discovery)
44
+ */
45
+ /**
46
+ * Authorization endpoint URL
47
+ * e.g., "https://idp.acme.com/authorize"
48
+ * Required if discoveryUrl not provided
49
+ */
50
+ authorizationEndpoint?: string;
51
+ /**
52
+ * Token endpoint URL
53
+ * e.g., "https://idp.acme.com/token"
54
+ * Required if discoveryUrl not provided
55
+ */
56
+ tokenEndpoint?: string;
57
+ /**
58
+ * UserInfo endpoint URL
59
+ * e.g., "https://idp.acme.com/userinfo"
60
+ * Optional - if not provided, only ID token claims will be used
61
+ */
62
+ userinfoEndpoint?: string;
63
+ /**
64
+ * JWKS (JSON Web Key Set) endpoint URL
65
+ * e.g., "https://idp.acme.com/.well-known/jwks.json"
66
+ * Required for ID token signature validation
67
+ * Required if discoveryUrl not provided
68
+ */
69
+ jwksUri?: string;
70
+ /**
71
+ * Additional custom claims to extract from ID token
72
+ * Map of app field names to OIDC claim paths
73
+ * e.g., { "department": "custom:department", "role": "custom:role" }
74
+ */
75
+ claimMapping?: Record<string, string>;
76
+ }
77
+ //# sourceMappingURL=OidcProviderConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"OidcProviderConfig.d.ts","sourceRoot":"","sources":["../src/OidcProviderConfig.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IAC/B;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAEjB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IAEH;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC"}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,38 @@
1
+ /**
2
+ * OIDC Callback Handler
3
+ *
4
+ * Handles the OIDC callback from the provider by:
5
+ * 1. Validating the state parameter to prevent CSRF attacks
6
+ * 2. Exchanging the authorization code for tokens (with PKCE validation)
7
+ * 3. Validating the ID token (signature, claims, nonce)
8
+ * 4. Fetching user profile from ID token and UserInfo endpoint
9
+ * 5. Calling the onAuthSuccess callback to create/link user and generate JWT token
10
+ * 6. Optionally storing the OIDC connection (if storeTokens enabled)
11
+ * 7. Returning the JWT token to the client (via JSON or redirect)
12
+ *
13
+ * Route: GET /oidc/:provider/callback?code=...&state=...&response_type=json
14
+ */
15
+ import { GetHandler, RouteProps } from "@flink-app/flink";
16
+ import CallbackRequest from "../schemas/CallbackRequest";
17
+ /**
18
+ * Path parameters for the handler
19
+ */
20
+ interface PathParams {
21
+ provider: string;
22
+ [key: string]: string;
23
+ }
24
+ /**
25
+ * Route configuration
26
+ * This handler is registered programmatically by the plugin
27
+ */
28
+ export declare const Route: RouteProps;
29
+ /**
30
+ * OIDC Callback Handler
31
+ *
32
+ * Completes the OIDC flow by exchanging the authorization code for tokens,
33
+ * validating the ID token, building user profile, calling the app's onAuthSuccess
34
+ * callback to generate JWT token, and returning the token to the client.
35
+ */
36
+ declare const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest>;
37
+ export default CallbackOidc;
38
+ //# sourceMappingURL=CallbackOidc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CallbackOidc.d.ts","sourceRoot":"","sources":["../../src/handlers/CallbackOidc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAc,UAAU,EAAwC,MAAM,kBAAkB,CAAC;AAC5G,OAAO,eAAe,MAAM,4BAA4B,CAAC;AAMzD;;GAEG;AACH,UAAU,UAAU;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,UAGnB,CAAC;AAEF;;;;;;GAMG;AACH,QAAA,MAAM,YAAY,EAAE,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,eAAe,CAgNnE,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+ /**
3
+ * OIDC Callback Handler
4
+ *
5
+ * Handles the OIDC callback from the provider by:
6
+ * 1. Validating the state parameter to prevent CSRF attacks
7
+ * 2. Exchanging the authorization code for tokens (with PKCE validation)
8
+ * 3. Validating the ID token (signature, claims, nonce)
9
+ * 4. Fetching user profile from ID token and UserInfo endpoint
10
+ * 5. Calling the onAuthSuccess callback to create/link user and generate JWT token
11
+ * 6. Optionally storing the OIDC connection (if storeTokens enabled)
12
+ * 7. Returning the JWT token to the client (via JSON or redirect)
13
+ *
14
+ * Route: GET /oidc/:provider/callback?code=...&state=...&response_type=json
15
+ */
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.Route = void 0;
18
+ const flink_1 = require("@flink-app/flink");
19
+ const state_utils_1 = require("../utils/state-utils");
20
+ const response_utils_1 = require("../utils/response-utils");
21
+ const encryption_utils_1 = require("../utils/encryption-utils");
22
+ const error_utils_1 = require("../utils/error-utils");
23
+ /**
24
+ * Route configuration
25
+ * This handler is registered programmatically by the plugin
26
+ */
27
+ exports.Route = {
28
+ path: "/oidc/:provider/callback",
29
+ method: flink_1.HttpMethod.get,
30
+ };
31
+ /**
32
+ * OIDC Callback Handler
33
+ *
34
+ * Completes the OIDC flow by exchanging the authorization code for tokens,
35
+ * validating the ID token, building user profile, calling the app's onAuthSuccess
36
+ * callback to generate JWT token, and returning the token to the client.
37
+ */
38
+ const CallbackOidc = async ({ ctx, req }) => {
39
+ const { provider } = req.params;
40
+ const { code, state, error: oidcError, error_description, response_type } = req.query;
41
+ try {
42
+ // Validate provider and response_type
43
+ (0, error_utils_1.validateProvider)(provider);
44
+ (0, error_utils_1.validateResponseType)(response_type);
45
+ // Check for OIDC provider errors (e.g., user denied access)
46
+ if (oidcError) {
47
+ const error = (0, error_utils_1.handleProviderError)({ error: oidcError, error_description });
48
+ // Call onAuthError callback if provided
49
+ const { options } = ctx.plugins.oidc;
50
+ if (options.onAuthError) {
51
+ const errorResult = await options.onAuthError({
52
+ error,
53
+ provider,
54
+ });
55
+ if (errorResult.redirectUrl) {
56
+ return {
57
+ status: 302,
58
+ headers: { Location: errorResult.redirectUrl },
59
+ data: {},
60
+ };
61
+ }
62
+ }
63
+ return (0, flink_1.badRequest)(error.message);
64
+ }
65
+ // Validate required parameters
66
+ if (!code || !state) {
67
+ throw (0, error_utils_1.createOidcError)(error_utils_1.OidcErrorCodes.MISSING_CODE, "Missing authorization code or state parameter", {
68
+ hasCode: !!code,
69
+ hasState: !!state,
70
+ });
71
+ }
72
+ // Find OIDC session by state
73
+ const session = await ctx.repos.oidcSessionRepo.getByState(state);
74
+ if (!session) {
75
+ throw (0, error_utils_1.createOidcError)(error_utils_1.OidcErrorCodes.SESSION_EXPIRED, "OIDC session not found or expired. Please try logging in again.", { state });
76
+ }
77
+ // Validate state parameter (CSRF protection)
78
+ if (!(0, state_utils_1.validateState)(state, session.state)) {
79
+ throw (0, error_utils_1.createOidcError)(error_utils_1.OidcErrorCodes.INVALID_STATE, "Invalid state parameter. Possible CSRF attack detected.", {
80
+ providedState: state.substring(0, 10) + "...",
81
+ });
82
+ }
83
+ // Delete session immediately after validation (one-time use)
84
+ await ctx.repos.oidcSessionRepo.deleteBySessionId(session.sessionId);
85
+ // Get plugin options
86
+ const { options } = ctx.plugins.oidc;
87
+ // Get provider instance
88
+ const providerRegistry = ctx.oidcProviderRegistry;
89
+ if (!providerRegistry) {
90
+ throw (0, error_utils_1.createOidcError)(error_utils_1.OidcErrorCodes.PROVIDER_NOT_CONFIGURED, "OIDC plugin not properly initialized");
91
+ }
92
+ const oidcProvider = await providerRegistry.getProvider(provider);
93
+ // Exchange authorization code for tokens with PKCE validation
94
+ const tokenSet = await oidcProvider.exchangeCodeForToken({
95
+ code,
96
+ codeVerifier: session.codeVerifier,
97
+ state: session.state,
98
+ nonce: session.nonce,
99
+ });
100
+ // Build user profile from ID token and UserInfo
101
+ const profile = await oidcProvider.buildProfile(tokenSet, true);
102
+ // Call onAuthSuccess callback to create/link user and generate JWT token
103
+ const authSuccessParams = {
104
+ profile,
105
+ claims: tokenSet.claims,
106
+ provider,
107
+ ...(options.storeTokens ? { tokens: tokenSet } : {}),
108
+ };
109
+ let authResult;
110
+ try {
111
+ authResult = await options.onAuthSuccess(authSuccessParams, ctx);
112
+ }
113
+ catch (error) {
114
+ // Handle JWT generation or user creation errors
115
+ flink_1.log.error("OIDC onAuthSuccess callback failed:", error);
116
+ const oidcError = (0, error_utils_1.createOidcError)(error_utils_1.OidcErrorCodes.JWT_GENERATION_FAILED, "Failed to complete authentication. Please try again.", {
117
+ originalError: error.message,
118
+ });
119
+ // Call onAuthError callback if provided
120
+ if (options.onAuthError) {
121
+ const errorResult = await options.onAuthError({
122
+ error: oidcError,
123
+ provider,
124
+ });
125
+ if (errorResult.redirectUrl) {
126
+ return {
127
+ status: 302,
128
+ headers: { Location: errorResult.redirectUrl },
129
+ data: {},
130
+ };
131
+ }
132
+ }
133
+ return (0, flink_1.internalServerError)("Authentication failed. Please try again.");
134
+ }
135
+ // Extract user and JWT token from callback result
136
+ const { user, token, redirectUrl } = authResult;
137
+ if (!token) {
138
+ throw (0, error_utils_1.createOidcError)(error_utils_1.OidcErrorCodes.JWT_GENERATION_FAILED, "No authentication token returned from callback", { hasUser: !!user });
139
+ }
140
+ // Store OIDC connection if token storage is enabled
141
+ if (options.storeTokens && user && user._id) {
142
+ // Get encryption secret (use provider client secret or global encryption key)
143
+ const staticProviderConfig = options.providers[provider];
144
+ const encryptionSecret = options.encryptionKey || staticProviderConfig?.clientSecret;
145
+ if (!encryptionSecret) {
146
+ flink_1.log.warn("No encryption secret available, tokens will not be stored");
147
+ }
148
+ else {
149
+ // Encrypt tokens before storing
150
+ const encryptedAccessToken = (0, encryption_utils_1.encryptToken)(tokenSet.accessToken, encryptionSecret);
151
+ const encryptedIdToken = (0, encryption_utils_1.encryptToken)(tokenSet.idToken, encryptionSecret);
152
+ const encryptedRefreshToken = tokenSet.refreshToken ? (0, encryption_utils_1.encryptToken)(tokenSet.refreshToken, encryptionSecret) : undefined;
153
+ // Calculate token expiration
154
+ const expiresAt = tokenSet.expiresIn ? new Date(Date.now() + tokenSet.expiresIn * 1000) : undefined;
155
+ // Create or update OIDC connection
156
+ const existingConnection = await ctx.repos.oidcConnectionRepo.findByUserAndProvider(user._id, provider);
157
+ if (existingConnection) {
158
+ await ctx.repos.oidcConnectionRepo.updateOne(existingConnection._id, {
159
+ accessToken: encryptedAccessToken,
160
+ idToken: encryptedIdToken,
161
+ refreshToken: encryptedRefreshToken,
162
+ scope: tokenSet.scope || "",
163
+ expiresAt,
164
+ updatedAt: new Date(),
165
+ });
166
+ }
167
+ else {
168
+ await ctx.repos.oidcConnectionRepo.create({
169
+ userId: user._id,
170
+ provider,
171
+ subject: tokenSet.claims.sub,
172
+ issuer: tokenSet.claims.iss,
173
+ email: profile.email,
174
+ accessToken: encryptedAccessToken,
175
+ idToken: encryptedIdToken,
176
+ refreshToken: encryptedRefreshToken,
177
+ scope: tokenSet.scope || "",
178
+ expiresAt,
179
+ createdAt: new Date(),
180
+ updatedAt: new Date(),
181
+ });
182
+ }
183
+ }
184
+ }
185
+ // Return JWT token in requested format
186
+ return (0, response_utils_1.formatTokenResponse)(token, user, redirectUrl || session.redirectUri, response_type);
187
+ }
188
+ catch (error) {
189
+ flink_1.log.error("OIDC callback error:", error);
190
+ // Handle OIDC-specific errors
191
+ if (error.code && Object.values(error_utils_1.OidcErrorCodes).includes(error.code)) {
192
+ // Call onAuthError callback if provided
193
+ const { options } = ctx.plugins.oidc;
194
+ if (options.onAuthError) {
195
+ try {
196
+ const errorResult = await options.onAuthError({
197
+ error,
198
+ provider,
199
+ });
200
+ if (errorResult.redirectUrl) {
201
+ return {
202
+ status: 302,
203
+ headers: { Location: errorResult.redirectUrl },
204
+ data: {},
205
+ };
206
+ }
207
+ }
208
+ catch (callbackError) {
209
+ flink_1.log.error("onAuthError callback failed:", callbackError);
210
+ }
211
+ }
212
+ return (0, flink_1.badRequest)(error.message);
213
+ }
214
+ // Handle provider errors
215
+ const mappedError = (0, error_utils_1.handleProviderError)(error);
216
+ return (0, flink_1.internalServerError)(mappedError.message);
217
+ }
218
+ };
219
+ exports.default = CallbackOidc;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * OIDC Initiate Handler
3
+ *
4
+ * Initiates the OIDC authorization code flow by:
5
+ * 1. Validating the provider is supported and configured
6
+ * 2. Generating cryptographically secure state, code_verifier, and nonce
7
+ * 3. Creating an OIDC session to track the flow
8
+ * 4. Building the provider's authorization URL with PKCE
9
+ * 5. Redirecting the user to the provider for authorization
10
+ *
11
+ * Route: GET /oidc/:provider/initiate?redirectUri={optional_redirect_url}
12
+ */
13
+ import { GetHandler, RouteProps } from "@flink-app/flink";
14
+ import InitiateRequest from "../schemas/InitiateRequest";
15
+ /**
16
+ * Path parameters for the handler
17
+ */
18
+ interface PathParams {
19
+ provider: string;
20
+ [key: string]: string;
21
+ }
22
+ /**
23
+ * Route configuration
24
+ * This handler is registered programmatically by the plugin
25
+ */
26
+ export declare const Route: RouteProps;
27
+ /**
28
+ * OIDC Initiate Handler
29
+ *
30
+ * Starts the OIDC flow by generating security parameters, creating a session,
31
+ * and redirecting to the OIDC provider's authorization URL.
32
+ */
33
+ declare const InitiateOidc: GetHandler<any, any, PathParams, InitiateRequest>;
34
+ export default InitiateOidc;
35
+ //# sourceMappingURL=InitiateOidc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"InitiateOidc.d.ts","sourceRoot":"","sources":["../../src/handlers/InitiateOidc.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,UAAU,EAAc,UAAU,EAAmC,MAAM,kBAAkB,CAAC;AACvG,OAAO,eAAe,MAAM,4BAA4B,CAAC;AAKzD;;GAEG;AACH,UAAU,UAAU;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,UAGnB,CAAC;AAEF;;;;;GAKG;AACH,QAAA,MAAM,YAAY,EAAE,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,eAAe,CAiEnE,CAAC;AAEF,eAAe,YAAY,CAAC"}