@flink-app/oidc-plugin 2.0.0-alpha.79 → 2.0.0-alpha.80

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,13 @@
1
1
  # @flink-app/oidc-plugin
2
2
 
3
+ ## 2.0.0-alpha.80
4
+
5
+ ### Patch Changes
6
+
7
+ - Add flink.oidc named logger with debug instrumentation across init, provider resolution, and auth flow
8
+ - @flink-app/flink@2.0.0-alpha.80
9
+ - @flink-app/jwt-auth-plugin@2.0.0-alpha.80
10
+
3
11
  ## 2.0.0-alpha.79
4
12
 
5
13
  ### Patch Changes
@@ -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,IAAI,GAAG,GAAG,EAAE,OAAO,EAAE,iBAAiB,CAAC,IAAI,CAAC,GAAG,WAAW,CAmNpF"}
1
+ {"version":3,"file":"OidcPlugin.d.ts","sourceRoot":"","sources":["../src/OidcPlugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,WAAW,EAAO,MAAM,kBAAkB,CAAC;AAG9D,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,CA2OpF"}
@@ -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");
@@ -135,6 +136,7 @@ function oidcPlugin(options) {
135
136
  `(authorizationEndpoint, tokenEndpoint, jwksUri)`);
136
137
  }
137
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}` : "");
138
140
  }
139
141
  if (!options.onAuthSuccess) {
140
142
  throw new Error("OIDC Plugin: onAuthSuccess callback is required");
@@ -147,8 +149,12 @@ function oidcPlugin(options) {
147
149
  if (providerWithSecret) {
148
150
  encryptionKey = providers[providerWithSecret].clientSecret;
149
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`);
150
153
  }
151
154
  }
155
+ else {
156
+ log_1.oidcLog.debug(`Encryption key: ${(0, log_1.maskSecret)(encryptionKey)} (explicit)`);
157
+ }
152
158
  // Encryption key is required when storing tokens
153
159
  if (options.storeTokens) {
154
160
  if (!encryptionKey || encryptionKey.length < 32) {
@@ -181,6 +187,7 @@ function oidcPlugin(options) {
181
187
  // Initialize repositories
182
188
  const sessionsCollectionName = options.sessionsCollectionName || "oidc_sessions";
183
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");
184
191
  sessionRepo = new OidcSessionRepo_1.default(sessionsCollectionName, db);
185
192
  connectionRepo = new OidcConnectionRepo_1.default(connectionsCollectionName, db);
186
193
  flinkApp.addRepo("oidcSessionRepo", sessionRepo);
@@ -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;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;;;;;;GAMG;AACH,QAAA,MAAM,YAAY,EAAE,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,eAAe,CAmOnE,CAAC;AAEF,eAAe,YAAY,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,CA4PnE,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
@@ -44,8 +45,10 @@ const CallbackOidc = async ({ ctx, req }) => {
44
45
  // Validate provider and response_type
45
46
  (0, error_utils_1.validateProvider)(provider);
46
47
  (0, error_utils_1.validateResponseType)(response_type);
48
+ log_1.oidcLog.debug(`Callback: provider="${provider}" hasCode=${!!code} hasState=${!!state} responseType="${response_type || "redirect"}"`);
47
49
  // Check for OIDC provider errors (e.g., user denied access)
48
50
  if (oidcError) {
51
+ log_1.oidcLog.debug(`Callback: IdP returned error="${oidcError}" description="${error_description || ""}"`);
49
52
  const error = (0, error_utils_1.handleProviderError)({ error: oidcError, error_description });
50
53
  // Try to retrieve session redirectUri (state may be available even on IdP errors)
51
54
  if (state) {
@@ -83,8 +86,10 @@ const CallbackOidc = async ({ ctx, req }) => {
83
86
  // Find OIDC session by state
84
87
  const session = await ctx.repos.oidcSessionRepo.getByState(state);
85
88
  if (!session) {
89
+ log_1.oidcLog.debug(`Callback: no session found for state (may be expired)`);
86
90
  throw (0, error_utils_1.createOidcError)(error_utils_1.OidcErrorCodes.SESSION_EXPIRED, "OIDC session not found or expired. Please try logging in again.", { state });
87
91
  }
92
+ log_1.oidcLog.debug(`Callback: session found sessionId=${session.sessionId} provider="${session.provider}" redirectUri="${session.redirectUri}"`);
88
93
  // Validate state parameter (CSRF protection)
89
94
  if (!(0, state_utils_1.validateState)(state, session.state)) {
90
95
  throw (0, error_utils_1.createOidcError)(error_utils_1.OidcErrorCodes.INVALID_STATE, "Invalid state parameter. Possible CSRF attack detected.", {
@@ -95,6 +100,7 @@ const CallbackOidc = async ({ ctx, req }) => {
95
100
  sessionRedirectUri = session.redirectUri;
96
101
  // Delete session immediately after validation (one-time use)
97
102
  await ctx.repos.oidcSessionRepo.deleteBySessionId(session.sessionId);
103
+ log_1.oidcLog.debug(`Callback: state validated, session deleted (one-time use)`);
98
104
  // Get plugin options
99
105
  const { options } = ctx.plugins.oidc;
100
106
  // Get provider instance
@@ -104,15 +110,20 @@ const CallbackOidc = async ({ ctx, req }) => {
104
110
  }
105
111
  const oidcProvider = await providerRegistry.getProvider(provider);
106
112
  // Exchange authorization code for tokens with PKCE validation
113
+ log_1.oidcLog.debug(`Callback: exchanging authorization code for tokens`);
107
114
  const tokenSet = await oidcProvider.exchangeCodeForToken({
108
115
  code,
109
116
  codeVerifier: session.codeVerifier,
110
117
  state: session.state,
111
118
  nonce: session.nonce,
112
119
  });
120
+ 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`);
113
121
  // Build user profile from ID token and UserInfo
122
+ log_1.oidcLog.debug(`Callback: building user profile`);
114
123
  const profile = await oidcProvider.buildProfile(tokenSet, true);
124
+ log_1.oidcLog.debug(`Callback: profile built id="${profile.id}" email="${profile.email || "(none)"}" name="${profile.name || "(none)"}"`);
115
125
  // Call onAuthSuccess callback to create/link user and generate JWT token
126
+ log_1.oidcLog.debug(`Callback: calling onAuthSuccess`);
116
127
  const authSuccessParams = {
117
128
  profile,
118
129
  claims: tokenSet.claims,
@@ -147,6 +158,7 @@ const CallbackOidc = async ({ ctx, req }) => {
147
158
  }
148
159
  return (0, flink_1.internalServerError)("Authentication failed. Please try again.");
149
160
  }
161
+ log_1.oidcLog.debug(`Callback: onAuthSuccess completed`);
150
162
  // Extract user and JWT token from callback result
151
163
  const { user, token, redirectUrl } = authResult;
152
164
  if (!token) {
@@ -197,8 +209,10 @@ const CallbackOidc = async ({ ctx, req }) => {
197
209
  }
198
210
  }
199
211
  }
212
+ const finalRedirectUrl = redirectUrl || session.redirectUri;
213
+ log_1.oidcLog.debug(`Callback: auth complete, responding via ${response_type === "json" ? "JSON" : `redirect to "${finalRedirectUrl}"`}`);
200
214
  // Return JWT token in requested format
201
- return (0, response_utils_1.formatTokenResponse)(token, user, redirectUrl || session.redirectUri, response_type);
215
+ return (0, response_utils_1.formatTokenResponse)(token, user, finalRedirectUrl, response_type);
202
216
  }
203
217
  catch (error) {
204
218
  flink_1.log.error("OIDC callback error:", error);
@@ -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;AAKzD;;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,CAiEnE,CAAC;AAEF,eAAe,YAAY,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,CAyEnE,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) {
@@ -49,6 +51,7 @@ const InitiateOidc = async ({ ctx, req }) => {
49
51
  // Determine redirect URI (use provided or default to callback URL)
50
52
  const staticProviderConfig = options.providers?.[provider];
51
53
  const finalRedirectUri = redirectUri || staticProviderConfig?.callbackUrl || `${req.protocol}://${req.get("host")}/oidc/${provider}/callback`;
54
+ log_1.oidcLog.debug(`Initiate: resolved redirectUri="${finalRedirectUri}" (source: ${redirectUri ? "query param" : staticProviderConfig?.callbackUrl ? "provider config" : "auto-detected"})`);
52
55
  // Generate cryptographically secure parameters
53
56
  const state = (0, state_utils_1.generateState)();
54
57
  const sessionId = (0, state_utils_1.generateSessionId)();
@@ -64,12 +67,14 @@ const InitiateOidc = async ({ ctx, req }) => {
64
67
  redirectUri: finalRedirectUri,
65
68
  createdAt: new Date(),
66
69
  });
70
+ log_1.oidcLog.debug(`Initiate: session created sessionId=${sessionId}`);
67
71
  // Build authorization URL with PKCE and nonce
68
72
  const authorizationUrl = await oidcProvider.getAuthorizationUrl({
69
73
  state,
70
74
  codeVerifier,
71
75
  nonce,
72
76
  });
77
+ log_1.oidcLog.debug(`Initiate: redirecting to IdP authorization URL`);
73
78
  // Redirect user to provider's authorization page
74
79
  return {
75
80
  status: 302,
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
+ }
@@ -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,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"}
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;AAI3D;;;;;;;;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;IAiD9D;;;;;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"}
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ProviderRegistry = void 0;
4
4
  const OidcProvider_1 = require("./OidcProvider");
5
5
  const error_utils_1 = require("../utils/error-utils");
6
+ const log_1 = require("../log");
6
7
  /**
7
8
  * Provider registry for managing OIDC provider instances
8
9
  *
@@ -37,23 +38,31 @@ class ProviderRegistry {
37
38
  // Check cache first
38
39
  const cachedProvider = this.providerInstances.get(providerName);
39
40
  if (cachedProvider) {
41
+ log_1.oidcLog.debug(`Provider "${providerName}": using cached instance`);
40
42
  return cachedProvider;
41
43
  }
42
44
  // Try static configuration
43
45
  let config = this.staticProviders[providerName] || null;
46
+ let configSource = "static";
44
47
  // Try dynamic loader if not in static config
45
48
  if (!config && this.providerLoader) {
49
+ log_1.oidcLog.debug(`Provider "${providerName}": not in static config, invoking providerLoader`);
46
50
  config = await this.providerLoader(providerName, this.ctx);
51
+ configSource = "dynamic";
47
52
  }
48
53
  if (!config) {
54
+ log_1.oidcLog.debug(`Provider "${providerName}": not found (static=[${Object.keys(this.staticProviders).join(", ")}], loader=${!!this.providerLoader})`);
49
55
  throw (0, error_utils_1.createOidcError)(error_utils_1.OidcErrorCodes.PROVIDER_NOT_CONFIGURED, `OIDC provider '${providerName}' is not configured`, {
50
56
  providerName,
51
57
  availableProviders: Object.keys(this.staticProviders),
52
58
  });
53
59
  }
60
+ log_1.oidcLog.debug(`Provider "${providerName}" resolved via ${configSource}:`, `issuer=${config.issuer}`, `clientId=${config.clientId}`, `clientSecret=${(0, log_1.maskSecret)(config.clientSecret)}`, `callbackUrl=${config.callbackUrl}`, config.discoveryUrl ? `discoveryUrl=${config.discoveryUrl}` : `authorizationEndpoint=${config.authorizationEndpoint}`, `scope=${(config.scope || ["openid", "email", "profile"]).join(" ")}`);
54
61
  // Create and initialize provider
62
+ log_1.oidcLog.debug(`Provider "${providerName}": initializing OIDC client`);
55
63
  const provider = new OidcProvider_1.OidcProvider(config);
56
64
  await provider.initialize();
65
+ log_1.oidcLog.debug(`Provider "${providerName}": initialized successfully`);
57
66
  // Cache the instance
58
67
  this.providerInstances.set(providerName, provider);
59
68
  return provider;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flink-app/oidc-plugin",
3
- "version": "2.0.0-alpha.79",
3
+ "version": "2.0.0-alpha.80",
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.79"
14
+ "@flink-app/jwt-auth-plugin": "2.0.0-alpha.80"
15
15
  },
16
16
  "peerDependencies": {
17
- "@flink-app/flink": ">=2.0.0-alpha.79",
17
+ "@flink-app/flink": ">=2.0.0-alpha.80",
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/jwt-auth-plugin": "2.0.0-alpha.79",
31
- "@flink-app/test-utils": "2.0.0-alpha.79",
32
- "@flink-app/flink": "2.0.0-alpha.79"
30
+ "@flink-app/jwt-auth-plugin": "2.0.0-alpha.80",
31
+ "@flink-app/test-utils": "2.0.0-alpha.80",
32
+ "@flink-app/flink": "2.0.0-alpha.80"
33
33
  },
34
34
  "scripts": {
35
35
  "test": "jasmine-ts --config=./spec/support/jasmine.json",
package/src/OidcPlugin.ts CHANGED
@@ -1,11 +1,12 @@
1
1
  import { FlinkApp, FlinkPlugin, log } from "@flink-app/flink";
2
+ import { oidcLog, maskSecret } from "./log";
2
3
  import { Db } from "mongodb";
3
4
  import { OidcPluginOptions } from "./OidcPluginOptions";
4
5
  import { OidcPluginContext } from "./OidcPluginContext";
5
6
  import { OidcInternalContext } from "./OidcInternalContext";
6
7
  import OidcSessionRepo from "./repos/OidcSessionRepo";
7
8
  import OidcConnectionRepo from "./repos/OidcConnectionRepo";
8
- import { encryptToken, decryptToken, validateEncryptionSecret } from "./utils/encryption-utils";
9
+ import { decryptToken, validateEncryptionSecret } from "./utils/encryption-utils";
9
10
  import OidcConnection from "./schemas/OidcConnection";
10
11
  import { ProviderRegistry } from "./providers/ProviderRegistry";
11
12
  import * as InitiateOidc from "./handlers/InitiateOidc";
@@ -117,6 +118,17 @@ export function oidcPlugin<TCtx = any>(options: OidcPluginOptions<TCtx>): FlinkP
117
118
  );
118
119
  }
119
120
  }
121
+
122
+ oidcLog.debug(
123
+ `Provider "${providerName}" config:`,
124
+ `issuer=${providerConfig.issuer}`,
125
+ `clientId=${providerConfig.clientId}`,
126
+ `clientSecret=${maskSecret(providerConfig.clientSecret)}`,
127
+ `callbackUrl=${providerConfig.callbackUrl}`,
128
+ providerConfig.discoveryUrl ? `discoveryUrl=${providerConfig.discoveryUrl}` : `authorizationEndpoint=${providerConfig.authorizationEndpoint} tokenEndpoint=${providerConfig.tokenEndpoint}`,
129
+ `scope=${(providerConfig.scope || ["openid", "email", "profile"]).join(" ")}`,
130
+ providerConfig.tokenEndpointAuthMethod ? `authMethod=${providerConfig.tokenEndpointAuthMethod}` : ""
131
+ );
120
132
  }
121
133
 
122
134
  if (!options.onAuthSuccess) {
@@ -133,7 +145,10 @@ export function oidcPlugin<TCtx = any>(options: OidcPluginOptions<TCtx>): FlinkP
133
145
  log.warn(
134
146
  "OIDC Plugin: No encryption key provided, deriving from client secret. " + "For better security, provide a dedicated encryptionKey in options."
135
147
  );
148
+ oidcLog.debug(`Encryption key derived from provider "${providerWithSecret}" clientSecret`);
136
149
  }
150
+ } else {
151
+ oidcLog.debug(`Encryption key: ${maskSecret(encryptionKey)} (explicit)`);
137
152
  }
138
153
 
139
154
  // Encryption key is required when storing tokens
@@ -175,6 +190,16 @@ export function oidcPlugin<TCtx = any>(options: OidcPluginOptions<TCtx>): FlinkP
175
190
  const sessionsCollectionName = options.sessionsCollectionName || "oidc_sessions";
176
191
  const connectionsCollectionName = options.connectionsCollectionName || "oidc_connections";
177
192
 
193
+ oidcLog.debug(
194
+ `Init: sessionsCollection=${sessionsCollectionName}`,
195
+ `connectionsCollection=${connectionsCollectionName}`,
196
+ `storeTokens=${!!options.storeTokens}`,
197
+ `sessionTTL=${options.sessionTTL ?? 600}s`,
198
+ `registerRoutes=${options.registerRoutes !== false}`,
199
+ configuredProviders.length > 0 ? `staticProviders=[${configuredProviders.join(", ")}]` : "staticProviders=[] (dynamic only)",
200
+ options.providerLoader ? "providerLoader=yes" : "providerLoader=no"
201
+ );
202
+
178
203
  sessionRepo = new OidcSessionRepo(sessionsCollectionName, db);
179
204
  connectionRepo = new OidcConnectionRepo(connectionsCollectionName, db);
180
205
 
@@ -19,6 +19,7 @@ import { validateState } from "../utils/state-utils";
19
19
  import { formatTokenResponse } from "../utils/response-utils";
20
20
  import { encryptToken } from "../utils/encryption-utils";
21
21
  import { validateProvider, validateResponseType, createOidcError, OidcErrorCodes, handleProviderError } from "../utils/error-utils";
22
+ import { oidcLog } from "../log";
22
23
 
23
24
  /**
24
25
  * Path parameters for the handler
@@ -56,8 +57,11 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
56
57
  validateProvider(provider);
57
58
  validateResponseType(response_type);
58
59
 
60
+ oidcLog.debug(`Callback: provider="${provider}" hasCode=${!!code} hasState=${!!state} responseType="${response_type || "redirect"}"`);
61
+
59
62
  // Check for OIDC provider errors (e.g., user denied access)
60
63
  if (oidcError) {
64
+ oidcLog.debug(`Callback: IdP returned error="${oidcError}" description="${error_description || ""}"`);
61
65
  const error = handleProviderError({ error: oidcError, error_description });
62
66
 
63
67
  // Try to retrieve session redirectUri (state may be available even on IdP errors)
@@ -102,9 +106,12 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
102
106
  const session = await ctx.repos.oidcSessionRepo.getByState(state);
103
107
 
104
108
  if (!session) {
109
+ oidcLog.debug(`Callback: no session found for state (may be expired)`);
105
110
  throw createOidcError(OidcErrorCodes.SESSION_EXPIRED, "OIDC session not found or expired. Please try logging in again.", { state });
106
111
  }
107
112
 
113
+ oidcLog.debug(`Callback: session found sessionId=${session.sessionId} provider="${session.provider}" redirectUri="${session.redirectUri}"`);
114
+
108
115
  // Validate state parameter (CSRF protection)
109
116
  if (!validateState(state, session.state)) {
110
117
  throw createOidcError(OidcErrorCodes.INVALID_STATE, "Invalid state parameter. Possible CSRF attack detected.", {
@@ -117,6 +124,7 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
117
124
 
118
125
  // Delete session immediately after validation (one-time use)
119
126
  await ctx.repos.oidcSessionRepo.deleteBySessionId(session.sessionId);
127
+ oidcLog.debug(`Callback: state validated, session deleted (one-time use)`);
120
128
 
121
129
  // Get plugin options
122
130
  const { options } = ctx.plugins.oidc;
@@ -130,6 +138,7 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
130
138
  const oidcProvider = await providerRegistry.getProvider(provider);
131
139
 
132
140
  // Exchange authorization code for tokens with PKCE validation
141
+ oidcLog.debug(`Callback: exchanging authorization code for tokens`);
133
142
  const tokenSet = await oidcProvider.exchangeCodeForToken({
134
143
  code,
135
144
  codeVerifier: session.codeVerifier,
@@ -137,10 +146,22 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
137
146
  nonce: session.nonce,
138
147
  });
139
148
 
149
+ oidcLog.debug(
150
+ `Callback: token exchange successful`,
151
+ `sub="${tokenSet.claims.sub}"`,
152
+ `iss="${tokenSet.claims.iss}"`,
153
+ `email="${tokenSet.claims.email || "(none)"}"`,
154
+ `hasRefreshToken=${!!tokenSet.refreshToken}`,
155
+ `expiresIn=${tokenSet.expiresIn ?? "(none)"}s`
156
+ );
157
+
140
158
  // Build user profile from ID token and UserInfo
159
+ oidcLog.debug(`Callback: building user profile`);
141
160
  const profile = await oidcProvider.buildProfile(tokenSet, true);
161
+ oidcLog.debug(`Callback: profile built id="${profile.id}" email="${profile.email || "(none)"}" name="${profile.name || "(none)"}"`);
142
162
 
143
163
  // Call onAuthSuccess callback to create/link user and generate JWT token
164
+ oidcLog.debug(`Callback: calling onAuthSuccess`);
144
165
  const authSuccessParams = {
145
166
  profile,
146
167
  claims: tokenSet.claims,
@@ -180,6 +201,8 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
180
201
  return internalServerError("Authentication failed. Please try again.");
181
202
  }
182
203
 
204
+ oidcLog.debug(`Callback: onAuthSuccess completed`);
205
+
183
206
  // Extract user and JWT token from callback result
184
207
  const { user, token, redirectUrl } = authResult;
185
208
 
@@ -235,8 +258,11 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
235
258
  }
236
259
  }
237
260
 
261
+ const finalRedirectUrl = redirectUrl || session.redirectUri;
262
+ oidcLog.debug(`Callback: auth complete, responding via ${response_type === "json" ? "JSON" : `redirect to "${finalRedirectUrl}"`}`);
263
+
238
264
  // Return JWT token in requested format
239
- return formatTokenResponse(token, user, redirectUrl || session.redirectUri, response_type);
265
+ return formatTokenResponse(token, user, finalRedirectUrl, response_type);
240
266
  } catch (error: any) {
241
267
  log.error("OIDC callback error:", error);
242
268
 
@@ -16,6 +16,7 @@ import InitiateRequest from "../schemas/InitiateRequest";
16
16
  import { generateState, generateSessionId, generateNonce } from "../utils/state-utils";
17
17
  import { validateProvider, createOidcError, OidcErrorCodes } from "../utils/error-utils";
18
18
  import { generators } from "openid-client";
19
+ import { oidcLog } from "../log";
19
20
 
20
21
  /**
21
22
  * Path parameters for the handler
@@ -48,6 +49,8 @@ const InitiateOidc: GetHandler<any, any, PathParams, InitiateRequest> = async ({
48
49
  // Validate provider name format
49
50
  validateProvider(provider);
50
51
 
52
+ oidcLog.debug(`Initiate: provider="${provider}" redirectUri="${redirectUri || "(not set)"}"`);
53
+
51
54
  // Get provider registry from context
52
55
  const providerRegistry = (ctx as any).oidcProviderRegistry;
53
56
  if (!providerRegistry) {
@@ -64,6 +67,8 @@ const InitiateOidc: GetHandler<any, any, PathParams, InitiateRequest> = async ({
64
67
  const staticProviderConfig = options.providers?.[provider];
65
68
  const finalRedirectUri = redirectUri || staticProviderConfig?.callbackUrl || `${req.protocol}://${req.get("host")}/oidc/${provider}/callback`;
66
69
 
70
+ oidcLog.debug(`Initiate: resolved redirectUri="${finalRedirectUri}" (source: ${redirectUri ? "query param" : staticProviderConfig?.callbackUrl ? "provider config" : "auto-detected"})`);
71
+
67
72
  // Generate cryptographically secure parameters
68
73
  const state = generateState();
69
74
  const sessionId = generateSessionId();
@@ -81,6 +86,8 @@ const InitiateOidc: GetHandler<any, any, PathParams, InitiateRequest> = async ({
81
86
  createdAt: new Date(),
82
87
  });
83
88
 
89
+ oidcLog.debug(`Initiate: session created sessionId=${sessionId}`);
90
+
84
91
  // Build authorization URL with PKCE and nonce
85
92
  const authorizationUrl = await oidcProvider.getAuthorizationUrl({
86
93
  state,
@@ -88,6 +95,8 @@ const InitiateOidc: GetHandler<any, any, PathParams, InitiateRequest> = async ({
88
95
  nonce,
89
96
  });
90
97
 
98
+ oidcLog.debug(`Initiate: redirecting to IdP authorization URL`);
99
+
91
100
  // Redirect user to provider's authorization page
92
101
  return {
93
102
  status: 302,
package/src/log.ts ADDED
@@ -0,0 +1,33 @@
1
+ import { FlinkLogFactory } from "@flink-app/flink";
2
+
3
+ /**
4
+ * Named logger for the OIDC plugin.
5
+ *
6
+ * Enables independent log level control via flink.config.js:
7
+ * ```js
8
+ * module.exports = {
9
+ * logging: {
10
+ * components: {
11
+ * "flink.oidc": "debug"
12
+ * }
13
+ * }
14
+ * };
15
+ * ```
16
+ *
17
+ * Or at runtime:
18
+ * ```ts
19
+ * FlinkLogFactory.setComponentLevel("flink.oidc", "debug");
20
+ * ```
21
+ */
22
+ export const oidcLog = FlinkLogFactory.createLogger("flink.oidc");
23
+
24
+ /**
25
+ * Mask a secret value for safe logging.
26
+ * Shows the first 4 characters followed by **** to help identify which secret
27
+ * is in use without exposing the full value.
28
+ */
29
+ export function maskSecret(value: string | undefined): string {
30
+ if (!value) return "(not set)";
31
+ if (value.length <= 4) return "****";
32
+ return `${value.slice(0, 4)}****`;
33
+ }
@@ -1,6 +1,7 @@
1
1
  import { OidcProvider } from "./OidcProvider";
2
2
  import { OidcProviderConfig } from "../OidcProviderConfig";
3
3
  import { createOidcError, OidcErrorCodes } from "../utils/error-utils";
4
+ import { oidcLog, maskSecret } from "../log";
4
5
 
5
6
  /**
6
7
  * Provider registry for managing OIDC provider instances
@@ -45,27 +46,44 @@ export class ProviderRegistry {
45
46
  // Check cache first
46
47
  const cachedProvider = this.providerInstances.get(providerName);
47
48
  if (cachedProvider) {
49
+ oidcLog.debug(`Provider "${providerName}": using cached instance`);
48
50
  return cachedProvider;
49
51
  }
50
52
 
51
53
  // Try static configuration
52
54
  let config: OidcProviderConfig | null = this.staticProviders[providerName] || null;
55
+ let configSource = "static";
53
56
 
54
57
  // Try dynamic loader if not in static config
55
58
  if (!config && this.providerLoader) {
59
+ oidcLog.debug(`Provider "${providerName}": not in static config, invoking providerLoader`);
56
60
  config = await this.providerLoader(providerName, this.ctx);
61
+ configSource = "dynamic";
57
62
  }
58
63
 
59
64
  if (!config) {
65
+ oidcLog.debug(`Provider "${providerName}": not found (static=[${Object.keys(this.staticProviders).join(", ")}], loader=${!!this.providerLoader})`);
60
66
  throw createOidcError(OidcErrorCodes.PROVIDER_NOT_CONFIGURED, `OIDC provider '${providerName}' is not configured`, {
61
67
  providerName,
62
68
  availableProviders: Object.keys(this.staticProviders),
63
69
  });
64
70
  }
65
71
 
72
+ oidcLog.debug(
73
+ `Provider "${providerName}" resolved via ${configSource}:`,
74
+ `issuer=${config.issuer}`,
75
+ `clientId=${config.clientId}`,
76
+ `clientSecret=${maskSecret(config.clientSecret)}`,
77
+ `callbackUrl=${config.callbackUrl}`,
78
+ config.discoveryUrl ? `discoveryUrl=${config.discoveryUrl}` : `authorizationEndpoint=${config.authorizationEndpoint}`,
79
+ `scope=${(config.scope || ["openid", "email", "profile"]).join(" ")}`
80
+ );
81
+
66
82
  // Create and initialize provider
83
+ oidcLog.debug(`Provider "${providerName}": initializing OIDC client`);
67
84
  const provider = new OidcProvider(config);
68
85
  await provider.initialize();
86
+ oidcLog.debug(`Provider "${providerName}": initialized successfully`);
69
87
 
70
88
  // Cache the instance
71
89
  this.providerInstances.set(providerName, provider);