@flink-app/oidc-plugin 2.0.0-alpha.76 → 2.0.0-alpha.77
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 +11 -0
- package/dist/OidcPlugin.d.ts +1 -1
- package/dist/OidcPlugin.d.ts.map +1 -1
- package/dist/OidcPlugin.js +9 -8
- package/dist/OidcPluginOptions.d.ts +6 -5
- package/dist/OidcPluginOptions.d.ts.map +1 -1
- package/dist/providers/ProviderRegistry.d.ts +2 -1
- package/dist/providers/ProviderRegistry.d.ts.map +1 -1
- package/dist/providers/ProviderRegistry.js +3 -2
- package/package.json +6 -6
- package/spec/plugin/OidcPlugin.spec.ts +29 -2
- package/spec/providers/ProviderRegistry.spec.ts +8 -7
- package/src/OidcPlugin.ts +11 -9
- package/src/OidcPluginOptions.ts +6 -5
- package/src/providers/ProviderRegistry.ts +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @flink-app/oidc-plugin
|
|
2
2
|
|
|
3
|
+
## 2.0.0-alpha.77
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Make providers optional when providerLoader is configured, add TCtx generic for typed ctx in providerLoader and onAuthSuccess callbacks
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- @flink-app/flink@2.0.0-alpha.77
|
|
12
|
+
- @flink-app/jwt-auth-plugin@2.0.0-alpha.77
|
|
13
|
+
|
|
3
14
|
## 2.0.0-alpha.76
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
package/dist/OidcPlugin.d.ts
CHANGED
|
@@ -73,5 +73,5 @@ import { OidcPluginOptions } from "./OidcPluginOptions";
|
|
|
73
73
|
* });
|
|
74
74
|
* ```
|
|
75
75
|
*/
|
|
76
|
-
export declare function oidcPlugin(options: OidcPluginOptions): FlinkPlugin;
|
|
76
|
+
export declare function oidcPlugin<TCtx = any>(options: OidcPluginOptions<TCtx>): FlinkPlugin;
|
|
77
77
|
//# sourceMappingURL=OidcPlugin.d.ts.map
|
package/dist/OidcPlugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OidcPlugin.d.ts","sourceRoot":"","sources":["../src/OidcPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,WAAW,EAAO,MAAM,kBAAkB,CAAC;AAE9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAWxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"OidcPlugin.d.ts","sourceRoot":"","sources":["../src/OidcPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,WAAW,EAAO,MAAM,kBAAkB,CAAC;AAE9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAWxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AACH,wBAAgB,UAAU,CAAC,IAAI,GAAG,GAAG,EAAE,OAAO,EAAE,iBAAiB,CAAC,IAAI,CAAC,GAAG,WAAW,CAmNpF"}
|
package/dist/OidcPlugin.js
CHANGED
|
@@ -109,13 +109,14 @@ const CallbackOidc = __importStar(require("./handlers/CallbackOidc"));
|
|
|
109
109
|
*/
|
|
110
110
|
function oidcPlugin(options) {
|
|
111
111
|
// Validation
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
const providers = options.providers || {};
|
|
113
|
+
if (Object.keys(providers).length === 0 && !options.providerLoader) {
|
|
114
|
+
throw new Error("OIDC Plugin: At least one provider must be configured, or providerLoader must be provided");
|
|
114
115
|
}
|
|
115
116
|
// Validate provider configurations
|
|
116
|
-
const configuredProviders = Object.keys(
|
|
117
|
+
const configuredProviders = Object.keys(providers);
|
|
117
118
|
for (const providerName of configuredProviders) {
|
|
118
|
-
const providerConfig =
|
|
119
|
+
const providerConfig = providers[providerName];
|
|
119
120
|
if (!providerConfig)
|
|
120
121
|
continue;
|
|
121
122
|
if (!providerConfig.issuer) {
|
|
@@ -142,9 +143,9 @@ function oidcPlugin(options) {
|
|
|
142
143
|
let encryptionKey = options.encryptionKey;
|
|
143
144
|
if (!encryptionKey) {
|
|
144
145
|
// Derive from the first configured provider that has a client secret
|
|
145
|
-
const providerWithSecret = configuredProviders.find((name) =>
|
|
146
|
+
const providerWithSecret = configuredProviders.find((name) => providers[name]?.clientSecret);
|
|
146
147
|
if (providerWithSecret) {
|
|
147
|
-
encryptionKey =
|
|
148
|
+
encryptionKey = providers[providerWithSecret].clientSecret;
|
|
148
149
|
flink_1.log.warn("OIDC Plugin: No encryption key provided, deriving from client secret. " + "For better security, provide a dedicated encryptionKey in options.");
|
|
149
150
|
}
|
|
150
151
|
}
|
|
@@ -189,7 +190,7 @@ function oidcPlugin(options) {
|
|
|
189
190
|
await db.collection(sessionsCollectionName).createIndex({ createdAt: 1 }, { expireAfterSeconds: sessionTTL });
|
|
190
191
|
flink_1.log.info(`OIDC Plugin: Created TTL index on ${sessionsCollectionName} with ${sessionTTL}s expiration`);
|
|
191
192
|
// Initialize provider registry
|
|
192
|
-
providerRegistry = new ProviderRegistry_1.ProviderRegistry(
|
|
193
|
+
providerRegistry = new ProviderRegistry_1.ProviderRegistry(providers, options.providerLoader, flinkApp.ctx);
|
|
193
194
|
// Store provider registry in app context for handlers to access
|
|
194
195
|
flinkApp.ctx.oidcProviderRegistry = providerRegistry;
|
|
195
196
|
// Register OIDC handlers
|
|
@@ -198,7 +199,7 @@ function oidcPlugin(options) {
|
|
|
198
199
|
flinkApp.addHandler(InitiateOidc);
|
|
199
200
|
flinkApp.addHandler(CallbackOidc);
|
|
200
201
|
}
|
|
201
|
-
flink_1.log.info(`OIDC Plugin initialized with providers: ${configuredProviders.join(", ")}`);
|
|
202
|
+
flink_1.log.info(`OIDC Plugin initialized${configuredProviders.length > 0 ? ` with providers: ${configuredProviders.join(", ")}` : " with dynamic provider loader"}`);
|
|
202
203
|
}
|
|
203
204
|
catch (error) {
|
|
204
205
|
flink_1.log.error("Failed to initialize OIDC Plugin:", error);
|
|
@@ -56,7 +56,7 @@ export interface AuthErrorCallbackResponse {
|
|
|
56
56
|
* The onAuthSuccess callback receives the Flink context as a second parameter,
|
|
57
57
|
* allowing the application to generate JWT tokens using ctx.plugins.jwtAuth.createToken().
|
|
58
58
|
*/
|
|
59
|
-
export interface OidcPluginOptions {
|
|
59
|
+
export interface OidcPluginOptions<TCtx = any> {
|
|
60
60
|
/**
|
|
61
61
|
* OIDC provider configurations
|
|
62
62
|
* Key = provider name (used in URLs: /oidc/{provider}/initiate)
|
|
@@ -81,7 +81,7 @@ export interface OidcPluginOptions {
|
|
|
81
81
|
* }
|
|
82
82
|
* }
|
|
83
83
|
*/
|
|
84
|
-
providers
|
|
84
|
+
providers?: Record<string, OidcProviderConfig>;
|
|
85
85
|
/**
|
|
86
86
|
* Whether to store OIDC tokens for future API access
|
|
87
87
|
* If false, tokens are discarded after authentication (auth-only mode)
|
|
@@ -163,7 +163,7 @@ export interface OidcPluginOptions {
|
|
|
163
163
|
* Includes accessToken, idToken, refreshToken
|
|
164
164
|
*/
|
|
165
165
|
tokens?: OidcTokenSet;
|
|
166
|
-
}, ctx:
|
|
166
|
+
}, ctx: TCtx) => Promise<AuthSuccessCallbackResponse>;
|
|
167
167
|
/**
|
|
168
168
|
* Callback invoked on OIDC authentication errors
|
|
169
169
|
*
|
|
@@ -204,11 +204,12 @@ export interface OidcPluginOptions {
|
|
|
204
204
|
* Use this for multi-tenant scenarios where each organization has different IdPs.
|
|
205
205
|
*
|
|
206
206
|
* @param providerName - Name of the provider to load
|
|
207
|
+
* @param ctx - Flink context with access to repos and plugins
|
|
207
208
|
* @returns Provider configuration or null if not found
|
|
208
209
|
*
|
|
209
210
|
* Example:
|
|
210
211
|
* ```typescript
|
|
211
|
-
* providerLoader: async (providerName) => {
|
|
212
|
+
* providerLoader: async (providerName, ctx) => {
|
|
212
213
|
* const config = await ctx.repos.oidcProviderRepo.getByName(providerName);
|
|
213
214
|
* if (!config || !config.enabled) {
|
|
214
215
|
* return null;
|
|
@@ -224,7 +225,7 @@ export interface OidcPluginOptions {
|
|
|
224
225
|
* }
|
|
225
226
|
* ```
|
|
226
227
|
*/
|
|
227
|
-
providerLoader?: (providerName: string) => Promise<OidcProviderConfig | null>;
|
|
228
|
+
providerLoader?: (providerName: string, ctx: TCtx) => Promise<OidcProviderConfig | null>;
|
|
228
229
|
/**
|
|
229
230
|
* Custom collection name for OIDC sessions
|
|
230
231
|
* Default: 'oidc_sessions'
|
|
@@ -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;
|
|
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"}
|
|
@@ -13,7 +13,8 @@ export declare class ProviderRegistry {
|
|
|
13
13
|
private staticProviders;
|
|
14
14
|
private providerInstances;
|
|
15
15
|
private providerLoader?;
|
|
16
|
-
|
|
16
|
+
private ctx;
|
|
17
|
+
constructor(staticProviders: Record<string, OidcProviderConfig>, providerLoader?: (providerName: string, ctx: any) => Promise<OidcProviderConfig | null>, ctx?: any);
|
|
17
18
|
/**
|
|
18
19
|
* Get provider instance by name
|
|
19
20
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProviderRegistry.d.ts","sourceRoot":"","sources":["../../src/providers/ProviderRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D;;;;;;;;GAQG;AACH,qBAAa,gBAAgB;IACzB,OAAO,CAAC,eAAe,CAAqC;IAC5D,OAAO,CAAC,iBAAiB,CAAwC;IACjE,OAAO,CAAC,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"ProviderRegistry.d.ts","sourceRoot":"","sources":["../../src/providers/ProviderRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D;;;;;;;;GAQG;AACH,qBAAa,gBAAgB;IACzB,OAAO,CAAC,eAAe,CAAqC;IAC5D,OAAO,CAAC,iBAAiB,CAAwC;IACjE,OAAO,CAAC,cAAc,CAAC,CAAyE;IAChG,OAAO,CAAC,GAAG,CAAM;gBAGb,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,EACnD,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,EACvF,GAAG,CAAC,EAAE,GAAG;IAOb;;;;;;;;;;;;;OAaG;IACG,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAgC9D;;;;;OAKG;IACH,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAI1C;;;;;;;OAOG;IACH,UAAU,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI;IAQvC;;;;OAIG;IACH,gBAAgB,IAAI,MAAM,EAAE;CAG/B"}
|
|
@@ -13,10 +13,11 @@ const error_utils_1 = require("../utils/error-utils");
|
|
|
13
13
|
* - Lazy initialization
|
|
14
14
|
*/
|
|
15
15
|
class ProviderRegistry {
|
|
16
|
-
constructor(staticProviders, providerLoader) {
|
|
16
|
+
constructor(staticProviders, providerLoader, ctx) {
|
|
17
17
|
this.providerInstances = new Map();
|
|
18
18
|
this.staticProviders = staticProviders;
|
|
19
19
|
this.providerLoader = providerLoader;
|
|
20
|
+
this.ctx = ctx;
|
|
20
21
|
}
|
|
21
22
|
/**
|
|
22
23
|
* Get provider instance by name
|
|
@@ -42,7 +43,7 @@ class ProviderRegistry {
|
|
|
42
43
|
let config = this.staticProviders[providerName] || null;
|
|
43
44
|
// Try dynamic loader if not in static config
|
|
44
45
|
if (!config && this.providerLoader) {
|
|
45
|
-
config = await this.providerLoader(providerName);
|
|
46
|
+
config = await this.providerLoader(providerName, this.ctx);
|
|
46
47
|
}
|
|
47
48
|
if (!config) {
|
|
48
49
|
throw (0, error_utils_1.createOidcError)(error_utils_1.OidcErrorCodes.PROVIDER_NOT_CONFIGURED, `OIDC provider '${providerName}' is not configured`, {
|
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.77",
|
|
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.77"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
|
-
"@flink-app/flink": ">=2.0.0-alpha.
|
|
17
|
+
"@flink-app/flink": ">=2.0.0-alpha.77",
|
|
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.
|
|
31
|
-
"@flink-app/test-utils": "2.0.0-alpha.
|
|
32
|
-
"@flink-app/jwt-auth-plugin": "2.0.0-alpha.
|
|
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"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"test": "jasmine-ts --config=./spec/support/jasmine.json",
|
|
@@ -11,13 +11,40 @@ import { createTestProviderConfig, createPublicClientProviderConfig } from "../h
|
|
|
11
11
|
describe("OidcPlugin", () => {
|
|
12
12
|
|
|
13
13
|
describe("configuration validation", () => {
|
|
14
|
-
it("should throw error if no providers configured", () => {
|
|
14
|
+
it("should throw error if no providers and no providerLoader configured", () => {
|
|
15
15
|
expect(() => {
|
|
16
16
|
oidcPlugin({
|
|
17
17
|
providers: {},
|
|
18
18
|
onAuthSuccess: async () => ({ user: {}, token: "", redirectUrl: "" }),
|
|
19
19
|
});
|
|
20
|
-
}).toThrowError(/At least one provider must be configured/);
|
|
20
|
+
}).toThrowError(/At least one provider must be configured, or providerLoader must be provided/);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should throw error if providers omitted and no providerLoader configured", () => {
|
|
24
|
+
expect(() => {
|
|
25
|
+
oidcPlugin({
|
|
26
|
+
onAuthSuccess: async () => ({ user: {}, token: "", redirectUrl: "" }),
|
|
27
|
+
});
|
|
28
|
+
}).toThrowError(/At least one provider must be configured, or providerLoader must be provided/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("should accept empty providers when providerLoader is configured", () => {
|
|
32
|
+
expect(() => {
|
|
33
|
+
oidcPlugin({
|
|
34
|
+
providers: {},
|
|
35
|
+
providerLoader: async () => null,
|
|
36
|
+
onAuthSuccess: async () => ({ user: {}, token: "", redirectUrl: "" }),
|
|
37
|
+
});
|
|
38
|
+
}).not.toThrow();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should accept omitted providers when providerLoader is configured", () => {
|
|
42
|
+
expect(() => {
|
|
43
|
+
oidcPlugin({
|
|
44
|
+
providerLoader: async () => null,
|
|
45
|
+
onAuthSuccess: async () => ({ user: {}, token: "", redirectUrl: "" }),
|
|
46
|
+
});
|
|
47
|
+
}).not.toThrow();
|
|
21
48
|
});
|
|
22
49
|
|
|
23
50
|
it("should throw error if onAuthSuccess is missing", () => {
|
|
@@ -29,7 +29,7 @@ describe("ProviderRegistry", () => {
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
it("should create registry with provider loader", () => {
|
|
32
|
-
const loader = async (name: string) => null;
|
|
32
|
+
const loader = async (name: string, ctx: any) => null;
|
|
33
33
|
const registry = new ProviderRegistry(staticProviders, loader);
|
|
34
34
|
expect(registry).toBeDefined();
|
|
35
35
|
});
|
|
@@ -49,7 +49,7 @@ describe("ProviderRegistry", () => {
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
it("should return true if provider loader is configured", () => {
|
|
52
|
-
const loader = async (name: string) => null;
|
|
52
|
+
const loader = async (name: string, ctx: any) => null;
|
|
53
53
|
const registry = new ProviderRegistry(staticProviders, loader);
|
|
54
54
|
// hasProvider returns true if loader exists, even for unknown providers
|
|
55
55
|
expect(registry.hasProvider("unknown")).toBe(true);
|
|
@@ -87,7 +87,8 @@ describe("ProviderRegistry", () => {
|
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
const loader = jasmine.createSpy("loader").and.returnValue(Promise.resolve(dynamicConfig));
|
|
90
|
-
const
|
|
90
|
+
const mockCtx = { repos: {} };
|
|
91
|
+
const registry = new ProviderRegistry(staticProviders, loader, mockCtx);
|
|
91
92
|
|
|
92
93
|
// Note: This will attempt real OIDC discovery, which will fail in tests
|
|
93
94
|
// We're primarily testing that the loader is called
|
|
@@ -95,13 +96,13 @@ describe("ProviderRegistry", () => {
|
|
|
95
96
|
await registry.getProvider("okta");
|
|
96
97
|
} catch (error) {
|
|
97
98
|
// Discovery will fail, but loader should have been called
|
|
98
|
-
expect(loader).toHaveBeenCalledWith("okta");
|
|
99
|
+
expect(loader).toHaveBeenCalledWith("okta", mockCtx);
|
|
99
100
|
}
|
|
100
101
|
});
|
|
101
102
|
|
|
102
103
|
it("should prioritize static config over loader", async () => {
|
|
103
104
|
const loader = jasmine.createSpy("loader").and.returnValue(Promise.resolve(null));
|
|
104
|
-
const registry = new ProviderRegistry(staticProviders, loader);
|
|
105
|
+
const registry = new ProviderRegistry(staticProviders, loader, {});
|
|
105
106
|
|
|
106
107
|
// Note: This will attempt real OIDC discovery
|
|
107
108
|
try {
|
|
@@ -113,7 +114,7 @@ describe("ProviderRegistry", () => {
|
|
|
113
114
|
});
|
|
114
115
|
|
|
115
116
|
it("should throw error if loader returns null", async () => {
|
|
116
|
-
const loader = async (name: string) => null;
|
|
117
|
+
const loader = async (name: string, ctx: any) => null;
|
|
117
118
|
const registry = new ProviderRegistry({}, loader);
|
|
118
119
|
|
|
119
120
|
try {
|
|
@@ -169,7 +170,7 @@ describe("ProviderRegistry", () => {
|
|
|
169
170
|
})
|
|
170
171
|
)
|
|
171
172
|
);
|
|
172
|
-
const registry = new ProviderRegistry({}, loader);
|
|
173
|
+
const registry = new ProviderRegistry({}, loader, {});
|
|
173
174
|
|
|
174
175
|
// First call - will attempt to initialize (and fail on discovery)
|
|
175
176
|
try {
|
package/src/OidcPlugin.ts
CHANGED
|
@@ -84,16 +84,18 @@ import * as CallbackOidc from "./handlers/CallbackOidc";
|
|
|
84
84
|
* });
|
|
85
85
|
* ```
|
|
86
86
|
*/
|
|
87
|
-
export function oidcPlugin(options: OidcPluginOptions): FlinkPlugin {
|
|
87
|
+
export function oidcPlugin<TCtx = any>(options: OidcPluginOptions<TCtx>): FlinkPlugin {
|
|
88
88
|
// Validation
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
const providers = options.providers || {};
|
|
90
|
+
|
|
91
|
+
if (Object.keys(providers).length === 0 && !options.providerLoader) {
|
|
92
|
+
throw new Error("OIDC Plugin: At least one provider must be configured, or providerLoader must be provided");
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
// Validate provider configurations
|
|
94
|
-
const configuredProviders = Object.keys(
|
|
96
|
+
const configuredProviders = Object.keys(providers);
|
|
95
97
|
for (const providerName of configuredProviders) {
|
|
96
|
-
const providerConfig =
|
|
98
|
+
const providerConfig = providers[providerName];
|
|
97
99
|
if (!providerConfig) continue;
|
|
98
100
|
|
|
99
101
|
if (!providerConfig.issuer) {
|
|
@@ -125,9 +127,9 @@ export function oidcPlugin(options: OidcPluginOptions): FlinkPlugin {
|
|
|
125
127
|
let encryptionKey = options.encryptionKey;
|
|
126
128
|
if (!encryptionKey) {
|
|
127
129
|
// Derive from the first configured provider that has a client secret
|
|
128
|
-
const providerWithSecret = configuredProviders.find((name) =>
|
|
130
|
+
const providerWithSecret = configuredProviders.find((name) => providers[name]?.clientSecret);
|
|
129
131
|
if (providerWithSecret) {
|
|
130
|
-
encryptionKey =
|
|
132
|
+
encryptionKey = providers[providerWithSecret].clientSecret;
|
|
131
133
|
log.warn(
|
|
132
134
|
"OIDC Plugin: No encryption key provided, deriving from client secret. " + "For better security, provide a dedicated encryptionKey in options."
|
|
133
135
|
);
|
|
@@ -186,7 +188,7 @@ export function oidcPlugin(options: OidcPluginOptions): FlinkPlugin {
|
|
|
186
188
|
log.info(`OIDC Plugin: Created TTL index on ${sessionsCollectionName} with ${sessionTTL}s expiration`);
|
|
187
189
|
|
|
188
190
|
// Initialize provider registry
|
|
189
|
-
providerRegistry = new ProviderRegistry(
|
|
191
|
+
providerRegistry = new ProviderRegistry(providers, options.providerLoader, flinkApp.ctx);
|
|
190
192
|
|
|
191
193
|
// Store provider registry in app context for handlers to access
|
|
192
194
|
(flinkApp.ctx as any).oidcProviderRegistry = providerRegistry;
|
|
@@ -198,7 +200,7 @@ export function oidcPlugin(options: OidcPluginOptions): FlinkPlugin {
|
|
|
198
200
|
flinkApp.addHandler(CallbackOidc);
|
|
199
201
|
}
|
|
200
202
|
|
|
201
|
-
log.info(`OIDC Plugin initialized with providers: ${configuredProviders.join(", ")}`);
|
|
203
|
+
log.info(`OIDC Plugin initialized${configuredProviders.length > 0 ? ` with providers: ${configuredProviders.join(", ")}` : " with dynamic provider loader"}`);
|
|
202
204
|
} catch (error) {
|
|
203
205
|
log.error("Failed to initialize OIDC Plugin:", error);
|
|
204
206
|
throw error;
|
package/src/OidcPluginOptions.ts
CHANGED
|
@@ -64,7 +64,7 @@ export interface AuthErrorCallbackResponse {
|
|
|
64
64
|
* The onAuthSuccess callback receives the Flink context as a second parameter,
|
|
65
65
|
* allowing the application to generate JWT tokens using ctx.plugins.jwtAuth.createToken().
|
|
66
66
|
*/
|
|
67
|
-
export interface OidcPluginOptions {
|
|
67
|
+
export interface OidcPluginOptions<TCtx = any> {
|
|
68
68
|
/**
|
|
69
69
|
* OIDC provider configurations
|
|
70
70
|
* Key = provider name (used in URLs: /oidc/{provider}/initiate)
|
|
@@ -89,7 +89,7 @@ export interface OidcPluginOptions {
|
|
|
89
89
|
* }
|
|
90
90
|
* }
|
|
91
91
|
*/
|
|
92
|
-
providers
|
|
92
|
+
providers?: Record<string, OidcProviderConfig>;
|
|
93
93
|
|
|
94
94
|
/**
|
|
95
95
|
* Whether to store OIDC tokens for future API access
|
|
@@ -178,7 +178,7 @@ export interface OidcPluginOptions {
|
|
|
178
178
|
*/
|
|
179
179
|
tokens?: OidcTokenSet;
|
|
180
180
|
},
|
|
181
|
-
ctx:
|
|
181
|
+
ctx: TCtx
|
|
182
182
|
) => Promise<AuthSuccessCallbackResponse>;
|
|
183
183
|
|
|
184
184
|
/**
|
|
@@ -219,11 +219,12 @@ export interface OidcPluginOptions {
|
|
|
219
219
|
* Use this for multi-tenant scenarios where each organization has different IdPs.
|
|
220
220
|
*
|
|
221
221
|
* @param providerName - Name of the provider to load
|
|
222
|
+
* @param ctx - Flink context with access to repos and plugins
|
|
222
223
|
* @returns Provider configuration or null if not found
|
|
223
224
|
*
|
|
224
225
|
* Example:
|
|
225
226
|
* ```typescript
|
|
226
|
-
* providerLoader: async (providerName) => {
|
|
227
|
+
* providerLoader: async (providerName, ctx) => {
|
|
227
228
|
* const config = await ctx.repos.oidcProviderRepo.getByName(providerName);
|
|
228
229
|
* if (!config || !config.enabled) {
|
|
229
230
|
* return null;
|
|
@@ -239,7 +240,7 @@ export interface OidcPluginOptions {
|
|
|
239
240
|
* }
|
|
240
241
|
* ```
|
|
241
242
|
*/
|
|
242
|
-
providerLoader?: (providerName: string) => Promise<OidcProviderConfig | null>;
|
|
243
|
+
providerLoader?: (providerName: string, ctx: TCtx) => Promise<OidcProviderConfig | null>;
|
|
243
244
|
|
|
244
245
|
/**
|
|
245
246
|
* Custom collection name for OIDC sessions
|
|
@@ -14,14 +14,17 @@ import { createOidcError, OidcErrorCodes } from "../utils/error-utils";
|
|
|
14
14
|
export class ProviderRegistry {
|
|
15
15
|
private staticProviders: Record<string, OidcProviderConfig>;
|
|
16
16
|
private providerInstances: Map<string, OidcProvider> = new Map();
|
|
17
|
-
private providerLoader?: (providerName: string) => Promise<OidcProviderConfig | null>;
|
|
17
|
+
private providerLoader?: (providerName: string, ctx: any) => Promise<OidcProviderConfig | null>;
|
|
18
|
+
private ctx: any;
|
|
18
19
|
|
|
19
20
|
constructor(
|
|
20
21
|
staticProviders: Record<string, OidcProviderConfig>,
|
|
21
|
-
providerLoader?: (providerName: string) => Promise<OidcProviderConfig | null
|
|
22
|
+
providerLoader?: (providerName: string, ctx: any) => Promise<OidcProviderConfig | null>,
|
|
23
|
+
ctx?: any
|
|
22
24
|
) {
|
|
23
25
|
this.staticProviders = staticProviders;
|
|
24
26
|
this.providerLoader = providerLoader;
|
|
27
|
+
this.ctx = ctx;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
/**
|
|
@@ -50,7 +53,7 @@ export class ProviderRegistry {
|
|
|
50
53
|
|
|
51
54
|
// Try dynamic loader if not in static config
|
|
52
55
|
if (!config && this.providerLoader) {
|
|
53
|
-
config = await this.providerLoader(providerName);
|
|
56
|
+
config = await this.providerLoader(providerName, this.ctx);
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
if (!config) {
|