@flink-app/oidc-plugin 2.0.0-alpha.80 → 2.0.0-alpha.82

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,20 @@
1
1
  # @flink-app/oidc-plugin
2
2
 
3
+ ## 2.0.0-alpha.82
4
+
5
+ ### Patch Changes
6
+
7
+ - Add metadata passthrough support. Any query parameters prefixed with `meta.` on the initiate endpoint (e.g. `?meta.intent=admin&meta.source=mobile`) are collected, stripped of the prefix, stored in the session, and delivered to both `onAuthSuccess` and `onAuthError` callbacks as `metadata: Record<string, string>`. Empty object when no meta params are provided.
8
+ - @flink-app/flink@2.0.0-alpha.82
9
+ - @flink-app/jwt-auth-plugin@2.0.0-alpha.82
10
+
11
+ ## 2.0.0-alpha.81
12
+
13
+ ### Patch Changes
14
+
15
+ - @flink-app/flink@2.0.0-alpha.81
16
+ - @flink-app/jwt-auth-plugin@2.0.0-alpha.81
17
+
3
18
  ## 2.0.0-alpha.80
4
19
 
5
20
  ### Patch Changes
@@ -110,7 +110,7 @@ export interface OidcPluginOptions<TCtx = any> {
110
110
  *
111
111
  * Example:
112
112
  * ```typescript
113
- * onAuthSuccess: async ({ profile, claims, provider }, ctx) => {
113
+ * onAuthSuccess: async ({ profile, claims, provider, metadata }, ctx) => {
114
114
  * // Find user by OIDC subject + issuer
115
115
  * let user = await ctx.repos.userRepo.getOne({
116
116
  * 'oidcConnections.subject': claims.sub,
@@ -168,6 +168,16 @@ export interface OidcPluginOptions<TCtx = any> {
168
168
  * ```
169
169
  */
170
170
  redirectUri: string;
171
+ /**
172
+ * Arbitrary metadata collected from meta.* query parameters on the initiate request.
173
+ * Opaque to the plugin — it only transports these values.
174
+ *
175
+ * Example: GET /oidc/acme/initiate?meta.intent=admin&meta.source=mobile
176
+ * Produces: { intent: "admin", source: "mobile" }
177
+ *
178
+ * Empty object ({}) if no meta.* params were provided.
179
+ */
180
+ metadata: Record<string, string>;
171
181
  /**
172
182
  * OIDC tokens (only if storeTokens: true)
173
183
  * Includes accessToken, idToken, refreshToken
@@ -210,6 +220,11 @@ export interface OidcPluginOptions<TCtx = any> {
210
220
  * (e.g., IdP returns an error before we can retrieve the session)
211
221
  */
212
222
  redirectUri?: string;
223
+ /**
224
+ * Metadata from the initiate request (meta.* query params, prefix stripped).
225
+ * Empty object ({}) if no meta.* params were provided or session was not found.
226
+ */
227
+ metadata: Record<string, string>;
213
228
  }) => Promise<AuthErrorCallbackResponse>;
214
229
  /**
215
230
  * 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;;;;;;;;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
+ {"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;;;;;;;;WAQG;QACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAEjC;;;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;QACrB;;;WAGG;QACH,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACpC,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;AAOzD;;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,CA4PnE,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;AAOzD;;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,CAqQnE,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -39,8 +39,9 @@ exports.Route = {
39
39
  const CallbackOidc = async ({ ctx, req }) => {
40
40
  const { provider } = req.params;
41
41
  const { code, state, error: oidcError, error_description, response_type } = req.query;
42
- // Track session outside try/catch so redirectUri is available in error handling
42
+ // Track session outside try/catch so redirectUri and metadata are available in error handling
43
43
  let sessionRedirectUri;
44
+ let sessionMetadata = {};
44
45
  try {
45
46
  // Validate provider and response_type
46
47
  (0, error_utils_1.validateProvider)(provider);
@@ -54,6 +55,7 @@ const CallbackOidc = async ({ ctx, req }) => {
54
55
  if (state) {
55
56
  const errorSession = await ctx.repos.oidcSessionRepo.getByState(state);
56
57
  sessionRedirectUri = errorSession?.redirectUri;
58
+ sessionMetadata = errorSession?.metadata ?? {};
57
59
  if (errorSession) {
58
60
  await ctx.repos.oidcSessionRepo.deleteBySessionId(errorSession.sessionId);
59
61
  }
@@ -65,6 +67,7 @@ const CallbackOidc = async ({ ctx, req }) => {
65
67
  error,
66
68
  provider,
67
69
  redirectUri: sessionRedirectUri,
70
+ metadata: sessionMetadata,
68
71
  });
69
72
  if (errorResult.redirectUrl) {
70
73
  return {
@@ -96,8 +99,9 @@ const CallbackOidc = async ({ ctx, req }) => {
96
99
  providedState: state.substring(0, 10) + "...",
97
100
  });
98
101
  }
99
- // Track redirectUri for error handling
102
+ // Track redirectUri and metadata for error handling
100
103
  sessionRedirectUri = session.redirectUri;
104
+ sessionMetadata = session.metadata ?? {};
101
105
  // Delete session immediately after validation (one-time use)
102
106
  await ctx.repos.oidcSessionRepo.deleteBySessionId(session.sessionId);
103
107
  log_1.oidcLog.debug(`Callback: state validated, session deleted (one-time use)`);
@@ -129,6 +133,7 @@ const CallbackOidc = async ({ ctx, req }) => {
129
133
  claims: tokenSet.claims,
130
134
  provider,
131
135
  redirectUri: session.redirectUri,
136
+ metadata: sessionMetadata,
132
137
  ...(options.storeTokens ? { tokens: tokenSet } : {}),
133
138
  };
134
139
  let authResult;
@@ -147,6 +152,7 @@ const CallbackOidc = async ({ ctx, req }) => {
147
152
  error: oidcError,
148
153
  provider,
149
154
  redirectUri: session.redirectUri,
155
+ metadata: sessionMetadata,
150
156
  });
151
157
  if (errorResult.redirectUrl) {
152
158
  return {
@@ -182,6 +188,7 @@ const CallbackOidc = async ({ ctx, req }) => {
182
188
  // Create or update OIDC connection
183
189
  const existingConnection = await ctx.repos.oidcConnectionRepo.findByUserAndProvider(user._id, provider);
184
190
  if (existingConnection) {
191
+ log_1.oidcLog.debug(`Callback: updating existing connection for userId=${user._id} provider="${provider}"`);
185
192
  await ctx.repos.oidcConnectionRepo.updateById(existingConnection._id, {
186
193
  accessToken: encryptedAccessToken,
187
194
  idToken: encryptedIdToken,
@@ -192,6 +199,7 @@ const CallbackOidc = async ({ ctx, req }) => {
192
199
  });
193
200
  }
194
201
  else {
202
+ log_1.oidcLog.debug(`Callback: creating new connection for userId=${user._id} provider="${provider}" subject="${tokenSet.claims.sub}"`);
195
203
  await ctx.repos.oidcConnectionRepo.create({
196
204
  userId: user._id,
197
205
  provider,
@@ -226,6 +234,7 @@ const CallbackOidc = async ({ ctx, req }) => {
226
234
  error,
227
235
  provider,
228
236
  redirectUri: sessionRedirectUri,
237
+ metadata: sessionMetadata,
229
238
  });
230
239
  if (errorResult.redirectUrl) {
231
240
  return {
@@ -1 +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;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;;;;;GAKG;AACH,QAAA,MAAM,YAAY,EAAE,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,eAAe,CAyEnE,CAAC;AAEF,eAAe,YAAY,CAAC"}
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;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;;;;;GAKG;AACH,QAAA,MAAM,YAAY,EAAE,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,eAAe,CAkFnE,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -51,6 +51,13 @@ const InitiateOidc = async ({ ctx, req }) => {
51
51
  // Determine redirect URI (use provided or default to callback URL)
52
52
  const staticProviderConfig = options.providers?.[provider];
53
53
  const finalRedirectUri = redirectUri || staticProviderConfig?.callbackUrl || `${req.protocol}://${req.get("host")}/oidc/${provider}/callback`;
54
+ // Collect meta.* query params as opaque metadata (prefix stripped)
55
+ const metadata = {};
56
+ for (const [key, value] of Object.entries(req.query)) {
57
+ if (key.startsWith("meta.") && typeof value === "string") {
58
+ metadata[key.slice(5)] = value;
59
+ }
60
+ }
54
61
  log_1.oidcLog.debug(`Initiate: resolved redirectUri="${finalRedirectUri}" (source: ${redirectUri ? "query param" : staticProviderConfig?.callbackUrl ? "provider config" : "auto-detected"})`);
55
62
  // Generate cryptographically secure parameters
56
63
  const state = (0, state_utils_1.generateState)();
@@ -65,6 +72,7 @@ const InitiateOidc = async ({ ctx, req }) => {
65
72
  nonce,
66
73
  provider,
67
74
  redirectUri: finalRedirectUri,
75
+ metadata,
68
76
  createdAt: new Date(),
69
77
  });
70
78
  log_1.oidcLog.debug(`Initiate: session created sessionId=${sessionId}`);
@@ -74,7 +82,7 @@ const InitiateOidc = async ({ ctx, req }) => {
74
82
  codeVerifier,
75
83
  nonce,
76
84
  });
77
- log_1.oidcLog.debug(`Initiate: redirecting to IdP authorization URL`);
85
+ log_1.oidcLog.debug(`Initiate: redirecting to IdP authorization URL: ${authorizationUrl}`);
78
86
  // Redirect user to provider's authorization page
79
87
  return {
80
88
  status: 302,
@@ -1 +1 @@
1
- {"version":3,"file":"OidcProvider.d.ts","sourceRoot":"","sources":["../../src/providers/OidcProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwD,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,WAAW,MAAM,wBAAwB,CAAC;AACjD,OAAO,YAAY,MAAM,yBAAyB,CAAC;AAInD;;;;;;;;;;GAUG;AACH,qBAAa,YAAY;IACrB,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAkB;gBAEzB,MAAM,EAAE,kBAAkB;IAItC;;;;;;;OAOG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA0DjC;;;;;OAKG;IACG,mBAAmB,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAiB1G;;;;;;;OAOG;IACG,oBAAoB,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IAqC/H;;;;;;;;OAQG;IACG,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAajE;;;;;;;;;OASG;IACG,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,eAAe,GAAE,OAAc,GAAG,OAAO,CAAC,WAAW,CAAC;IA2BjG;;;;OAIG;YACW,iBAAiB;IAU/B;;;;OAIG;IACH,iBAAiB,IAAI,GAAG;CAM3B"}
1
+ {"version":3,"file":"OidcProvider.d.ts","sourceRoot":"","sources":["../../src/providers/OidcProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwD,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,WAAW,MAAM,wBAAwB,CAAC;AACjD,OAAO,YAAY,MAAM,yBAAyB,CAAC;AAKnD;;;;;;;;;;GAUG;AACH,qBAAa,YAAY;IACrB,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAkB;gBAEzB,MAAM,EAAE,kBAAkB;IAItC;;;;;;;OAOG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAkEjC;;;;;OAKG;IACG,mBAAmB,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAiB1G;;;;;;;OAOG;IACG,oBAAoB,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IAqC/H;;;;;;;;OAQG;IACG,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAajE;;;;;;;;;OASG;IACG,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,eAAe,GAAE,OAAc,GAAG,OAAO,CAAC,WAAW,CAAC;IA2BjG;;;;OAIG;YACW,iBAAiB;IAU/B;;;;OAIG;IACH,iBAAiB,IAAI,GAAG;CAM3B"}
@@ -4,6 +4,7 @@ exports.OidcProvider = void 0;
4
4
  const openid_client_1 = require("openid-client");
5
5
  const claims_mapper_1 = require("../utils/claims-mapper");
6
6
  const error_utils_1 = require("../utils/error-utils");
7
+ const log_1 = require("../log");
7
8
  /**
8
9
  * Generic OIDC Provider implementation using openid-client
9
10
  *
@@ -37,7 +38,9 @@ class OidcProvider {
37
38
  try {
38
39
  // Option 1: OIDC Discovery
39
40
  if (this.config.discoveryUrl) {
41
+ log_1.oidcLog.debug(`Provider "${this.config.issuer}": discovering from ${this.config.discoveryUrl}`);
40
42
  this.issuer = await openid_client_1.Issuer.discover(this.config.discoveryUrl);
43
+ log_1.oidcLog.debug(`Provider "${this.config.issuer}": discovery complete`, `authorization_endpoint=${this.issuer.metadata.authorization_endpoint}`, `token_endpoint=${this.issuer.metadata.token_endpoint}`, `userinfo_endpoint=${this.issuer.metadata.userinfo_endpoint ?? "(none)"}`, `jwks_uri=${this.issuer.metadata.jwks_uri}`);
41
44
  }
42
45
  // Option 2: Manual configuration
43
46
  else {
@@ -178,7 +181,7 @@ class OidcProvider {
178
181
  }
179
182
  catch (error) {
180
183
  // UserInfo is optional - continue with ID token claims only
181
- console.warn("Failed to fetch UserInfo, using ID token claims only:", error);
184
+ log_1.oidcLog.warn(`Failed to fetch UserInfo from ${this.config.userinfoEndpoint}, using ID token claims only:`, error);
182
185
  }
183
186
  }
184
187
  // Apply custom claim mapping if configured
@@ -37,6 +37,11 @@ export default interface OidcSession {
37
37
  * Can be overridden by the client via query parameter
38
38
  */
39
39
  redirectUri: string;
40
+ /**
41
+ * Arbitrary metadata collected from meta.* query parameters on initiate
42
+ * Passed through to onAuthSuccess and onAuthError callbacks unchanged
43
+ */
44
+ metadata?: Record<string, string>;
40
45
  /**
41
46
  * Session creation timestamp
42
47
  * MongoDB TTL index will automatically delete expired sessions
@@ -1 +1 @@
1
- {"version":3,"file":"OidcSession.d.ts","sourceRoot":"","sources":["../../src/schemas/OidcSession.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,OAAO,WAAW,WAAW;IAChC;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,SAAS,EAAE,IAAI,CAAC;CACnB"}
1
+ {"version":3,"file":"OidcSession.d.ts","sourceRoot":"","sources":["../../src/schemas/OidcSession.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,OAAO,WAAW,WAAW;IAChC;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,SAAS,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAElC;;;OAGG;IACH,SAAS,EAAE,IAAI,CAAC;CACnB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flink-app/oidc-plugin",
3
- "version": "2.0.0-alpha.80",
3
+ "version": "2.0.0-alpha.82",
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.80"
14
+ "@flink-app/jwt-auth-plugin": "2.0.0-alpha.82"
15
15
  },
16
16
  "peerDependencies": {
17
- "@flink-app/flink": ">=2.0.0-alpha.80",
17
+ "@flink-app/flink": ">=2.0.0-alpha.82",
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/jwt-auth-plugin": "2.0.0-alpha.80",
31
- "@flink-app/test-utils": "2.0.0-alpha.80",
32
- "@flink-app/flink": "2.0.0-alpha.80"
30
+ "@flink-app/flink": "2.0.0-alpha.82",
31
+ "@flink-app/jwt-auth-plugin": "2.0.0-alpha.82",
32
+ "@flink-app/test-utils": "2.0.0-alpha.82"
33
33
  },
34
34
  "scripts": {
35
35
  "test": "jasmine-ts --config=./spec/support/jasmine.json",
@@ -120,7 +120,7 @@ export interface OidcPluginOptions<TCtx = any> {
120
120
  *
121
121
  * Example:
122
122
  * ```typescript
123
- * onAuthSuccess: async ({ profile, claims, provider }, ctx) => {
123
+ * onAuthSuccess: async ({ profile, claims, provider, metadata }, ctx) => {
124
124
  * // Find user by OIDC subject + issuer
125
125
  * let user = await ctx.repos.userRepo.getOne({
126
126
  * 'oidcConnections.subject': claims.sub,
@@ -183,6 +183,17 @@ export interface OidcPluginOptions<TCtx = any> {
183
183
  */
184
184
  redirectUri: string;
185
185
 
186
+ /**
187
+ * Arbitrary metadata collected from meta.* query parameters on the initiate request.
188
+ * Opaque to the plugin — it only transports these values.
189
+ *
190
+ * Example: GET /oidc/acme/initiate?meta.intent=admin&meta.source=mobile
191
+ * Produces: { intent: "admin", source: "mobile" }
192
+ *
193
+ * Empty object ({}) if no meta.* params were provided.
194
+ */
195
+ metadata: Record<string, string>;
196
+
186
197
  /**
187
198
  * OIDC tokens (only if storeTokens: true)
188
199
  * Includes accessToken, idToken, refreshToken
@@ -228,6 +239,11 @@ export interface OidcPluginOptions<TCtx = any> {
228
239
  * (e.g., IdP returns an error before we can retrieve the session)
229
240
  */
230
241
  redirectUri?: string;
242
+ /**
243
+ * Metadata from the initiate request (meta.* query params, prefix stripped).
244
+ * Empty object ({}) if no meta.* params were provided or session was not found.
245
+ */
246
+ metadata: Record<string, string>;
231
247
  }) => Promise<AuthErrorCallbackResponse>;
232
248
 
233
249
  /**
@@ -49,8 +49,9 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
49
49
  const { provider } = req.params;
50
50
  const { code, state, error: oidcError, error_description, response_type } = req.query;
51
51
 
52
- // Track session outside try/catch so redirectUri is available in error handling
52
+ // Track session outside try/catch so redirectUri and metadata are available in error handling
53
53
  let sessionRedirectUri: string | undefined;
54
+ let sessionMetadata: Record<string, string> = {};
54
55
 
55
56
  try {
56
57
  // Validate provider and response_type
@@ -68,6 +69,7 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
68
69
  if (state) {
69
70
  const errorSession = await ctx.repos.oidcSessionRepo.getByState(state);
70
71
  sessionRedirectUri = errorSession?.redirectUri;
72
+ sessionMetadata = errorSession?.metadata ?? {};
71
73
  if (errorSession) {
72
74
  await ctx.repos.oidcSessionRepo.deleteBySessionId(errorSession.sessionId);
73
75
  }
@@ -80,6 +82,7 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
80
82
  error,
81
83
  provider,
82
84
  redirectUri: sessionRedirectUri,
85
+ metadata: sessionMetadata,
83
86
  });
84
87
 
85
88
  if (errorResult.redirectUrl) {
@@ -119,8 +122,9 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
119
122
  });
120
123
  }
121
124
 
122
- // Track redirectUri for error handling
125
+ // Track redirectUri and metadata for error handling
123
126
  sessionRedirectUri = session.redirectUri;
127
+ sessionMetadata = session.metadata ?? {};
124
128
 
125
129
  // Delete session immediately after validation (one-time use)
126
130
  await ctx.repos.oidcSessionRepo.deleteBySessionId(session.sessionId);
@@ -167,6 +171,7 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
167
171
  claims: tokenSet.claims,
168
172
  provider,
169
173
  redirectUri: session.redirectUri,
174
+ metadata: sessionMetadata,
170
175
  ...(options.storeTokens ? { tokens: tokenSet } : {}),
171
176
  };
172
177
 
@@ -187,6 +192,7 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
187
192
  error: oidcError,
188
193
  provider,
189
194
  redirectUri: session.redirectUri,
195
+ metadata: sessionMetadata,
190
196
  });
191
197
 
192
198
  if (errorResult.redirectUrl) {
@@ -231,6 +237,7 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
231
237
  const existingConnection = await ctx.repos.oidcConnectionRepo.findByUserAndProvider(user._id, provider);
232
238
 
233
239
  if (existingConnection) {
240
+ oidcLog.debug(`Callback: updating existing connection for userId=${user._id} provider="${provider}"`);
234
241
  await ctx.repos.oidcConnectionRepo.updateById(existingConnection._id!, {
235
242
  accessToken: encryptedAccessToken,
236
243
  idToken: encryptedIdToken,
@@ -240,6 +247,7 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
240
247
  updatedAt: new Date(),
241
248
  });
242
249
  } else {
250
+ oidcLog.debug(`Callback: creating new connection for userId=${user._id} provider="${provider}" subject="${tokenSet.claims.sub}"`);
243
251
  await ctx.repos.oidcConnectionRepo.create({
244
252
  userId: user._id,
245
253
  provider,
@@ -276,6 +284,7 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
276
284
  error,
277
285
  provider,
278
286
  redirectUri: sessionRedirectUri,
287
+ metadata: sessionMetadata,
279
288
  });
280
289
 
281
290
  if (errorResult.redirectUrl) {
@@ -67,6 +67,14 @@ const InitiateOidc: GetHandler<any, any, PathParams, InitiateRequest> = async ({
67
67
  const staticProviderConfig = options.providers?.[provider];
68
68
  const finalRedirectUri = redirectUri || staticProviderConfig?.callbackUrl || `${req.protocol}://${req.get("host")}/oidc/${provider}/callback`;
69
69
 
70
+ // Collect meta.* query params as opaque metadata (prefix stripped)
71
+ const metadata: Record<string, string> = {};
72
+ for (const [key, value] of Object.entries(req.query)) {
73
+ if (key.startsWith("meta.") && typeof value === "string") {
74
+ metadata[key.slice(5)] = value;
75
+ }
76
+ }
77
+
70
78
  oidcLog.debug(`Initiate: resolved redirectUri="${finalRedirectUri}" (source: ${redirectUri ? "query param" : staticProviderConfig?.callbackUrl ? "provider config" : "auto-detected"})`);
71
79
 
72
80
  // Generate cryptographically secure parameters
@@ -83,6 +91,7 @@ const InitiateOidc: GetHandler<any, any, PathParams, InitiateRequest> = async ({
83
91
  nonce,
84
92
  provider,
85
93
  redirectUri: finalRedirectUri,
94
+ metadata,
86
95
  createdAt: new Date(),
87
96
  });
88
97
 
@@ -95,7 +104,7 @@ const InitiateOidc: GetHandler<any, any, PathParams, InitiateRequest> = async ({
95
104
  nonce,
96
105
  });
97
106
 
98
- oidcLog.debug(`Initiate: redirecting to IdP authorization URL`);
107
+ oidcLog.debug(`Initiate: redirecting to IdP authorization URL: ${authorizationUrl}`);
99
108
 
100
109
  // Redirect user to provider's authorization page
101
110
  return {
@@ -4,6 +4,7 @@ import OidcProfile from "../schemas/OidcProfile";
4
4
  import OidcTokenSet from "../schemas/OidcTokenSet";
5
5
  import { mapClaimsToProfile, extractCustomClaims } from "../utils/claims-mapper";
6
6
  import { createOidcError, OidcErrorCodes } from "../utils/error-utils";
7
+ import { oidcLog } from "../log";
7
8
 
8
9
  /**
9
10
  * Generic OIDC Provider implementation using openid-client
@@ -42,7 +43,15 @@ export class OidcProvider {
42
43
  try {
43
44
  // Option 1: OIDC Discovery
44
45
  if (this.config.discoveryUrl) {
46
+ oidcLog.debug(`Provider "${this.config.issuer}": discovering from ${this.config.discoveryUrl}`);
45
47
  this.issuer = await Issuer.discover(this.config.discoveryUrl);
48
+ oidcLog.debug(
49
+ `Provider "${this.config.issuer}": discovery complete`,
50
+ `authorization_endpoint=${this.issuer.metadata.authorization_endpoint}`,
51
+ `token_endpoint=${this.issuer.metadata.token_endpoint}`,
52
+ `userinfo_endpoint=${this.issuer.metadata.userinfo_endpoint ?? "(none)"}`,
53
+ `jwks_uri=${this.issuer.metadata.jwks_uri}`
54
+ );
46
55
  }
47
56
  // Option 2: Manual configuration
48
57
  else {
@@ -203,7 +212,7 @@ export class OidcProvider {
203
212
  claims = { ...claims, ...userinfo };
204
213
  } catch (error) {
205
214
  // UserInfo is optional - continue with ID token claims only
206
- console.warn("Failed to fetch UserInfo, using ID token claims only:", error);
215
+ oidcLog.warn(`Failed to fetch UserInfo from ${this.config.userinfoEndpoint}, using ID token claims only:`, error);
207
216
  }
208
217
  }
209
218
 
@@ -44,6 +44,12 @@ export default interface OidcSession {
44
44
  */
45
45
  redirectUri: string;
46
46
 
47
+ /**
48
+ * Arbitrary metadata collected from meta.* query parameters on initiate
49
+ * Passed through to onAuthSuccess and onAuthError callbacks unchanged
50
+ */
51
+ metadata?: Record<string, string>;
52
+
47
53
  /**
48
54
  * Session creation timestamp
49
55
  * MongoDB TTL index will automatically delete expired sessions