@flink-app/oidc-plugin 2.0.0-alpha.77 → 2.0.0-alpha.79

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @flink-app/oidc-plugin
2
2
 
3
+ ## 2.0.0-alpha.79
4
+
5
+ ### Patch Changes
6
+
7
+ - Expose redirectUri from session in onAuthSuccess and onAuthError callbacks, enabling multi-client redirect decisions
8
+ - @flink-app/flink@2.0.0-alpha.79
9
+ - @flink-app/jwt-auth-plugin@2.0.0-alpha.79
10
+
11
+ ## 2.0.0-alpha.78
12
+
13
+ ### Patch Changes
14
+
15
+ - Fix optional chaining on providers in handlers after making providers optional
16
+ - @flink-app/flink@2.0.0-alpha.78
17
+ - @flink-app/jwt-auth-plugin@2.0.0-alpha.78
18
+
3
19
  ## 2.0.0-alpha.77
4
20
 
5
21
  ### Minor Changes
@@ -158,6 +158,16 @@ export interface OidcPluginOptions<TCtx = any> {
158
158
  * Provider name (e.g., "acme", "contoso")
159
159
  */
160
160
  provider: string;
161
+ /**
162
+ * The redirectUri passed on the initiate request (stored in session)
163
+ * Use this to redirect back to the originating client application.
164
+ *
165
+ * Example:
166
+ * ```typescript
167
+ * return { user, token, redirectUrl: redirectUri || DEFAULT_REDIRECT_URL };
168
+ * ```
169
+ */
170
+ redirectUri: string;
161
171
  /**
162
172
  * OIDC tokens (only if storeTokens: true)
163
173
  * Includes accessToken, idToken, refreshToken
@@ -194,6 +204,12 @@ export interface OidcPluginOptions<TCtx = any> {
194
204
  onAuthError?: (params: {
195
205
  error: OidcError;
196
206
  provider: string;
207
+ /**
208
+ * The redirectUri from the initiate request (if available)
209
+ * May be undefined for errors that occur before session lookup
210
+ * (e.g., IdP returns an error before we can retrieve the session)
211
+ */
212
+ redirectUri?: string;
197
213
  }) => Promise<AuthErrorCallbackResponse>;
198
214
  /**
199
215
  * Dynamic provider loader callback
@@ -1 +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,CAAC,IAAI,GAAG,GAAG;IACzC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAE/C;;;;;;;;;;;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,IAAI,KACR,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IAEzF;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAEhC;;;OAGG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAC;IAEnC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B"}
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,CAAC,IAAI,GAAG,GAAG;IACzC;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAE/C;;;;;;;;;;;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;;;;;;;;WAQG;QACH,WAAW,EAAE,MAAM,CAAC;QAEpB;;;WAGG;QACH,MAAM,CAAC,EAAE,YAAY,CAAC;KACzB,EACD,GAAG,EAAE,IAAI,KACR,OAAO,CAAC,2BAA2B,CAAC,CAAC;IAE1C;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE;QACnB,KAAK,EAAE,SAAS,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;QACjB;;;;WAIG;QACH,WAAW,CAAC,EAAE,MAAM,CAAC;KACxB,KAAK,OAAO,CAAC,yBAAyB,CAAC,CAAC;IAEzC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IAEzF;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAEhC;;;OAGG;IACH,yBAAyB,CAAC,EAAE,MAAM,CAAC;IAEnC;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC5B"}
@@ -1 +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"}
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,CAmOnE,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -38,6 +38,8 @@ exports.Route = {
38
38
  const CallbackOidc = async ({ ctx, req }) => {
39
39
  const { provider } = req.params;
40
40
  const { code, state, error: oidcError, error_description, response_type } = req.query;
41
+ // Track session outside try/catch so redirectUri is available in error handling
42
+ let sessionRedirectUri;
41
43
  try {
42
44
  // Validate provider and response_type
43
45
  (0, error_utils_1.validateProvider)(provider);
@@ -45,12 +47,21 @@ const CallbackOidc = async ({ ctx, req }) => {
45
47
  // Check for OIDC provider errors (e.g., user denied access)
46
48
  if (oidcError) {
47
49
  const error = (0, error_utils_1.handleProviderError)({ error: oidcError, error_description });
50
+ // Try to retrieve session redirectUri (state may be available even on IdP errors)
51
+ if (state) {
52
+ const errorSession = await ctx.repos.oidcSessionRepo.getByState(state);
53
+ sessionRedirectUri = errorSession?.redirectUri;
54
+ if (errorSession) {
55
+ await ctx.repos.oidcSessionRepo.deleteBySessionId(errorSession.sessionId);
56
+ }
57
+ }
48
58
  // Call onAuthError callback if provided
49
59
  const { options } = ctx.plugins.oidc;
50
60
  if (options.onAuthError) {
51
61
  const errorResult = await options.onAuthError({
52
62
  error,
53
63
  provider,
64
+ redirectUri: sessionRedirectUri,
54
65
  });
55
66
  if (errorResult.redirectUrl) {
56
67
  return {
@@ -80,6 +91,8 @@ const CallbackOidc = async ({ ctx, req }) => {
80
91
  providedState: state.substring(0, 10) + "...",
81
92
  });
82
93
  }
94
+ // Track redirectUri for error handling
95
+ sessionRedirectUri = session.redirectUri;
83
96
  // Delete session immediately after validation (one-time use)
84
97
  await ctx.repos.oidcSessionRepo.deleteBySessionId(session.sessionId);
85
98
  // Get plugin options
@@ -104,6 +117,7 @@ const CallbackOidc = async ({ ctx, req }) => {
104
117
  profile,
105
118
  claims: tokenSet.claims,
106
119
  provider,
120
+ redirectUri: session.redirectUri,
107
121
  ...(options.storeTokens ? { tokens: tokenSet } : {}),
108
122
  };
109
123
  let authResult;
@@ -121,6 +135,7 @@ const CallbackOidc = async ({ ctx, req }) => {
121
135
  const errorResult = await options.onAuthError({
122
136
  error: oidcError,
123
137
  provider,
138
+ redirectUri: session.redirectUri,
124
139
  });
125
140
  if (errorResult.redirectUrl) {
126
141
  return {
@@ -140,7 +155,7 @@ const CallbackOidc = async ({ ctx, req }) => {
140
155
  // Store OIDC connection if token storage is enabled
141
156
  if (options.storeTokens && user && user._id) {
142
157
  // Get encryption secret (use provider client secret or global encryption key)
143
- const staticProviderConfig = options.providers[provider];
158
+ const staticProviderConfig = options.providers?.[provider];
144
159
  const encryptionSecret = options.encryptionKey || staticProviderConfig?.clientSecret;
145
160
  if (!encryptionSecret) {
146
161
  flink_1.log.warn("No encryption secret available, tokens will not be stored");
@@ -196,6 +211,7 @@ const CallbackOidc = async ({ ctx, req }) => {
196
211
  const errorResult = await options.onAuthError({
197
212
  error,
198
213
  provider,
214
+ redirectUri: sessionRedirectUri,
199
215
  });
200
216
  if (errorResult.redirectUrl) {
201
217
  return {
@@ -47,7 +47,7 @@ const InitiateOidc = async ({ ctx, req }) => {
47
47
  // Get plugin options
48
48
  const { options } = ctx.plugins.oidc;
49
49
  // Determine redirect URI (use provided or default to callback URL)
50
- const staticProviderConfig = options.providers[provider];
50
+ const staticProviderConfig = options.providers?.[provider];
51
51
  const finalRedirectUri = redirectUri || staticProviderConfig?.callbackUrl || `${req.protocol}://${req.get("host")}/oidc/${provider}/callback`;
52
52
  // Generate cryptographically secure parameters
53
53
  const state = (0, state_utils_1.generateState)();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flink-app/oidc-plugin",
3
- "version": "2.0.0-alpha.77",
3
+ "version": "2.0.0-alpha.79",
4
4
  "description": "Flink plugin for OIDC authentication with generic IdP support",
5
5
  "author": "joel@frost.se",
6
6
  "license": "MIT",
@@ -11,10 +11,10 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "openid-client": "^5.7.0",
14
- "@flink-app/jwt-auth-plugin": "2.0.0-alpha.77"
14
+ "@flink-app/jwt-auth-plugin": "2.0.0-alpha.79"
15
15
  },
16
16
  "peerDependencies": {
17
- "@flink-app/flink": ">=2.0.0-alpha.77",
17
+ "@flink-app/flink": ">=2.0.0-alpha.79",
18
18
  "mongodb": "^6.15.0"
19
19
  },
20
20
  "peerDependenciesMeta": {
@@ -27,9 +27,9 @@
27
27
  "@types/node": "22.13.10",
28
28
  "ts-node": "^10.9.2",
29
29
  "tsc-watch": "^4.2.9",
30
- "@flink-app/flink": "2.0.0-alpha.77",
31
- "@flink-app/test-utils": "2.0.0-alpha.77",
32
- "@flink-app/jwt-auth-plugin": "2.0.0-alpha.77"
30
+ "@flink-app/jwt-auth-plugin": "2.0.0-alpha.79",
31
+ "@flink-app/test-utils": "2.0.0-alpha.79",
32
+ "@flink-app/flink": "2.0.0-alpha.79"
33
33
  },
34
34
  "scripts": {
35
35
  "test": "jasmine-ts --config=./spec/support/jasmine.json",
@@ -172,6 +172,17 @@ export interface OidcPluginOptions<TCtx = any> {
172
172
  */
173
173
  provider: string;
174
174
 
175
+ /**
176
+ * The redirectUri passed on the initiate request (stored in session)
177
+ * Use this to redirect back to the originating client application.
178
+ *
179
+ * Example:
180
+ * ```typescript
181
+ * return { user, token, redirectUrl: redirectUri || DEFAULT_REDIRECT_URL };
182
+ * ```
183
+ */
184
+ redirectUri: string;
185
+
175
186
  /**
176
187
  * OIDC tokens (only if storeTokens: true)
177
188
  * Includes accessToken, idToken, refreshToken
@@ -208,7 +219,16 @@ export interface OidcPluginOptions<TCtx = any> {
208
219
  * }
209
220
  * ```
210
221
  */
211
- onAuthError?: (params: { error: OidcError; provider: string }) => Promise<AuthErrorCallbackResponse>;
222
+ onAuthError?: (params: {
223
+ error: OidcError;
224
+ provider: string;
225
+ /**
226
+ * The redirectUri from the initiate request (if available)
227
+ * May be undefined for errors that occur before session lookup
228
+ * (e.g., IdP returns an error before we can retrieve the session)
229
+ */
230
+ redirectUri?: string;
231
+ }) => Promise<AuthErrorCallbackResponse>;
212
232
 
213
233
  /**
214
234
  * Dynamic provider loader callback
@@ -48,6 +48,9 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
48
48
  const { provider } = req.params;
49
49
  const { code, state, error: oidcError, error_description, response_type } = req.query;
50
50
 
51
+ // Track session outside try/catch so redirectUri is available in error handling
52
+ let sessionRedirectUri: string | undefined;
53
+
51
54
  try {
52
55
  // Validate provider and response_type
53
56
  validateProvider(provider);
@@ -57,12 +60,22 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
57
60
  if (oidcError) {
58
61
  const error = handleProviderError({ error: oidcError, error_description });
59
62
 
63
+ // Try to retrieve session redirectUri (state may be available even on IdP errors)
64
+ if (state) {
65
+ const errorSession = await ctx.repos.oidcSessionRepo.getByState(state);
66
+ sessionRedirectUri = errorSession?.redirectUri;
67
+ if (errorSession) {
68
+ await ctx.repos.oidcSessionRepo.deleteBySessionId(errorSession.sessionId);
69
+ }
70
+ }
71
+
60
72
  // Call onAuthError callback if provided
61
73
  const { options } = ctx.plugins.oidc;
62
74
  if (options.onAuthError) {
63
75
  const errorResult = await options.onAuthError({
64
76
  error,
65
77
  provider,
78
+ redirectUri: sessionRedirectUri,
66
79
  });
67
80
 
68
81
  if (errorResult.redirectUrl) {
@@ -99,6 +112,9 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
99
112
  });
100
113
  }
101
114
 
115
+ // Track redirectUri for error handling
116
+ sessionRedirectUri = session.redirectUri;
117
+
102
118
  // Delete session immediately after validation (one-time use)
103
119
  await ctx.repos.oidcSessionRepo.deleteBySessionId(session.sessionId);
104
120
 
@@ -129,6 +145,7 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
129
145
  profile,
130
146
  claims: tokenSet.claims,
131
147
  provider,
148
+ redirectUri: session.redirectUri,
132
149
  ...(options.storeTokens ? { tokens: tokenSet } : {}),
133
150
  };
134
151
 
@@ -148,6 +165,7 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
148
165
  const errorResult = await options.onAuthError({
149
166
  error: oidcError,
150
167
  provider,
168
+ redirectUri: session.redirectUri,
151
169
  });
152
170
 
153
171
  if (errorResult.redirectUrl) {
@@ -172,7 +190,7 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
172
190
  // Store OIDC connection if token storage is enabled
173
191
  if (options.storeTokens && user && user._id) {
174
192
  // Get encryption secret (use provider client secret or global encryption key)
175
- const staticProviderConfig = options.providers[provider];
193
+ const staticProviderConfig = options.providers?.[provider];
176
194
  const encryptionSecret = options.encryptionKey || staticProviderConfig?.clientSecret;
177
195
 
178
196
  if (!encryptionSecret) {
@@ -231,6 +249,7 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
231
249
  const errorResult = await options.onAuthError({
232
250
  error,
233
251
  provider,
252
+ redirectUri: sessionRedirectUri,
234
253
  });
235
254
 
236
255
  if (errorResult.redirectUrl) {
@@ -61,7 +61,7 @@ const InitiateOidc: GetHandler<any, any, PathParams, InitiateRequest> = async ({
61
61
  const { options } = ctx.plugins.oidc;
62
62
 
63
63
  // Determine redirect URI (use provided or default to callback URL)
64
- const staticProviderConfig = options.providers[provider];
64
+ const staticProviderConfig = options.providers?.[provider];
65
65
  const finalRedirectUri = redirectUri || staticProviderConfig?.callbackUrl || `${req.protocol}://${req.get("host")}/oidc/${provider}/callback`;
66
66
 
67
67
  // Generate cryptographically secure parameters