@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 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
@@ -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
@@ -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,CAiNlE"}
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"}
@@ -109,13 +109,14 @@ const CallbackOidc = __importStar(require("./handlers/CallbackOidc"));
109
109
  */
110
110
  function oidcPlugin(options) {
111
111
  // Validation
112
- if (!options.providers || Object.keys(options.providers).length === 0) {
113
- throw new Error("OIDC Plugin: At least one provider must be configured");
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(options.providers);
117
+ const configuredProviders = Object.keys(providers);
117
118
  for (const providerName of configuredProviders) {
118
- const providerConfig = options.providers[providerName];
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) => options.providers[name]?.clientSecret);
146
+ const providerWithSecret = configuredProviders.find((name) => providers[name]?.clientSecret);
146
147
  if (providerWithSecret) {
147
- encryptionKey = options.providers[providerWithSecret].clientSecret;
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(options.providers, options.providerLoader);
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: Record<string, OidcProviderConfig>;
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: any) => Promise<AuthSuccessCallbackResponse>;
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;IAC9B;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;IAE9C;;;;;;;;;;;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,GAAG,KACP,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;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAC;IAE9E;;;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;;;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
- constructor(staticProviders: Record<string, OidcProviderConfig>, providerLoader?: (providerName: string) => Promise<OidcProviderConfig | null>);
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,CAA+D;gBAGlF,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,EACnD,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC;IAMjF;;;;;;;;;;;;;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"}
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.76",
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.76"
14
+ "@flink-app/jwt-auth-plugin": "2.0.0-alpha.77"
15
15
  },
16
16
  "peerDependencies": {
17
- "@flink-app/flink": ">=2.0.0-alpha.76",
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.76",
31
- "@flink-app/test-utils": "2.0.0-alpha.76",
32
- "@flink-app/jwt-auth-plugin": "2.0.0-alpha.76"
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 registry = new ProviderRegistry(staticProviders, loader);
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
- if (!options.providers || Object.keys(options.providers).length === 0) {
90
- throw new Error("OIDC Plugin: At least one provider must be configured");
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(options.providers);
96
+ const configuredProviders = Object.keys(providers);
95
97
  for (const providerName of configuredProviders) {
96
- const providerConfig = options.providers[providerName];
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) => options.providers[name]?.clientSecret);
130
+ const providerWithSecret = configuredProviders.find((name) => providers[name]?.clientSecret);
129
131
  if (providerWithSecret) {
130
- encryptionKey = options.providers[providerWithSecret].clientSecret;
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(options.providers, options.providerLoader);
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;
@@ -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: Record<string, OidcProviderConfig>;
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: any
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) {