@flink-app/oidc-plugin 2.0.0-alpha.68 → 2.0.0-alpha.69
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 +13 -0
- package/dist/OidcPlugin.d.ts.map +1 -1
- package/dist/OidcPlugin.js +18 -12
- package/dist/OidcPluginOptions.d.ts +3 -1
- package/dist/OidcPluginOptions.d.ts.map +1 -1
- package/dist/OidcProviderConfig.d.ts +12 -1
- package/dist/OidcProviderConfig.d.ts.map +1 -1
- package/dist/providers/OidcProvider.d.ts.map +1 -1
- package/dist/providers/OidcProvider.js +13 -3
- package/package.json +6 -6
- package/spec/helpers/test-helpers.ts +14 -0
- package/spec/plugin/OidcPlugin.spec.ts +75 -1
- package/src/OidcPlugin.ts +19 -13
- package/src/OidcPluginOptions.ts +3 -1
- package/src/OidcProviderConfig.ts +13 -1
- package/src/providers/OidcProvider.ts +15 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @flink-app/oidc-plugin
|
|
2
2
|
|
|
3
|
+
## 2.0.0-alpha.69
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- feat(oidc-plugin): support public clients with PKCE-only authentication (no client secret)
|
|
8
|
+
|
|
9
|
+
`clientSecret` is now optional in `OidcProviderConfig`. When omitted, the plugin configures `openid-client` with `token_endpoint_auth_method: "none"` for public client flows. Added `tokenEndpointAuthMethod` option for explicit control. Encryption key derivation skips providers without a secret, and `storeTokens: true` requires an explicit `encryptionKey` when no provider has a client secret.
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- @flink-app/flink@2.0.0-alpha.69
|
|
14
|
+
- @flink-app/jwt-auth-plugin@2.0.0-alpha.69
|
|
15
|
+
|
|
3
16
|
## 2.0.0-alpha.68
|
|
4
17
|
|
|
5
18
|
### Patch Changes
|
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,OAAO,EAAE,iBAAiB,GAAG,WAAW,CAiNlE"}
|
package/dist/OidcPlugin.js
CHANGED
|
@@ -124,9 +124,6 @@ function oidcPlugin(options) {
|
|
|
124
124
|
if (!providerConfig.clientId) {
|
|
125
125
|
throw new Error(`OIDC Plugin: ${providerName} clientId is required`);
|
|
126
126
|
}
|
|
127
|
-
if (!providerConfig.clientSecret) {
|
|
128
|
-
throw new Error(`OIDC Plugin: ${providerName} clientSecret is required`);
|
|
129
|
-
}
|
|
130
127
|
if (!providerConfig.callbackUrl) {
|
|
131
128
|
throw new Error(`OIDC Plugin: ${providerName} callbackUrl is required`);
|
|
132
129
|
}
|
|
@@ -144,19 +141,28 @@ function oidcPlugin(options) {
|
|
|
144
141
|
// Determine encryption key
|
|
145
142
|
let encryptionKey = options.encryptionKey;
|
|
146
143
|
if (!encryptionKey) {
|
|
147
|
-
// Derive from first configured provider
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
encryptionKey = firstProviderConfig.clientSecret;
|
|
144
|
+
// Derive from the first configured provider that has a client secret
|
|
145
|
+
const providerWithSecret = configuredProviders.find((name) => options.providers[name]?.clientSecret);
|
|
146
|
+
if (providerWithSecret) {
|
|
147
|
+
encryptionKey = options.providers[providerWithSecret].clientSecret;
|
|
152
148
|
flink_1.log.warn("OIDC Plugin: No encryption key provided, deriving from client secret. " + "For better security, provide a dedicated encryptionKey in options.");
|
|
153
149
|
}
|
|
154
150
|
}
|
|
155
|
-
|
|
156
|
-
|
|
151
|
+
// Encryption key is required when storing tokens
|
|
152
|
+
if (options.storeTokens) {
|
|
153
|
+
if (!encryptionKey || encryptionKey.length < 32) {
|
|
154
|
+
throw new Error("OIDC Plugin: Encryption key must be at least 32 characters. " +
|
|
155
|
+
"Provide an explicit encryptionKey when using storeTokens with public clients (no clientSecret).");
|
|
156
|
+
}
|
|
157
|
+
(0, encryption_utils_1.validateEncryptionSecret)(encryptionKey);
|
|
158
|
+
}
|
|
159
|
+
else if (encryptionKey) {
|
|
160
|
+
// Validate if provided, even when not storing tokens (used for getConnection/getConnections)
|
|
161
|
+
if (encryptionKey.length < 32) {
|
|
162
|
+
throw new Error("OIDC Plugin: Encryption key must be at least 32 characters");
|
|
163
|
+
}
|
|
164
|
+
(0, encryption_utils_1.validateEncryptionSecret)(encryptionKey);
|
|
157
165
|
}
|
|
158
|
-
// Validate encryption key
|
|
159
|
-
(0, encryption_utils_1.validateEncryptionSecret)(encryptionKey);
|
|
160
166
|
let flinkApp;
|
|
161
167
|
let sessionRepo;
|
|
162
168
|
let connectionRepo;
|
|
@@ -246,7 +246,9 @@ export interface OidcPluginOptions {
|
|
|
246
246
|
sessionTTL?: number;
|
|
247
247
|
/**
|
|
248
248
|
* Encryption key for encrypting stored OIDC tokens
|
|
249
|
-
* If not provided, will be derived from first configured provider's client secret
|
|
249
|
+
* If not provided, will be derived from the first configured provider's client secret
|
|
250
|
+
*
|
|
251
|
+
* Required when using storeTokens with public clients (no clientSecret).
|
|
250
252
|
*
|
|
251
253
|
* Recommended: Use a dedicated encryption key from environment variables
|
|
252
254
|
* Must be at least 32 characters
|
|
@@ -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
|
|
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"}
|
|
@@ -18,8 +18,19 @@ export interface OidcProviderConfig {
|
|
|
18
18
|
/**
|
|
19
19
|
* OAuth 2.0 client secret
|
|
20
20
|
* Provided by the IdP - keep this secure!
|
|
21
|
+
*
|
|
22
|
+
* Optional for public clients using PKCE-only authentication.
|
|
23
|
+
* When omitted, token_endpoint_auth_method defaults to "none".
|
|
21
24
|
*/
|
|
22
|
-
clientSecret
|
|
25
|
+
clientSecret?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Token endpoint authentication method
|
|
28
|
+
*
|
|
29
|
+
* - "client_secret_basic": HTTP Basic auth with client_id and client_secret (default when clientSecret is provided)
|
|
30
|
+
* - "client_secret_post": client_id and client_secret in POST body
|
|
31
|
+
* - "none": No client authentication (default when clientSecret is omitted, for public/PKCE-only clients)
|
|
32
|
+
*/
|
|
33
|
+
tokenEndpointAuthMethod?: "client_secret_basic" | "client_secret_post" | "none";
|
|
23
34
|
/**
|
|
24
35
|
* Callback URL for OAuth redirect
|
|
25
36
|
* Must match the redirect URI registered with the IdP
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OidcProviderConfig.d.ts","sourceRoot":"","sources":["../src/OidcProviderConfig.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IAC/B;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB
|
|
1
|
+
{"version":3,"file":"OidcProviderConfig.d.ts","sourceRoot":"","sources":["../src/OidcProviderConfig.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IAC/B;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,qBAAqB,GAAG,oBAAoB,GAAG,MAAM,CAAC;IAEhF;;;;OAIG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IAEjB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IAEH;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAE/B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OidcProvider.d.ts","sourceRoot":"","sources":["../../src/providers/OidcProvider.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"OidcProvider.d.ts","sourceRoot":"","sources":["../../src/providers/OidcProvider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwD,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACvG,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,WAAW,MAAM,wBAAwB,CAAC;AACjD,OAAO,YAAY,MAAM,yBAAyB,CAAC;AAInD;;;;;;;;;;GAUG;AACH,qBAAa,YAAY;IACrB,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAkB;gBAEzB,MAAM,EAAE,kBAAkB;IAItC;;;;;;;OAOG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IA0DjC;;;;;OAKG;IACG,mBAAmB,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAiB1G;;;;;;;OAOG;IACG,oBAAoB,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IAqC/H;;;;;;;;OAQG;IACG,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAajE;;;;;;;;;OASG;IACG,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,eAAe,GAAE,OAAc,GAAG,OAAO,CAAC,WAAW,CAAC;IA2BjG;;;;OAIG;YACW,iBAAiB;IAU/B;;;;OAIG;IACH,iBAAiB,IAAI,GAAG;CAM3B"}
|
|
@@ -54,12 +54,22 @@ class OidcProvider {
|
|
|
54
54
|
});
|
|
55
55
|
}
|
|
56
56
|
// Create OIDC client
|
|
57
|
-
|
|
57
|
+
const clientMetadata = {
|
|
58
58
|
client_id: this.config.clientId,
|
|
59
|
-
client_secret: this.config.clientSecret,
|
|
60
59
|
redirect_uris: [this.config.callbackUrl],
|
|
61
60
|
response_types: ["code"],
|
|
62
|
-
}
|
|
61
|
+
};
|
|
62
|
+
if (this.config.clientSecret) {
|
|
63
|
+
clientMetadata.client_secret = this.config.clientSecret;
|
|
64
|
+
clientMetadata.token_endpoint_auth_method =
|
|
65
|
+
this.config.tokenEndpointAuthMethod || "client_secret_basic";
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
// Public client (PKCE-only) — openid-client requires this to skip client authentication
|
|
69
|
+
clientMetadata.token_endpoint_auth_method =
|
|
70
|
+
this.config.tokenEndpointAuthMethod || "none";
|
|
71
|
+
}
|
|
72
|
+
this.client = new this.issuer.Client(clientMetadata);
|
|
63
73
|
this.initialized = true;
|
|
64
74
|
}
|
|
65
75
|
catch (error) {
|
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.69",
|
|
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.69"
|
|
15
15
|
},
|
|
16
16
|
"peerDependencies": {
|
|
17
|
-
"@flink-app/flink": ">=2.0.0-alpha.
|
|
17
|
+
"@flink-app/flink": ">=2.0.0-alpha.69",
|
|
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/
|
|
32
|
-
"@flink-app/
|
|
30
|
+
"@flink-app/flink": "2.0.0-alpha.69",
|
|
31
|
+
"@flink-app/test-utils": "2.0.0-alpha.69",
|
|
32
|
+
"@flink-app/jwt-auth-plugin": "2.0.0-alpha.69"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
35
|
"test": "jasmine-ts --config=./spec/support/jasmine.json",
|
|
@@ -39,6 +39,20 @@ export function createTestProviderConfig(overrides?: Partial<OidcProviderConfig>
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Create a test OIDC provider configuration for a public client (no clientSecret)
|
|
44
|
+
*/
|
|
45
|
+
export function createPublicClientProviderConfig(overrides?: Partial<OidcProviderConfig>): OidcProviderConfig {
|
|
46
|
+
return {
|
|
47
|
+
issuer: "https://test-idp.example.com",
|
|
48
|
+
clientId: "test-public-client-id",
|
|
49
|
+
callbackUrl: "http://localhost:3000/oidc/test/callback",
|
|
50
|
+
discoveryUrl: "https://test-idp.example.com/.well-known/openid-configuration",
|
|
51
|
+
scope: ["openid", "email", "profile"],
|
|
52
|
+
...overrides,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
42
56
|
/**
|
|
43
57
|
* Create a mock OIDC token set
|
|
44
58
|
*/
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { oidcPlugin } from "../../src/OidcPlugin";
|
|
9
|
-
import { createTestProviderConfig } from "../helpers/test-helpers";
|
|
9
|
+
import { createTestProviderConfig, createPublicClientProviderConfig } from "../helpers/test-helpers";
|
|
10
10
|
|
|
11
11
|
describe("OidcPlugin", () => {
|
|
12
12
|
|
|
@@ -72,6 +72,80 @@ describe("OidcPlugin", () => {
|
|
|
72
72
|
});
|
|
73
73
|
});
|
|
74
74
|
|
|
75
|
+
describe("public client (PKCE-only) support", () => {
|
|
76
|
+
it("should accept provider without clientSecret when encryptionKey is provided", () => {
|
|
77
|
+
expect(() => {
|
|
78
|
+
oidcPlugin({
|
|
79
|
+
providers: {
|
|
80
|
+
test: createPublicClientProviderConfig(),
|
|
81
|
+
},
|
|
82
|
+
encryptionKey: "valid-encryption-key-at-least-32-chars-long",
|
|
83
|
+
onAuthSuccess: async () => ({ user: {}, token: "", redirectUrl: "" }),
|
|
84
|
+
});
|
|
85
|
+
}).not.toThrow();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("should accept provider without clientSecret when storeTokens is false", () => {
|
|
89
|
+
expect(() => {
|
|
90
|
+
oidcPlugin({
|
|
91
|
+
providers: {
|
|
92
|
+
test: createPublicClientProviderConfig(),
|
|
93
|
+
},
|
|
94
|
+
storeTokens: false,
|
|
95
|
+
onAuthSuccess: async () => ({ user: {}, token: "", redirectUrl: "" }),
|
|
96
|
+
});
|
|
97
|
+
}).not.toThrow();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should accept provider without clientSecret when storeTokens is not set (defaults to false)", () => {
|
|
101
|
+
expect(() => {
|
|
102
|
+
oidcPlugin({
|
|
103
|
+
providers: {
|
|
104
|
+
test: createPublicClientProviderConfig(),
|
|
105
|
+
},
|
|
106
|
+
onAuthSuccess: async () => ({ user: {}, token: "", redirectUrl: "" }),
|
|
107
|
+
});
|
|
108
|
+
}).not.toThrow();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should throw when storeTokens is true, no encryptionKey, and no provider has clientSecret", () => {
|
|
112
|
+
expect(() => {
|
|
113
|
+
oidcPlugin({
|
|
114
|
+
providers: {
|
|
115
|
+
test: createPublicClientProviderConfig(),
|
|
116
|
+
},
|
|
117
|
+
storeTokens: true,
|
|
118
|
+
onAuthSuccess: async () => ({ user: {}, token: "", redirectUrl: "" }),
|
|
119
|
+
});
|
|
120
|
+
}).toThrowError(/Encryption key must be at least 32 characters/);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("should derive encryption key from provider with clientSecret when mixed with public clients", () => {
|
|
124
|
+
expect(() => {
|
|
125
|
+
oidcPlugin({
|
|
126
|
+
providers: {
|
|
127
|
+
publicProvider: createPublicClientProviderConfig(),
|
|
128
|
+
confidentialProvider: createTestProviderConfig(),
|
|
129
|
+
},
|
|
130
|
+
onAuthSuccess: async () => ({ user: {}, token: "", redirectUrl: "" }),
|
|
131
|
+
});
|
|
132
|
+
}).not.toThrow();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("should accept storeTokens with explicit encryptionKey for public clients", () => {
|
|
136
|
+
expect(() => {
|
|
137
|
+
oidcPlugin({
|
|
138
|
+
providers: {
|
|
139
|
+
test: createPublicClientProviderConfig(),
|
|
140
|
+
},
|
|
141
|
+
storeTokens: true,
|
|
142
|
+
encryptionKey: "valid-encryption-key-at-least-32-chars-long",
|
|
143
|
+
onAuthSuccess: async () => ({ user: {}, token: "", redirectUrl: "" }),
|
|
144
|
+
});
|
|
145
|
+
}).not.toThrow();
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
75
149
|
describe("plugin structure", () => {
|
|
76
150
|
it("should return plugin with correct ID", () => {
|
|
77
151
|
const plugin = oidcPlugin({
|
package/src/OidcPlugin.ts
CHANGED
|
@@ -102,9 +102,6 @@ export function oidcPlugin(options: OidcPluginOptions): FlinkPlugin {
|
|
|
102
102
|
if (!providerConfig.clientId) {
|
|
103
103
|
throw new Error(`OIDC Plugin: ${providerName} clientId is required`);
|
|
104
104
|
}
|
|
105
|
-
if (!providerConfig.clientSecret) {
|
|
106
|
-
throw new Error(`OIDC Plugin: ${providerName} clientSecret is required`);
|
|
107
|
-
}
|
|
108
105
|
if (!providerConfig.callbackUrl) {
|
|
109
106
|
throw new Error(`OIDC Plugin: ${providerName} callbackUrl is required`);
|
|
110
107
|
}
|
|
@@ -127,24 +124,33 @@ export function oidcPlugin(options: OidcPluginOptions): FlinkPlugin {
|
|
|
127
124
|
// Determine encryption key
|
|
128
125
|
let encryptionKey = options.encryptionKey;
|
|
129
126
|
if (!encryptionKey) {
|
|
130
|
-
// Derive from first configured provider
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
encryptionKey = firstProviderConfig.clientSecret;
|
|
127
|
+
// Derive from the first configured provider that has a client secret
|
|
128
|
+
const providerWithSecret = configuredProviders.find((name) => options.providers[name]?.clientSecret);
|
|
129
|
+
if (providerWithSecret) {
|
|
130
|
+
encryptionKey = options.providers[providerWithSecret].clientSecret;
|
|
135
131
|
log.warn(
|
|
136
132
|
"OIDC Plugin: No encryption key provided, deriving from client secret. " + "For better security, provide a dedicated encryptionKey in options."
|
|
137
133
|
);
|
|
138
134
|
}
|
|
139
135
|
}
|
|
140
136
|
|
|
141
|
-
|
|
142
|
-
|
|
137
|
+
// Encryption key is required when storing tokens
|
|
138
|
+
if (options.storeTokens) {
|
|
139
|
+
if (!encryptionKey || encryptionKey.length < 32) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
"OIDC Plugin: Encryption key must be at least 32 characters. " +
|
|
142
|
+
"Provide an explicit encryptionKey when using storeTokens with public clients (no clientSecret)."
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
validateEncryptionSecret(encryptionKey);
|
|
146
|
+
} else if (encryptionKey) {
|
|
147
|
+
// Validate if provided, even when not storing tokens (used for getConnection/getConnections)
|
|
148
|
+
if (encryptionKey.length < 32) {
|
|
149
|
+
throw new Error("OIDC Plugin: Encryption key must be at least 32 characters");
|
|
150
|
+
}
|
|
151
|
+
validateEncryptionSecret(encryptionKey);
|
|
143
152
|
}
|
|
144
153
|
|
|
145
|
-
// Validate encryption key
|
|
146
|
-
validateEncryptionSecret(encryptionKey);
|
|
147
|
-
|
|
148
154
|
let flinkApp: FlinkApp<OidcInternalContext>;
|
|
149
155
|
let sessionRepo: OidcSessionRepo;
|
|
150
156
|
let connectionRepo: OidcConnectionRepo;
|
package/src/OidcPluginOptions.ts
CHANGED
|
@@ -265,7 +265,9 @@ export interface OidcPluginOptions {
|
|
|
265
265
|
|
|
266
266
|
/**
|
|
267
267
|
* Encryption key for encrypting stored OIDC tokens
|
|
268
|
-
* If not provided, will be derived from first configured provider's client secret
|
|
268
|
+
* If not provided, will be derived from the first configured provider's client secret
|
|
269
|
+
*
|
|
270
|
+
* Required when using storeTokens with public clients (no clientSecret).
|
|
269
271
|
*
|
|
270
272
|
* Recommended: Use a dedicated encryption key from environment variables
|
|
271
273
|
* Must be at least 32 characters
|
|
@@ -20,8 +20,20 @@ export interface OidcProviderConfig {
|
|
|
20
20
|
/**
|
|
21
21
|
* OAuth 2.0 client secret
|
|
22
22
|
* Provided by the IdP - keep this secure!
|
|
23
|
+
*
|
|
24
|
+
* Optional for public clients using PKCE-only authentication.
|
|
25
|
+
* When omitted, token_endpoint_auth_method defaults to "none".
|
|
23
26
|
*/
|
|
24
|
-
clientSecret
|
|
27
|
+
clientSecret?: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Token endpoint authentication method
|
|
31
|
+
*
|
|
32
|
+
* - "client_secret_basic": HTTP Basic auth with client_id and client_secret (default when clientSecret is provided)
|
|
33
|
+
* - "client_secret_post": client_id and client_secret in POST body
|
|
34
|
+
* - "none": No client authentication (default when clientSecret is omitted, for public/PKCE-only clients)
|
|
35
|
+
*/
|
|
36
|
+
tokenEndpointAuthMethod?: "client_secret_basic" | "client_secret_post" | "none";
|
|
25
37
|
|
|
26
38
|
/**
|
|
27
39
|
* Callback URL for OAuth redirect
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Issuer, Client, generators, TokenSet, UserinfoResponse } from "openid-client";
|
|
1
|
+
import { Issuer, Client, ClientMetadata, generators, TokenSet, UserinfoResponse } from "openid-client";
|
|
2
2
|
import { OidcProviderConfig } from "../OidcProviderConfig";
|
|
3
3
|
import OidcProfile from "../schemas/OidcProfile";
|
|
4
4
|
import OidcTokenSet from "../schemas/OidcTokenSet";
|
|
@@ -65,12 +65,23 @@ export class OidcProvider {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// Create OIDC client
|
|
68
|
-
|
|
68
|
+
const clientMetadata: ClientMetadata = {
|
|
69
69
|
client_id: this.config.clientId,
|
|
70
|
-
client_secret: this.config.clientSecret,
|
|
71
70
|
redirect_uris: [this.config.callbackUrl],
|
|
72
71
|
response_types: ["code"],
|
|
73
|
-
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (this.config.clientSecret) {
|
|
75
|
+
clientMetadata.client_secret = this.config.clientSecret;
|
|
76
|
+
clientMetadata.token_endpoint_auth_method =
|
|
77
|
+
this.config.tokenEndpointAuthMethod || "client_secret_basic";
|
|
78
|
+
} else {
|
|
79
|
+
// Public client (PKCE-only) — openid-client requires this to skip client authentication
|
|
80
|
+
clientMetadata.token_endpoint_auth_method =
|
|
81
|
+
this.config.tokenEndpointAuthMethod || "none";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
this.client = new this.issuer.Client(clientMetadata);
|
|
74
85
|
|
|
75
86
|
this.initialized = true;
|
|
76
87
|
} catch (error: any) {
|