@flink-app/oidc-plugin 2.0.0-alpha.89 → 2.0.0-alpha.91

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,25 @@
1
1
  # @flink-app/oidc-plugin
2
2
 
3
+ ## 2.0.0-alpha.91
4
+
5
+ ### Minor Changes
6
+
7
+ - 1bdbc82: feat(oidc-plugin): add per-provider `useUserInfoEndpoint` config flag (default `true`) to disable UserInfo calls for IdPs where the endpoint is unreliable or redundant with ID token claims
8
+
9
+ ### Patch Changes
10
+
11
+ - @flink-app/flink@2.0.0-alpha.91
12
+ - @flink-app/jwt-auth-plugin@2.0.0-alpha.91
13
+
14
+ ## 2.0.0-alpha.90
15
+
16
+ ### Patch Changes
17
+
18
+ - e18a37b: Add raw IdP response trace logs and prune redundant debug logs
19
+ - Updated dependencies [0d84b5f]
20
+ - @flink-app/flink@2.0.0-alpha.90
21
+ - @flink-app/jwt-auth-plugin@2.0.0-alpha.90
22
+
3
23
  ## 2.0.0-alpha.89
4
24
 
5
25
  ### Patch Changes
package/README.md CHANGED
@@ -203,6 +203,26 @@ await app.start();
203
203
  }
204
204
  ```
205
205
 
206
+ #### Disabling the UserInfo endpoint
207
+
208
+ By default, the plugin calls the UserInfo endpoint after token exchange to
209
+ enrich the profile with additional claims. Some IdPs either don't expose
210
+ extra claims beyond the ID token or return unreliable 5xx errors from
211
+ UserInfo. In those cases, set `useUserInfoEndpoint: false` on the provider
212
+ config to skip the UserInfo call entirely and build profiles from ID token
213
+ claims only:
214
+
215
+ ```typescript
216
+ {
217
+ issuer: "https://idp.acme.com",
218
+ clientId: "your-client-id",
219
+ clientSecret: "your-client-secret",
220
+ callbackUrl: "https://myapp.com/oidc/acme/callback",
221
+ discoveryUrl: "https://idp.acme.com/.well-known/openid-configuration",
222
+ useUserInfoEndpoint: false, // skip UserInfo; use ID token claims only
223
+ }
224
+ ```
225
+
206
226
  ### Callback Functions
207
227
 
208
228
  #### onAuthSuccess
@@ -619,6 +639,7 @@ interface OidcProviderConfigDB {
619
639
  authorizationEndpoint?: string;
620
640
  tokenEndpoint?: string;
621
641
  userinfoEndpoint?: string;
642
+ useUserInfoEndpoint?: boolean; // default true
622
643
  jwksUri?: string;
623
644
  scope: string[];
624
645
  createdAt: Date;
@@ -84,5 +84,14 @@ export interface OidcProviderConfig {
84
84
  * e.g., { "department": "custom:department", "role": "custom:role" }
85
85
  */
86
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;
87
96
  }
88
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;;;;;;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
+ {"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;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,CAiRnE,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,CA4QnE,CAAC;AAEF,eAAe,YAAY,CAAC"}
@@ -114,7 +114,6 @@ const CallbackOidc = async ({ ctx, req }) => {
114
114
  }
115
115
  const oidcProvider = await providerRegistry.getProvider(provider);
116
116
  // Exchange authorization code for tokens with PKCE validation
117
- log_1.oidcLog.debug(`Callback: exchanging authorization code for tokens`);
118
117
  const tokenSet = await oidcProvider.exchangeCodeForToken({
119
118
  code,
120
119
  codeVerifier: session.codeVerifier,
@@ -122,12 +121,10 @@ const CallbackOidc = async ({ ctx, req }) => {
122
121
  nonce: session.nonce,
123
122
  });
124
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`);
125
- // Build user profile from ID token and UserInfo
126
- log_1.oidcLog.debug(`Callback: building user profile`);
127
- const profile = await oidcProvider.buildProfile(tokenSet, true);
124
+ // Build user profile from ID token and (optionally) UserInfo
125
+ const profile = await oidcProvider.buildProfile(tokenSet, oidcProvider.config.useUserInfoEndpoint ?? true);
128
126
  log_1.oidcLog.debug(`Callback: profile built id="${profile.id}" email="${profile.email || "(none)"}" name="${profile.name || "(none)"}"`);
129
127
  // Call onAuthSuccess callback to create/link user and generate JWT token
130
- log_1.oidcLog.debug(`Callback: calling onAuthSuccess`);
131
128
  const authSuccessParams = {
132
129
  profile,
133
130
  claims: tokenSet.claims,
@@ -164,7 +161,6 @@ const CallbackOidc = async ({ ctx, req }) => {
164
161
  }
165
162
  return (0, flink_1.internalServerError)("Authentication failed. Please try again.");
166
163
  }
167
- log_1.oidcLog.debug(`Callback: onAuthSuccess completed`);
168
164
  // Extract user and JWT token from callback result
169
165
  const { user, token, redirectUrl } = authResult;
170
166
  if (!token) {
@@ -14,7 +14,7 @@ import OidcTokenSet from "../schemas/OidcTokenSet";
14
14
  * - Claims mapping to profile
15
15
  */
16
16
  export declare class OidcProvider {
17
- private config;
17
+ readonly config: OidcProviderConfig;
18
18
  private issuer;
19
19
  private client;
20
20
  private initialized;
@@ -1 +1 @@
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;AAKnD;;;;;;;;;;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;IAkEjC;;;;;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;IAsC/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;IAiCjG;;;;OAIG;YACW,iBAAiB;IAU/B;;;;OAIG;IACH,iBAAiB,IAAI,GAAG;CAM3B"}
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;AAKnD;;;;;;;;;;GAUG;AACH,qBAAa,YAAY;IACrB,SAAgB,MAAM,EAAE,kBAAkB,CAAC;IAC3C,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;IAsEjC;;;;;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;IAoD/H;;;;;;;;OAQG;IACG,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAsBjE;;;;;;;;;OASG;IACG,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,eAAe,GAAE,OAAc,GAAG,OAAO,CAAC,WAAW,CAAC;IAgCjG;;;;OAIG;YACW,iBAAiB;IAU/B;;;;OAIG;IACH,iBAAiB,IAAI,GAAG;CAM3B"}
@@ -41,6 +41,7 @@ class OidcProvider {
41
41
  log_1.oidcLog.debug(`Provider "${this.config.issuer}": discovering from ${this.config.discoveryUrl}`);
42
42
  this.issuer = await openid_client_1.Issuer.discover(this.config.discoveryUrl);
43
43
  log_1.oidcLog.debug(`Provider "${this.config.issuer}": discovery complete`, `authorization_endpoint=${this.issuer.metadata.authorization_endpoint}`, `token_endpoint=${this.issuer.metadata.token_endpoint}`, `userinfo_endpoint=${this.issuer.metadata.userinfo_endpoint ?? "(none)"}`, `jwks_uri=${this.issuer.metadata.jwks_uri}`);
44
+ log_1.oidcLog.trace(`Provider "${this.config.issuer}": raw discovery metadata from ${this.config.discoveryUrl}`, this.issuer.metadata);
44
45
  }
45
46
  // Option 2: Manual configuration
46
47
  else {
@@ -120,6 +121,10 @@ class OidcProvider {
120
121
  state: params.state,
121
122
  nonce: params.nonce,
122
123
  });
124
+ // Raw response from the token endpoint (includes access_token, id_token,
125
+ // refresh_token, token_type, expires_at, scope, and any non-standard fields
126
+ // returned by the IdP). Trace-level because it contains secrets.
127
+ log_1.oidcLog.trace(`Provider "${this.config.issuer}": raw token endpoint response`, { ...tokenSet });
123
128
  // Extract claims from ID token (already validated by openid-client)
124
129
  const claims = tokenSet.claims();
125
130
  log_1.oidcLog.trace(`Provider "${this.config.issuer}": extracted claims from ID token`, claims);
@@ -134,6 +139,15 @@ class OidcProvider {
134
139
  };
135
140
  }
136
141
  catch (error) {
142
+ // openid-client attaches the raw IdP error response on OPError.
143
+ log_1.oidcLog.trace(`Provider "${this.config.issuer}": raw token exchange error`, {
144
+ name: error.name,
145
+ message: error.message,
146
+ error: error.error,
147
+ error_description: error.error_description,
148
+ error_uri: error.error_uri,
149
+ response: error.response?.body ?? error.response,
150
+ });
137
151
  throw (0, error_utils_1.createOidcError)(error_utils_1.OidcErrorCodes.TOKEN_EXCHANGE_FAILED, `Token exchange failed: ${error.message}`, {
138
152
  originalError: error.message,
139
153
  errorCode: error.error,
@@ -153,9 +167,18 @@ class OidcProvider {
153
167
  await this.ensureInitialized();
154
168
  try {
155
169
  const userinfo = await this.client.userinfo(accessToken);
170
+ // Raw response from the UserInfo endpoint, before any mapping/merging.
171
+ log_1.oidcLog.trace(`Provider "${this.config.issuer}": raw UserInfo endpoint response`, userinfo);
156
172
  return userinfo;
157
173
  }
158
174
  catch (error) {
175
+ log_1.oidcLog.trace(`Provider "${this.config.issuer}": raw UserInfo error`, {
176
+ name: error.name,
177
+ message: error.message,
178
+ error: error.error,
179
+ error_description: error.error_description,
180
+ response: error.response?.body ?? error.response,
181
+ });
159
182
  throw (0, error_utils_1.createOidcError)(error_utils_1.OidcErrorCodes.USERINFO_FAILED, `UserInfo request failed: ${error.message}`, {
160
183
  originalError: error.message,
161
184
  });
@@ -181,7 +204,6 @@ class OidcProvider {
181
204
  const userinfo = await this.getUserInfo(tokenSet.accessToken);
182
205
  // Merge UserInfo claims with ID token claims
183
206
  claims = { ...claims, ...userinfo };
184
- log_1.oidcLog.trace(`Provider "${this.config.issuer}": merged claims after UserInfo`, claims);
185
207
  }
186
208
  catch (error) {
187
209
  // UserInfo is optional - continue with ID token claims only
@@ -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;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"}
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;IA+C9D;;;;;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"}
@@ -59,10 +59,8 @@ class ProviderRegistry {
59
59
  }
60
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(" ")}`);
61
61
  // Create and initialize provider
62
- log_1.oidcLog.debug(`Provider "${providerName}": initializing OIDC client`);
63
62
  const provider = new OidcProvider_1.OidcProvider(config);
64
63
  await provider.initialize();
65
- log_1.oidcLog.debug(`Provider "${providerName}": initialized successfully`);
66
64
  // Cache the instance
67
65
  this.providerInstances.set(providerName, provider);
68
66
  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.89",
3
+ "version": "2.0.0-alpha.91",
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.89"
14
+ "@flink-app/jwt-auth-plugin": "2.0.0-alpha.91"
15
15
  },
16
16
  "peerDependencies": {
17
- "@flink-app/flink": ">=2.0.0-alpha.89",
17
+ "@flink-app/flink": ">=2.0.0-alpha.91",
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.89",
31
- "@flink-app/test-utils": "2.0.0-alpha.89",
32
- "@flink-app/jwt-auth-plugin": "2.0.0-alpha.89"
30
+ "@flink-app/jwt-auth-plugin": "2.0.0-alpha.91",
31
+ "@flink-app/test-utils": "2.0.0-alpha.91",
32
+ "@flink-app/flink": "2.0.0-alpha.91"
33
33
  },
34
34
  "scripts": {
35
35
  "test": "jasmine-ts --config=./spec/support/jasmine.json",
@@ -96,4 +96,14 @@ export interface OidcProviderConfig {
96
96
  * e.g., { "department": "custom:department", "role": "custom:role" }
97
97
  */
98
98
  claimMapping?: Record<string, string>;
99
+
100
+ /**
101
+ * Whether to fetch additional claims from the UserInfo endpoint.
102
+ * Default: true
103
+ *
104
+ * Set to `false` for IdPs whose UserInfo endpoint is unreliable or
105
+ * returns no additional data beyond what's already in the ID token.
106
+ * When disabled, profiles are built from ID token claims only.
107
+ */
108
+ useUserInfoEndpoint?: boolean;
99
109
  }
@@ -142,7 +142,6 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
142
142
  const oidcProvider = await providerRegistry.getProvider(provider);
143
143
 
144
144
  // Exchange authorization code for tokens with PKCE validation
145
- oidcLog.debug(`Callback: exchanging authorization code for tokens`);
146
145
  const tokenSet = await oidcProvider.exchangeCodeForToken({
147
146
  code,
148
147
  codeVerifier: session.codeVerifier,
@@ -159,13 +158,11 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
159
158
  `expiresIn=${tokenSet.expiresIn ?? "(none)"}s`
160
159
  );
161
160
 
162
- // Build user profile from ID token and UserInfo
163
- oidcLog.debug(`Callback: building user profile`);
164
- const profile = await oidcProvider.buildProfile(tokenSet, true);
161
+ // Build user profile from ID token and (optionally) UserInfo
162
+ const profile = await oidcProvider.buildProfile(tokenSet, oidcProvider.config.useUserInfoEndpoint ?? true);
165
163
  oidcLog.debug(`Callback: profile built id="${profile.id}" email="${profile.email || "(none)"}" name="${profile.name || "(none)"}"`);
166
164
 
167
165
  // Call onAuthSuccess callback to create/link user and generate JWT token
168
- oidcLog.debug(`Callback: calling onAuthSuccess`);
169
166
  const authSuccessParams = {
170
167
  profile,
171
168
  claims: tokenSet.claims,
@@ -207,8 +204,6 @@ const CallbackOidc: GetHandler<any, any, PathParams, CallbackRequest> = async ({
207
204
  return internalServerError("Authentication failed. Please try again.");
208
205
  }
209
206
 
210
- oidcLog.debug(`Callback: onAuthSuccess completed`);
211
-
212
207
  // Extract user and JWT token from callback result
213
208
  const { user, token, redirectUrl } = authResult;
214
209
 
@@ -18,7 +18,7 @@ import { oidcLog } from "../log";
18
18
  * - Claims mapping to profile
19
19
  */
20
20
  export class OidcProvider {
21
- private config: OidcProviderConfig;
21
+ public readonly config: OidcProviderConfig;
22
22
  private issuer: Issuer<Client> | null = null;
23
23
  private client: Client | null = null;
24
24
  private initialized: boolean = false;
@@ -52,6 +52,10 @@ export class OidcProvider {
52
52
  `userinfo_endpoint=${this.issuer.metadata.userinfo_endpoint ?? "(none)"}`,
53
53
  `jwks_uri=${this.issuer.metadata.jwks_uri}`
54
54
  );
55
+ oidcLog.trace(
56
+ `Provider "${this.config.issuer}": raw discovery metadata from ${this.config.discoveryUrl}`,
57
+ this.issuer.metadata
58
+ );
55
59
  }
56
60
  // Option 2: Manual configuration
57
61
  else {
@@ -149,6 +153,11 @@ export class OidcProvider {
149
153
  }
150
154
  );
151
155
 
156
+ // Raw response from the token endpoint (includes access_token, id_token,
157
+ // refresh_token, token_type, expires_at, scope, and any non-standard fields
158
+ // returned by the IdP). Trace-level because it contains secrets.
159
+ oidcLog.trace(`Provider "${this.config.issuer}": raw token endpoint response`, { ...tokenSet });
160
+
152
161
  // Extract claims from ID token (already validated by openid-client)
153
162
  const claims = tokenSet.claims();
154
163
  oidcLog.trace(`Provider "${this.config.issuer}": extracted claims from ID token`, claims);
@@ -163,6 +172,15 @@ export class OidcProvider {
163
172
  claims,
164
173
  };
165
174
  } catch (error: any) {
175
+ // openid-client attaches the raw IdP error response on OPError.
176
+ oidcLog.trace(`Provider "${this.config.issuer}": raw token exchange error`, {
177
+ name: error.name,
178
+ message: error.message,
179
+ error: error.error,
180
+ error_description: error.error_description,
181
+ error_uri: error.error_uri,
182
+ response: error.response?.body ?? error.response,
183
+ });
166
184
  throw createOidcError(OidcErrorCodes.TOKEN_EXCHANGE_FAILED, `Token exchange failed: ${error.message}`, {
167
185
  originalError: error.message,
168
186
  errorCode: error.error,
@@ -184,8 +202,17 @@ export class OidcProvider {
184
202
 
185
203
  try {
186
204
  const userinfo = await this.client!.userinfo(accessToken);
205
+ // Raw response from the UserInfo endpoint, before any mapping/merging.
206
+ oidcLog.trace(`Provider "${this.config.issuer}": raw UserInfo endpoint response`, userinfo);
187
207
  return userinfo;
188
208
  } catch (error: any) {
209
+ oidcLog.trace(`Provider "${this.config.issuer}": raw UserInfo error`, {
210
+ name: error.name,
211
+ message: error.message,
212
+ error: error.error,
213
+ error_description: error.error_description,
214
+ response: error.response?.body ?? error.response,
215
+ });
189
216
  throw createOidcError(OidcErrorCodes.USERINFO_FAILED, `UserInfo request failed: ${error.message}`, {
190
217
  originalError: error.message,
191
218
  });
@@ -214,7 +241,6 @@ export class OidcProvider {
214
241
  const userinfo = await this.getUserInfo(tokenSet.accessToken);
215
242
  // Merge UserInfo claims with ID token claims
216
243
  claims = { ...claims, ...userinfo };
217
- oidcLog.trace(`Provider "${this.config.issuer}": merged claims after UserInfo`, claims);
218
244
  } catch (error) {
219
245
  // UserInfo is optional - continue with ID token claims only
220
246
  oidcLog.warn(`Failed to fetch UserInfo from ${userinfoUrl}, using ID token claims only:`, error);
@@ -80,10 +80,8 @@ export class ProviderRegistry {
80
80
  );
81
81
 
82
82
  // Create and initialize provider
83
- oidcLog.debug(`Provider "${providerName}": initializing OIDC client`);
84
83
  const provider = new OidcProvider(config);
85
84
  await provider.initialize();
86
- oidcLog.debug(`Provider "${providerName}": initialized successfully`);
87
85
 
88
86
  // Cache the instance
89
87
  this.providerInstances.set(providerName, provider);