@flink-app/oidc-plugin 1.0.0 → 2.0.0-alpha.100
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 +558 -0
- package/README.md +23 -2
- package/dist/OidcPlugin.d.ts +1 -1
- package/dist/OidcPlugin.d.ts.map +1 -1
- package/dist/OidcPlugin.js +32 -18
- package/dist/OidcPluginOptions.d.ts +75 -8
- package/dist/OidcPluginOptions.d.ts.map +1 -1
- package/dist/OidcProviderConfig.d.ts +21 -1
- package/dist/OidcProviderConfig.d.ts.map +1 -1
- package/dist/handlers/CallbackOidc.d.ts.map +1 -1
- package/dist/handlers/CallbackOidc.js +50 -5
- package/dist/handlers/InitiateOidc.d.ts.map +1 -1
- package/dist/handlers/InitiateOidc.js +14 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/log.d.ts +27 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +37 -0
- package/dist/providers/OidcProvider.d.ts +1 -1
- package/dist/providers/OidcProvider.d.ts.map +1 -1
- package/dist/providers/OidcProvider.js +46 -5
- package/dist/providers/ProviderRegistry.d.ts +2 -1
- package/dist/providers/ProviderRegistry.d.ts.map +1 -1
- package/dist/providers/ProviderRegistry.js +10 -2
- package/dist/schemas/CallbackRequest.d.ts +3 -6
- package/dist/schemas/CallbackRequest.d.ts.map +1 -1
- package/dist/schemas/InitiateRequest.d.ts +3 -6
- package/dist/schemas/InitiateRequest.d.ts.map +1 -1
- package/dist/schemas/OidcSession.d.ts +5 -0
- package/dist/schemas/OidcSession.d.ts.map +1 -1
- package/dist/utils/response-utils.d.ts +16 -5
- package/dist/utils/response-utils.d.ts.map +1 -1
- package/dist/utils/response-utils.js +31 -10
- package/examples/multi-provider.ts +1 -1
- package/package.json +8 -11
- package/spec/helpers/test-helpers.ts +14 -0
- package/spec/plugin/OidcPlugin.spec.ts +104 -3
- package/spec/providers/ProviderRegistry.spec.ts +8 -7
- package/spec/utils/response-utils.spec.ts +85 -0
- package/src/OidcPlugin.ts +54 -21
- package/src/OidcPluginOptions.ts +84 -9
- package/src/OidcProviderConfig.ts +23 -1
- package/src/handlers/CallbackOidc.ts +66 -5
- package/src/handlers/InitiateOidc.ts +19 -1
- package/src/index.ts +1 -1
- package/src/log.ts +33 -0
- package/src/providers/OidcProvider.ts +60 -7
- package/src/providers/ProviderRegistry.ts +22 -3
- package/src/schemas/CallbackRequest.ts +4 -6
- package/src/schemas/InitiateRequest.ts +3 -6
- package/src/schemas/OidcSession.ts +6 -0
- package/src/utils/response-utils.ts +53 -10
package/dist/OidcPlugin.js
CHANGED
|
@@ -28,6 +28,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
28
28
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
29
|
exports.oidcPlugin = oidcPlugin;
|
|
30
30
|
const flink_1 = require("@flink-app/flink");
|
|
31
|
+
const log_1 = require("./log");
|
|
31
32
|
const OidcSessionRepo_1 = __importDefault(require("./repos/OidcSessionRepo"));
|
|
32
33
|
const OidcConnectionRepo_1 = __importDefault(require("./repos/OidcConnectionRepo"));
|
|
33
34
|
const encryption_utils_1 = require("./utils/encryption-utils");
|
|
@@ -109,13 +110,14 @@ const CallbackOidc = __importStar(require("./handlers/CallbackOidc"));
|
|
|
109
110
|
*/
|
|
110
111
|
function oidcPlugin(options) {
|
|
111
112
|
// Validation
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
const providers = options.providers || {};
|
|
114
|
+
if (Object.keys(providers).length === 0 && !options.providerLoader) {
|
|
115
|
+
throw new Error("OIDC Plugin: At least one provider must be configured, or providerLoader must be provided");
|
|
114
116
|
}
|
|
115
117
|
// Validate provider configurations
|
|
116
|
-
const configuredProviders = Object.keys(
|
|
118
|
+
const configuredProviders = Object.keys(providers);
|
|
117
119
|
for (const providerName of configuredProviders) {
|
|
118
|
-
const providerConfig =
|
|
120
|
+
const providerConfig = providers[providerName];
|
|
119
121
|
if (!providerConfig)
|
|
120
122
|
continue;
|
|
121
123
|
if (!providerConfig.issuer) {
|
|
@@ -124,9 +126,6 @@ function oidcPlugin(options) {
|
|
|
124
126
|
if (!providerConfig.clientId) {
|
|
125
127
|
throw new Error(`OIDC Plugin: ${providerName} clientId is required`);
|
|
126
128
|
}
|
|
127
|
-
if (!providerConfig.clientSecret) {
|
|
128
|
-
throw new Error(`OIDC Plugin: ${providerName} clientSecret is required`);
|
|
129
|
-
}
|
|
130
129
|
if (!providerConfig.callbackUrl) {
|
|
131
130
|
throw new Error(`OIDC Plugin: ${providerName} callbackUrl is required`);
|
|
132
131
|
}
|
|
@@ -137,6 +136,7 @@ function oidcPlugin(options) {
|
|
|
137
136
|
`(authorizationEndpoint, tokenEndpoint, jwksUri)`);
|
|
138
137
|
}
|
|
139
138
|
}
|
|
139
|
+
log_1.oidcLog.debug(`Provider "${providerName}" config:`, `issuer=${providerConfig.issuer}`, `clientId=${providerConfig.clientId}`, `clientSecret=${(0, log_1.maskSecret)(providerConfig.clientSecret)}`, `callbackUrl=${providerConfig.callbackUrl}`, providerConfig.discoveryUrl ? `discoveryUrl=${providerConfig.discoveryUrl}` : `authorizationEndpoint=${providerConfig.authorizationEndpoint} tokenEndpoint=${providerConfig.tokenEndpoint}`, `scope=${(providerConfig.scope || ["openid", "email", "profile"]).join(" ")}`, providerConfig.tokenEndpointAuthMethod ? `authMethod=${providerConfig.tokenEndpointAuthMethod}` : "");
|
|
140
140
|
}
|
|
141
141
|
if (!options.onAuthSuccess) {
|
|
142
142
|
throw new Error("OIDC Plugin: onAuthSuccess callback is required");
|
|
@@ -144,19 +144,32 @@ function oidcPlugin(options) {
|
|
|
144
144
|
// Determine encryption key
|
|
145
145
|
let encryptionKey = options.encryptionKey;
|
|
146
146
|
if (!encryptionKey) {
|
|
147
|
-
// Derive from first configured provider
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
encryptionKey = firstProviderConfig.clientSecret;
|
|
147
|
+
// Derive from the first configured provider that has a client secret
|
|
148
|
+
const providerWithSecret = configuredProviders.find((name) => providers[name]?.clientSecret);
|
|
149
|
+
if (providerWithSecret) {
|
|
150
|
+
encryptionKey = providers[providerWithSecret].clientSecret;
|
|
152
151
|
flink_1.log.warn("OIDC Plugin: No encryption key provided, deriving from client secret. " + "For better security, provide a dedicated encryptionKey in options.");
|
|
152
|
+
log_1.oidcLog.debug(`Encryption key derived from provider "${providerWithSecret}" clientSecret`);
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
else {
|
|
156
|
+
log_1.oidcLog.debug(`Encryption key: ${(0, log_1.maskSecret)(encryptionKey)} (explicit)`);
|
|
157
|
+
}
|
|
158
|
+
// Encryption key is required when storing tokens
|
|
159
|
+
if (options.storeTokens) {
|
|
160
|
+
if (!encryptionKey || encryptionKey.length < 32) {
|
|
161
|
+
throw new Error("OIDC Plugin: Encryption key must be at least 32 characters. " +
|
|
162
|
+
"Provide an explicit encryptionKey when using storeTokens with public clients (no clientSecret).");
|
|
163
|
+
}
|
|
164
|
+
(0, encryption_utils_1.validateEncryptionSecret)(encryptionKey);
|
|
165
|
+
}
|
|
166
|
+
else if (encryptionKey) {
|
|
167
|
+
// Validate if provided, even when not storing tokens (used for getConnection/getConnections)
|
|
168
|
+
if (encryptionKey.length < 32) {
|
|
169
|
+
throw new Error("OIDC Plugin: Encryption key must be at least 32 characters");
|
|
170
|
+
}
|
|
171
|
+
(0, encryption_utils_1.validateEncryptionSecret)(encryptionKey);
|
|
157
172
|
}
|
|
158
|
-
// Validate encryption key
|
|
159
|
-
(0, encryption_utils_1.validateEncryptionSecret)(encryptionKey);
|
|
160
173
|
let flinkApp;
|
|
161
174
|
let sessionRepo;
|
|
162
175
|
let connectionRepo;
|
|
@@ -174,6 +187,7 @@ function oidcPlugin(options) {
|
|
|
174
187
|
// Initialize repositories
|
|
175
188
|
const sessionsCollectionName = options.sessionsCollectionName || "oidc_sessions";
|
|
176
189
|
const connectionsCollectionName = options.connectionsCollectionName || "oidc_connections";
|
|
190
|
+
log_1.oidcLog.debug(`Init: sessionsCollection=${sessionsCollectionName}`, `connectionsCollection=${connectionsCollectionName}`, `storeTokens=${!!options.storeTokens}`, `sessionTTL=${options.sessionTTL ?? 600}s`, `registerRoutes=${options.registerRoutes !== false}`, configuredProviders.length > 0 ? `staticProviders=[${configuredProviders.join(", ")}]` : "staticProviders=[] (dynamic only)", options.providerLoader ? "providerLoader=yes" : "providerLoader=no");
|
|
177
191
|
sessionRepo = new OidcSessionRepo_1.default(sessionsCollectionName, db);
|
|
178
192
|
connectionRepo = new OidcConnectionRepo_1.default(connectionsCollectionName, db);
|
|
179
193
|
flinkApp.addRepo("oidcSessionRepo", sessionRepo);
|
|
@@ -183,7 +197,7 @@ function oidcPlugin(options) {
|
|
|
183
197
|
await db.collection(sessionsCollectionName).createIndex({ createdAt: 1 }, { expireAfterSeconds: sessionTTL });
|
|
184
198
|
flink_1.log.info(`OIDC Plugin: Created TTL index on ${sessionsCollectionName} with ${sessionTTL}s expiration`);
|
|
185
199
|
// Initialize provider registry
|
|
186
|
-
providerRegistry = new ProviderRegistry_1.ProviderRegistry(
|
|
200
|
+
providerRegistry = new ProviderRegistry_1.ProviderRegistry(providers, options.providerLoader, flinkApp.ctx);
|
|
187
201
|
// Store provider registry in app context for handlers to access
|
|
188
202
|
flinkApp.ctx.oidcProviderRegistry = providerRegistry;
|
|
189
203
|
// Register OIDC handlers
|
|
@@ -192,7 +206,7 @@ function oidcPlugin(options) {
|
|
|
192
206
|
flinkApp.addHandler(InitiateOidc);
|
|
193
207
|
flinkApp.addHandler(CallbackOidc);
|
|
194
208
|
}
|
|
195
|
-
flink_1.log.info(`OIDC Plugin initialized with providers: ${configuredProviders.join(", ")}`);
|
|
209
|
+
flink_1.log.info(`OIDC Plugin initialized${configuredProviders.length > 0 ? ` with providers: ${configuredProviders.join(", ")}` : " with dynamic provider loader"}`);
|
|
196
210
|
}
|
|
197
211
|
catch (error) {
|
|
198
212
|
flink_1.log.error("Failed to initialize OIDC Plugin:", error);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import OidcProfile from "./schemas/OidcProfile";
|
|
2
2
|
import OidcTokenSet from "./schemas/OidcTokenSet";
|
|
3
3
|
import { OidcProviderConfig } from "./OidcProviderConfig";
|
|
4
|
+
import { TokenDeliveryMode } from "./utils/response-utils";
|
|
4
5
|
/**
|
|
5
6
|
* OIDC error information passed to onAuthError callback
|
|
6
7
|
*/
|
|
@@ -35,7 +36,9 @@ export interface AuthSuccessCallbackResponse {
|
|
|
35
36
|
token: string;
|
|
36
37
|
/**
|
|
37
38
|
* Optional redirect URL after authentication
|
|
38
|
-
*
|
|
39
|
+
*
|
|
40
|
+
* The plugin will attach the JWT token to this URL according to the
|
|
41
|
+
* top-level `tokenDelivery` option (fragment by default, or query).
|
|
39
42
|
*/
|
|
40
43
|
redirectUrl?: string;
|
|
41
44
|
}
|
|
@@ -56,7 +59,7 @@ export interface AuthErrorCallbackResponse {
|
|
|
56
59
|
* The onAuthSuccess callback receives the Flink context as a second parameter,
|
|
57
60
|
* allowing the application to generate JWT tokens using ctx.plugins.jwtAuth.createToken().
|
|
58
61
|
*/
|
|
59
|
-
export interface OidcPluginOptions {
|
|
62
|
+
export interface OidcPluginOptions<TCtx = any> {
|
|
60
63
|
/**
|
|
61
64
|
* OIDC provider configurations
|
|
62
65
|
* Key = provider name (used in URLs: /oidc/{provider}/initiate)
|
|
@@ -81,7 +84,7 @@ export interface OidcPluginOptions {
|
|
|
81
84
|
* }
|
|
82
85
|
* }
|
|
83
86
|
*/
|
|
84
|
-
providers
|
|
87
|
+
providers?: Record<string, OidcProviderConfig>;
|
|
85
88
|
/**
|
|
86
89
|
* Whether to store OIDC tokens for future API access
|
|
87
90
|
* If false, tokens are discarded after authentication (auth-only mode)
|
|
@@ -110,7 +113,7 @@ export interface OidcPluginOptions {
|
|
|
110
113
|
*
|
|
111
114
|
* Example:
|
|
112
115
|
* ```typescript
|
|
113
|
-
* onAuthSuccess: async ({ profile, claims, provider }, ctx) => {
|
|
116
|
+
* onAuthSuccess: async ({ profile, claims, provider, metadata }, ctx) => {
|
|
114
117
|
* // Find user by OIDC subject + issuer
|
|
115
118
|
* let user = await ctx.repos.userRepo.getOne({
|
|
116
119
|
* 'oidcConnections.subject': claims.sub,
|
|
@@ -158,12 +161,32 @@ export interface OidcPluginOptions {
|
|
|
158
161
|
* Provider name (e.g., "acme", "contoso")
|
|
159
162
|
*/
|
|
160
163
|
provider: string;
|
|
164
|
+
/**
|
|
165
|
+
* The redirectUri passed on the initiate request (stored in session)
|
|
166
|
+
* Use this to redirect back to the originating client application.
|
|
167
|
+
*
|
|
168
|
+
* Example:
|
|
169
|
+
* ```typescript
|
|
170
|
+
* return { user, token, redirectUrl: redirectUri || DEFAULT_REDIRECT_URL };
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
redirectUri: string;
|
|
174
|
+
/**
|
|
175
|
+
* Arbitrary metadata collected from meta.* query parameters on the initiate request.
|
|
176
|
+
* Opaque to the plugin — it only transports these values.
|
|
177
|
+
*
|
|
178
|
+
* Example: GET /oidc/acme/initiate?meta.intent=admin&meta.source=mobile
|
|
179
|
+
* Produces: { intent: "admin", source: "mobile" }
|
|
180
|
+
*
|
|
181
|
+
* Empty object ({}) if no meta.* params were provided.
|
|
182
|
+
*/
|
|
183
|
+
metadata: Record<string, string>;
|
|
161
184
|
/**
|
|
162
185
|
* OIDC tokens (only if storeTokens: true)
|
|
163
186
|
* Includes accessToken, idToken, refreshToken
|
|
164
187
|
*/
|
|
165
188
|
tokens?: OidcTokenSet;
|
|
166
|
-
}, ctx:
|
|
189
|
+
}, ctx: TCtx) => Promise<AuthSuccessCallbackResponse>;
|
|
167
190
|
/**
|
|
168
191
|
* Callback invoked on OIDC authentication errors
|
|
169
192
|
*
|
|
@@ -194,6 +217,17 @@ export interface OidcPluginOptions {
|
|
|
194
217
|
onAuthError?: (params: {
|
|
195
218
|
error: OidcError;
|
|
196
219
|
provider: string;
|
|
220
|
+
/**
|
|
221
|
+
* The redirectUri from the initiate request (if available)
|
|
222
|
+
* May be undefined for errors that occur before session lookup
|
|
223
|
+
* (e.g., IdP returns an error before we can retrieve the session)
|
|
224
|
+
*/
|
|
225
|
+
redirectUri?: string;
|
|
226
|
+
/**
|
|
227
|
+
* Metadata from the initiate request (meta.* query params, prefix stripped).
|
|
228
|
+
* Empty object ({}) if no meta.* params were provided or session was not found.
|
|
229
|
+
*/
|
|
230
|
+
metadata: Record<string, string>;
|
|
197
231
|
}) => Promise<AuthErrorCallbackResponse>;
|
|
198
232
|
/**
|
|
199
233
|
* Dynamic provider loader callback
|
|
@@ -204,11 +238,12 @@ export interface OidcPluginOptions {
|
|
|
204
238
|
* Use this for multi-tenant scenarios where each organization has different IdPs.
|
|
205
239
|
*
|
|
206
240
|
* @param providerName - Name of the provider to load
|
|
241
|
+
* @param ctx - Flink context with access to repos and plugins
|
|
207
242
|
* @returns Provider configuration or null if not found
|
|
208
243
|
*
|
|
209
244
|
* Example:
|
|
210
245
|
* ```typescript
|
|
211
|
-
* providerLoader: async (providerName) => {
|
|
246
|
+
* providerLoader: async (providerName, ctx) => {
|
|
212
247
|
* const config = await ctx.repos.oidcProviderRepo.getByName(providerName);
|
|
213
248
|
* if (!config || !config.enabled) {
|
|
214
249
|
* return null;
|
|
@@ -224,7 +259,7 @@ export interface OidcPluginOptions {
|
|
|
224
259
|
* }
|
|
225
260
|
* ```
|
|
226
261
|
*/
|
|
227
|
-
providerLoader?: (providerName: string) => Promise<OidcProviderConfig | null>;
|
|
262
|
+
providerLoader?: (providerName: string, ctx: TCtx) => Promise<OidcProviderConfig | null>;
|
|
228
263
|
/**
|
|
229
264
|
* Custom collection name for OIDC sessions
|
|
230
265
|
* Default: 'oidc_sessions'
|
|
@@ -246,7 +281,9 @@ export interface OidcPluginOptions {
|
|
|
246
281
|
sessionTTL?: number;
|
|
247
282
|
/**
|
|
248
283
|
* Encryption key for encrypting stored OIDC tokens
|
|
249
|
-
* If not provided, will be derived from first configured provider's client secret
|
|
284
|
+
* If not provided, will be derived from the first configured provider's client secret
|
|
285
|
+
*
|
|
286
|
+
* Required when using storeTokens with public clients (no clientSecret).
|
|
250
287
|
*
|
|
251
288
|
* Recommended: Use a dedicated encryption key from environment variables
|
|
252
289
|
* Must be at least 32 characters
|
|
@@ -254,6 +291,36 @@ export interface OidcPluginOptions {
|
|
|
254
291
|
* Example: process.env.OIDC_ENCRYPTION_KEY
|
|
255
292
|
*/
|
|
256
293
|
encryptionKey?: string;
|
|
294
|
+
/**
|
|
295
|
+
* Controls how the JWT token is delivered back to the client on the
|
|
296
|
+
* post-callback redirect (ignored when the caller uses `?response_type=json`).
|
|
297
|
+
*
|
|
298
|
+
* - `"fragment"` (default): appends `#token=...` to the redirectUrl. Best
|
|
299
|
+
* for web browsers — URL fragments are not sent to the server in HTTP
|
|
300
|
+
* requests, which preserves a small but real security property.
|
|
301
|
+
* - `"query"`: appends `?token=...` (or `&token=...`). Required for native
|
|
302
|
+
* mobile flows where the callback comes back via a custom URL scheme,
|
|
303
|
+
* because iOS `ASWebAuthenticationSession` strips URL fragments before
|
|
304
|
+
* delivering the URL to the app.
|
|
305
|
+
* - function: called once per callback to decide the delivery mode. Use
|
|
306
|
+
* this when one app serves both web and native clients. The function
|
|
307
|
+
* receives the resolved redirectUrl and the metadata bag from the
|
|
308
|
+
* initiate request, so the host can route by URL scheme, by
|
|
309
|
+
* `meta.intent`, or any other signal — the plugin makes no assumptions.
|
|
310
|
+
*
|
|
311
|
+
* Default: `"fragment"` (unchanged from prior versions).
|
|
312
|
+
*
|
|
313
|
+
* Example — route native (custom-scheme) clients to query, browsers to fragment:
|
|
314
|
+
* ```typescript
|
|
315
|
+
* tokenDelivery: ({ redirectUrl }) =>
|
|
316
|
+
* /^https?:/i.test(redirectUrl) ? "fragment" : "query",
|
|
317
|
+
* ```
|
|
318
|
+
*/
|
|
319
|
+
tokenDelivery?: TokenDeliveryMode | ((params: {
|
|
320
|
+
redirectUrl: string;
|
|
321
|
+
provider: string;
|
|
322
|
+
metadata: Record<string, string>;
|
|
323
|
+
}) => TokenDeliveryMode);
|
|
257
324
|
/**
|
|
258
325
|
* Whether to register OIDC routes automatically
|
|
259
326
|
* If false, you must manually handle OIDC flow
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"OidcPluginOptions.d.ts","sourceRoot":"","sources":["../src/OidcPluginOptions.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|
|
@@ -73,5 +84,14 @@ export interface OidcProviderConfig {
|
|
|
73
84
|
* e.g., { "department": "custom:department", "role": "custom:role" }
|
|
74
85
|
*/
|
|
75
86
|
claimMapping?: Record<string, string>;
|
|
87
|
+
/**
|
|
88
|
+
* Whether to fetch additional claims from the UserInfo endpoint.
|
|
89
|
+
* Default: true
|
|
90
|
+
*
|
|
91
|
+
* Set to `false` for IdPs whose UserInfo endpoint is unreliable or
|
|
92
|
+
* returns no additional data beyond what's already in the ID token.
|
|
93
|
+
* When disabled, profiles are built from ID token claims only.
|
|
94
|
+
*/
|
|
95
|
+
useUserInfoEndpoint?: boolean;
|
|
76
96
|
}
|
|
77
97
|
//# sourceMappingURL=OidcProviderConfig.d.ts.map
|
|
@@ -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;IAEtC;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACjC"}
|
|
@@ -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;
|
|
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,CA4QnE,CAAC;AAEF,eAAe,YAAY,CAAC"}
|
|
@@ -20,6 +20,7 @@ const state_utils_1 = require("../utils/state-utils");
|
|
|
20
20
|
const response_utils_1 = require("../utils/response-utils");
|
|
21
21
|
const encryption_utils_1 = require("../utils/encryption-utils");
|
|
22
22
|
const error_utils_1 = require("../utils/error-utils");
|
|
23
|
+
const log_1 = require("../log");
|
|
23
24
|
/**
|
|
24
25
|
* Route configuration
|
|
25
26
|
* This handler is registered programmatically by the plugin
|
|
@@ -38,19 +39,35 @@ exports.Route = {
|
|
|
38
39
|
const CallbackOidc = async ({ ctx, req }) => {
|
|
39
40
|
const { provider } = req.params;
|
|
40
41
|
const { code, state, error: oidcError, error_description, response_type } = req.query;
|
|
42
|
+
// Track session outside try/catch so redirectUri and metadata are available in error handling
|
|
43
|
+
let sessionRedirectUri;
|
|
44
|
+
let sessionMetadata = {};
|
|
41
45
|
try {
|
|
42
46
|
// Validate provider and response_type
|
|
43
47
|
(0, error_utils_1.validateProvider)(provider);
|
|
44
48
|
(0, error_utils_1.validateResponseType)(response_type);
|
|
49
|
+
log_1.oidcLog.debug(`Callback: provider="${provider}" hasCode=${!!code} hasState=${!!state} responseType="${response_type || "redirect"}"`);
|
|
45
50
|
// Check for OIDC provider errors (e.g., user denied access)
|
|
46
51
|
if (oidcError) {
|
|
52
|
+
log_1.oidcLog.debug(`Callback: IdP returned error="${oidcError}" description="${error_description || ""}"`);
|
|
47
53
|
const error = (0, error_utils_1.handleProviderError)({ error: oidcError, error_description });
|
|
54
|
+
// Try to retrieve session redirectUri (state may be available even on IdP errors)
|
|
55
|
+
if (state) {
|
|
56
|
+
const errorSession = await ctx.repos.oidcSessionRepo.getByState(state);
|
|
57
|
+
sessionRedirectUri = errorSession?.redirectUri;
|
|
58
|
+
sessionMetadata = errorSession?.metadata ?? {};
|
|
59
|
+
if (errorSession) {
|
|
60
|
+
await ctx.repos.oidcSessionRepo.deleteBySessionId(errorSession.sessionId);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
48
63
|
// Call onAuthError callback if provided
|
|
49
64
|
const { options } = ctx.plugins.oidc;
|
|
50
65
|
if (options.onAuthError) {
|
|
51
66
|
const errorResult = await options.onAuthError({
|
|
52
67
|
error,
|
|
53
68
|
provider,
|
|
69
|
+
redirectUri: sessionRedirectUri,
|
|
70
|
+
metadata: sessionMetadata,
|
|
54
71
|
});
|
|
55
72
|
if (errorResult.redirectUrl) {
|
|
56
73
|
return {
|
|
@@ -72,16 +89,22 @@ const CallbackOidc = async ({ ctx, req }) => {
|
|
|
72
89
|
// Find OIDC session by state
|
|
73
90
|
const session = await ctx.repos.oidcSessionRepo.getByState(state);
|
|
74
91
|
if (!session) {
|
|
92
|
+
log_1.oidcLog.debug(`Callback: no session found for state (may be expired)`);
|
|
75
93
|
throw (0, error_utils_1.createOidcError)(error_utils_1.OidcErrorCodes.SESSION_EXPIRED, "OIDC session not found or expired. Please try logging in again.", { state });
|
|
76
94
|
}
|
|
95
|
+
log_1.oidcLog.debug(`Callback: session found sessionId=${session.sessionId} provider="${session.provider}" redirectUri="${session.redirectUri}"`);
|
|
77
96
|
// Validate state parameter (CSRF protection)
|
|
78
97
|
if (!(0, state_utils_1.validateState)(state, session.state)) {
|
|
79
98
|
throw (0, error_utils_1.createOidcError)(error_utils_1.OidcErrorCodes.INVALID_STATE, "Invalid state parameter. Possible CSRF attack detected.", {
|
|
80
99
|
providedState: state.substring(0, 10) + "...",
|
|
81
100
|
});
|
|
82
101
|
}
|
|
102
|
+
// Track redirectUri and metadata for error handling
|
|
103
|
+
sessionRedirectUri = session.redirectUri;
|
|
104
|
+
sessionMetadata = session.metadata ?? {};
|
|
83
105
|
// Delete session immediately after validation (one-time use)
|
|
84
106
|
await ctx.repos.oidcSessionRepo.deleteBySessionId(session.sessionId);
|
|
107
|
+
log_1.oidcLog.debug(`Callback: state validated, session deleted (one-time use)`);
|
|
85
108
|
// Get plugin options
|
|
86
109
|
const { options } = ctx.plugins.oidc;
|
|
87
110
|
// Get provider instance
|
|
@@ -97,13 +120,17 @@ const CallbackOidc = async ({ ctx, req }) => {
|
|
|
97
120
|
state: session.state,
|
|
98
121
|
nonce: session.nonce,
|
|
99
122
|
});
|
|
100
|
-
|
|
101
|
-
|
|
123
|
+
log_1.oidcLog.debug(`Callback: token exchange successful`, `sub="${tokenSet.claims.sub}"`, `iss="${tokenSet.claims.iss}"`, `email="${tokenSet.claims.email || "(none)"}"`, `hasRefreshToken=${!!tokenSet.refreshToken}`, `expiresIn=${tokenSet.expiresIn ?? "(none)"}s`);
|
|
124
|
+
// Build user profile from ID token and (optionally) UserInfo
|
|
125
|
+
const profile = await oidcProvider.buildProfile(tokenSet, oidcProvider.config.useUserInfoEndpoint ?? true);
|
|
126
|
+
log_1.oidcLog.debug(`Callback: profile built id="${profile.id}" email="${profile.email || "(none)"}" name="${profile.name || "(none)"}"`);
|
|
102
127
|
// Call onAuthSuccess callback to create/link user and generate JWT token
|
|
103
128
|
const authSuccessParams = {
|
|
104
129
|
profile,
|
|
105
130
|
claims: tokenSet.claims,
|
|
106
131
|
provider,
|
|
132
|
+
redirectUri: session.redirectUri,
|
|
133
|
+
metadata: sessionMetadata,
|
|
107
134
|
...(options.storeTokens ? { tokens: tokenSet } : {}),
|
|
108
135
|
};
|
|
109
136
|
let authResult;
|
|
@@ -121,6 +148,8 @@ const CallbackOidc = async ({ ctx, req }) => {
|
|
|
121
148
|
const errorResult = await options.onAuthError({
|
|
122
149
|
error: oidcError,
|
|
123
150
|
provider,
|
|
151
|
+
redirectUri: session.redirectUri,
|
|
152
|
+
metadata: sessionMetadata,
|
|
124
153
|
});
|
|
125
154
|
if (errorResult.redirectUrl) {
|
|
126
155
|
return {
|
|
@@ -140,7 +169,7 @@ const CallbackOidc = async ({ ctx, req }) => {
|
|
|
140
169
|
// Store OIDC connection if token storage is enabled
|
|
141
170
|
if (options.storeTokens && user && user._id) {
|
|
142
171
|
// Get encryption secret (use provider client secret or global encryption key)
|
|
143
|
-
const staticProviderConfig = options.providers[provider];
|
|
172
|
+
const staticProviderConfig = options.providers?.[provider];
|
|
144
173
|
const encryptionSecret = options.encryptionKey || staticProviderConfig?.clientSecret;
|
|
145
174
|
if (!encryptionSecret) {
|
|
146
175
|
flink_1.log.warn("No encryption secret available, tokens will not be stored");
|
|
@@ -155,7 +184,8 @@ const CallbackOidc = async ({ ctx, req }) => {
|
|
|
155
184
|
// Create or update OIDC connection
|
|
156
185
|
const existingConnection = await ctx.repos.oidcConnectionRepo.findByUserAndProvider(user._id, provider);
|
|
157
186
|
if (existingConnection) {
|
|
158
|
-
|
|
187
|
+
log_1.oidcLog.debug(`Callback: updating existing connection for userId=${user._id} provider="${provider}"`);
|
|
188
|
+
await ctx.repos.oidcConnectionRepo.updateById(existingConnection._id, {
|
|
159
189
|
accessToken: encryptedAccessToken,
|
|
160
190
|
idToken: encryptedIdToken,
|
|
161
191
|
refreshToken: encryptedRefreshToken,
|
|
@@ -165,6 +195,7 @@ const CallbackOidc = async ({ ctx, req }) => {
|
|
|
165
195
|
});
|
|
166
196
|
}
|
|
167
197
|
else {
|
|
198
|
+
log_1.oidcLog.debug(`Callback: creating new connection for userId=${user._id} provider="${provider}" subject="${tokenSet.claims.sub}"`);
|
|
168
199
|
await ctx.repos.oidcConnectionRepo.create({
|
|
169
200
|
userId: user._id,
|
|
170
201
|
provider,
|
|
@@ -182,8 +213,20 @@ const CallbackOidc = async ({ ctx, req }) => {
|
|
|
182
213
|
}
|
|
183
214
|
}
|
|
184
215
|
}
|
|
216
|
+
const finalRedirectUrl = redirectUrl || session.redirectUri;
|
|
217
|
+
log_1.oidcLog.debug(`Callback: auth complete, responding via ${response_type === "json" ? "JSON" : `redirect to "${finalRedirectUrl}"`}`);
|
|
218
|
+
// Resolve token delivery mode: literal, function, or default "fragment".
|
|
219
|
+
// The plugin stays a dumb transport — the host app makes the policy decision.
|
|
220
|
+
const tokenDeliverySetting = options.tokenDelivery ?? "fragment";
|
|
221
|
+
const resolvedTokenDelivery = typeof tokenDeliverySetting === "function"
|
|
222
|
+
? tokenDeliverySetting({
|
|
223
|
+
redirectUrl: finalRedirectUrl,
|
|
224
|
+
provider,
|
|
225
|
+
metadata: sessionMetadata,
|
|
226
|
+
})
|
|
227
|
+
: tokenDeliverySetting;
|
|
185
228
|
// Return JWT token in requested format
|
|
186
|
-
return (0, response_utils_1.formatTokenResponse)(token, user,
|
|
229
|
+
return (0, response_utils_1.formatTokenResponse)(token, user, finalRedirectUrl, response_type, resolvedTokenDelivery);
|
|
187
230
|
}
|
|
188
231
|
catch (error) {
|
|
189
232
|
flink_1.log.error("OIDC callback error:", error);
|
|
@@ -196,6 +239,8 @@ const CallbackOidc = async ({ ctx, req }) => {
|
|
|
196
239
|
const errorResult = await options.onAuthError({
|
|
197
240
|
error,
|
|
198
241
|
provider,
|
|
242
|
+
redirectUri: sessionRedirectUri,
|
|
243
|
+
metadata: sessionMetadata,
|
|
199
244
|
});
|
|
200
245
|
if (errorResult.redirectUrl) {
|
|
201
246
|
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;
|
|
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"}
|
|
@@ -17,6 +17,7 @@ const flink_1 = require("@flink-app/flink");
|
|
|
17
17
|
const state_utils_1 = require("../utils/state-utils");
|
|
18
18
|
const error_utils_1 = require("../utils/error-utils");
|
|
19
19
|
const openid_client_1 = require("openid-client");
|
|
20
|
+
const log_1 = require("../log");
|
|
20
21
|
/**
|
|
21
22
|
* Route configuration
|
|
22
23
|
* This handler is registered programmatically by the plugin
|
|
@@ -37,6 +38,7 @@ const InitiateOidc = async ({ ctx, req }) => {
|
|
|
37
38
|
try {
|
|
38
39
|
// Validate provider name format
|
|
39
40
|
(0, error_utils_1.validateProvider)(provider);
|
|
41
|
+
log_1.oidcLog.debug(`Initiate: provider="${provider}" redirectUri="${redirectUri || "(not set)"}"`);
|
|
40
42
|
// Get provider registry from context
|
|
41
43
|
const providerRegistry = ctx.oidcProviderRegistry;
|
|
42
44
|
if (!providerRegistry) {
|
|
@@ -47,8 +49,16 @@ const InitiateOidc = async ({ ctx, req }) => {
|
|
|
47
49
|
// Get plugin options
|
|
48
50
|
const { options } = ctx.plugins.oidc;
|
|
49
51
|
// Determine redirect URI (use provided or default to callback URL)
|
|
50
|
-
const staticProviderConfig = options.providers[provider];
|
|
52
|
+
const staticProviderConfig = options.providers?.[provider];
|
|
51
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
|
+
}
|
|
61
|
+
log_1.oidcLog.debug(`Initiate: resolved redirectUri="${finalRedirectUri}" (source: ${redirectUri ? "query param" : staticProviderConfig?.callbackUrl ? "provider config" : "auto-detected"})`);
|
|
52
62
|
// Generate cryptographically secure parameters
|
|
53
63
|
const state = (0, state_utils_1.generateState)();
|
|
54
64
|
const sessionId = (0, state_utils_1.generateSessionId)();
|
|
@@ -62,14 +72,17 @@ const InitiateOidc = async ({ ctx, req }) => {
|
|
|
62
72
|
nonce,
|
|
63
73
|
provider,
|
|
64
74
|
redirectUri: finalRedirectUri,
|
|
75
|
+
metadata,
|
|
65
76
|
createdAt: new Date(),
|
|
66
77
|
});
|
|
78
|
+
log_1.oidcLog.debug(`Initiate: session created sessionId=${sessionId}`);
|
|
67
79
|
// Build authorization URL with PKCE and nonce
|
|
68
80
|
const authorizationUrl = await oidcProvider.getAuthorizationUrl({
|
|
69
81
|
state,
|
|
70
82
|
codeVerifier,
|
|
71
83
|
nonce,
|
|
72
84
|
});
|
|
85
|
+
log_1.oidcLog.debug(`Initiate: redirecting to IdP authorization URL: ${authorizationUrl}`);
|
|
73
86
|
// Redirect user to provider's authorization page
|
|
74
87
|
return {
|
|
75
88
|
status: 302,
|
package/dist/index.d.ts
CHANGED
|
@@ -22,6 +22,6 @@ export { ProviderRegistry } from "./providers/ProviderRegistry";
|
|
|
22
22
|
export { generateState, generateSessionId, generateNonce, validateState } from "./utils/state-utils";
|
|
23
23
|
export { encryptToken, decryptToken, validateEncryptionSecret } from "./utils/encryption-utils";
|
|
24
24
|
export { mapClaimsToProfile, extractCustomClaims } from "./utils/claims-mapper";
|
|
25
|
-
export { formatTokenResponse } from "./utils/response-utils";
|
|
25
|
+
export { formatTokenResponse, TokenDeliveryMode } from "./utils/response-utils";
|
|
26
26
|
export { createOidcError, validateProvider, handleProviderError, OidcErrorCodes } from "./utils/error-utils";
|
|
27
27
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,2BAA2B,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAC3H,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGxD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAGvE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAGhE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACrG,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAChG,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,2BAA2B,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAC3H,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAG1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAGxD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACrE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAGvE,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAGhE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACrG,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAChG,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Named logger for the OIDC plugin.
|
|
3
|
+
*
|
|
4
|
+
* Enables independent log level control via flink.config.js:
|
|
5
|
+
* ```js
|
|
6
|
+
* module.exports = {
|
|
7
|
+
* logging: {
|
|
8
|
+
* components: {
|
|
9
|
+
* "flink.oidc": "debug"
|
|
10
|
+
* }
|
|
11
|
+
* }
|
|
12
|
+
* };
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* Or at runtime:
|
|
16
|
+
* ```ts
|
|
17
|
+
* FlinkLogFactory.setComponentLevel("flink.oidc", "debug");
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare const oidcLog: import("@flink-app/flink").ComponentLogger;
|
|
21
|
+
/**
|
|
22
|
+
* Mask a secret value for safe logging.
|
|
23
|
+
* Shows the first 4 characters followed by **** to help identify which secret
|
|
24
|
+
* is in use without exposing the full value.
|
|
25
|
+
*/
|
|
26
|
+
export declare function maskSecret(value: string | undefined): string;
|
|
27
|
+
//# sourceMappingURL=log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,OAAO,4CAA6C,CAAC;AAElE;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAI5D"}
|
package/dist/log.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.oidcLog = void 0;
|
|
4
|
+
exports.maskSecret = maskSecret;
|
|
5
|
+
const flink_1 = require("@flink-app/flink");
|
|
6
|
+
/**
|
|
7
|
+
* Named logger for the OIDC plugin.
|
|
8
|
+
*
|
|
9
|
+
* Enables independent log level control via flink.config.js:
|
|
10
|
+
* ```js
|
|
11
|
+
* module.exports = {
|
|
12
|
+
* logging: {
|
|
13
|
+
* components: {
|
|
14
|
+
* "flink.oidc": "debug"
|
|
15
|
+
* }
|
|
16
|
+
* }
|
|
17
|
+
* };
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* Or at runtime:
|
|
21
|
+
* ```ts
|
|
22
|
+
* FlinkLogFactory.setComponentLevel("flink.oidc", "debug");
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
exports.oidcLog = flink_1.FlinkLogFactory.createLogger("flink.oidc");
|
|
26
|
+
/**
|
|
27
|
+
* Mask a secret value for safe logging.
|
|
28
|
+
* Shows the first 4 characters followed by **** to help identify which secret
|
|
29
|
+
* is in use without exposing the full value.
|
|
30
|
+
*/
|
|
31
|
+
function maskSecret(value) {
|
|
32
|
+
if (!value)
|
|
33
|
+
return "(not set)";
|
|
34
|
+
if (value.length <= 4)
|
|
35
|
+
return "****";
|
|
36
|
+
return `${value.slice(0, 4)}****`;
|
|
37
|
+
}
|