@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 +15 -0
- package/dist/OidcPluginOptions.d.ts +16 -1
- package/dist/OidcPluginOptions.d.ts.map +1 -1
- package/dist/handlers/CallbackOidc.d.ts.map +1 -1
- package/dist/handlers/CallbackOidc.js +11 -2
- package/dist/handlers/InitiateOidc.d.ts.map +1 -1
- package/dist/handlers/InitiateOidc.js +9 -1
- package/dist/providers/OidcProvider.d.ts.map +1 -1
- package/dist/providers/OidcProvider.js +4 -1
- package/dist/schemas/OidcSession.d.ts +5 -0
- package/dist/schemas/OidcSession.d.ts.map +1 -1
- package/package.json +6 -6
- package/src/OidcPluginOptions.ts +17 -1
- package/src/handlers/CallbackOidc.ts +11 -2
- package/src/handlers/InitiateOidc.ts +10 -1
- package/src/providers/OidcProvider.ts +10 -1
- package/src/schemas/OidcSession.ts +6 -0
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;
|
|
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,
|
|
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
|
|
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,
|
|
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;
|
|
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
|
-
|
|
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.
|
|
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.
|
|
14
|
+
"@flink-app/jwt-auth-plugin": "2.0.0-alpha.82"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
|
-
"@flink-app/flink": ">=2.0.0-alpha.
|
|
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/
|
|
31
|
-
"@flink-app/
|
|
32
|
-
"@flink-app/
|
|
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",
|
package/src/OidcPluginOptions.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|