@flink-app/oidc-plugin 2.0.0-alpha.81 → 2.0.0-alpha.83

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.83
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies
8
+ - @flink-app/flink@2.0.0-alpha.83
9
+ - @flink-app/jwt-auth-plugin@2.0.0-alpha.83
10
+
11
+ ## 2.0.0-alpha.82
12
+
13
+ ### Patch Changes
14
+
15
+ - 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.
16
+ - @flink-app/flink@2.0.0-alpha.82
17
+ - @flink-app/jwt-auth-plugin@2.0.0-alpha.82
18
+
3
19
  ## 2.0.0-alpha.81
4
20
 
5
21
  ### 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,CA8PnE,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 {
@@ -228,6 +234,7 @@ const CallbackOidc = async ({ ctx, req }) => {
228
234
  error,
229
235
  provider,
230
236
  redirectUri: sessionRedirectUri,
237
+ metadata: sessionMetadata,
231
238
  });
232
239
  if (errorResult.redirectUrl) {
233
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}`);
@@ -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.81",
3
+ "version": "2.0.0-alpha.83",
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.81"
14
+ "@flink-app/jwt-auth-plugin": "2.0.0-alpha.83"
15
15
  },
16
16
  "peerDependencies": {
17
- "@flink-app/flink": ">=2.0.0-alpha.81",
17
+ "@flink-app/flink": ">=2.0.0-alpha.83",
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.81",
31
- "@flink-app/jwt-auth-plugin": "2.0.0-alpha.81",
32
- "@flink-app/test-utils": "2.0.0-alpha.81"
30
+ "@flink-app/flink": "2.0.0-alpha.83",
31
+ "@flink-app/jwt-auth-plugin": "2.0.0-alpha.83",
32
+ "@flink-app/test-utils": "2.0.0-alpha.83"
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) {
@@ -278,6 +284,7 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
278
284
  error,
279
285
  provider,
280
286
  redirectUri: sessionRedirectUri,
287
+ metadata: sessionMetadata,
281
288
  });
282
289
 
283
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
 
@@ -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