@tdacorp/identity-client 0.2.3 → 0.2.5

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.
@@ -1,6 +1,7 @@
1
1
  // src/discovery.ts
2
2
  import { createRemoteJWKSet } from "jose";
3
3
  var DISCOVERY_CACHE_TTL_MS = 6e5;
4
+ var FETCH_TIMEOUT_MS = 2500;
4
5
  var CACHE_MAX_ENTRIES = 100;
5
6
  var discoveryCache = /* @__PURE__ */ new Map();
6
7
  var discoveryPending = /* @__PURE__ */ new Map();
@@ -31,23 +32,36 @@ function assertHttps(url, label) {
31
32
  throw new Error(`Discovery: ${label} "${url}" must be https (loopback hosts are the only http exception)`);
32
33
  }
33
34
  }
35
+ var ENDPOINT_FIELDS = [
36
+ { field: "jwks_uri", required: true },
37
+ { field: "token_endpoint", required: true },
38
+ { field: "authorization_endpoint", required: true },
39
+ { field: "userinfo_endpoint", required: false },
40
+ { field: "end_session_endpoint", required: false },
41
+ { field: "revocation_endpoint", required: false },
42
+ { field: "introspection_endpoint", required: false },
43
+ { field: "registration_endpoint", required: false }
44
+ ];
45
+ function validateEndpointField(field, url, issuerOrigin) {
46
+ assertHttps(url, field);
47
+ if (new URL(url).origin !== issuerOrigin) {
48
+ throw new Error(`Discovery: ${field} "${url}" is not same-origin as issuer`);
49
+ }
50
+ }
34
51
  function validateDiscoveryDocument(document, issuerUrl) {
35
52
  assertHttps(issuerUrl, "issuer");
36
- assertHttps(document.jwks_uri, "jwks_uri");
37
- assertHttps(document.token_endpoint, "token_endpoint");
38
- assertHttps(document.authorization_endpoint, "authorization_endpoint");
39
53
  if (document.issuer !== issuerUrl) {
40
54
  throw new Error(`Discovery: document issuer "${document.issuer}" does not match the configured issuer "${issuerUrl}"`);
41
55
  }
42
56
  const issuerOrigin = new URL(issuerUrl).origin;
43
- if (new URL(document.jwks_uri).origin !== issuerOrigin) {
44
- throw new Error(`Discovery: jwks_uri "${document.jwks_uri}" is not same-origin as issuer "${issuerUrl}"`);
45
- }
46
- if (new URL(document.token_endpoint).origin !== issuerOrigin) {
47
- throw new Error(`Discovery: token_endpoint "${document.token_endpoint}" is not same-origin as issuer "${issuerUrl}"`);
48
- }
49
- if (new URL(document.authorization_endpoint).origin !== issuerOrigin) {
50
- throw new Error(`Discovery: authorization_endpoint "${document.authorization_endpoint}" is not same-origin as issuer "${issuerUrl}"`);
57
+ for (const { field, required } of ENDPOINT_FIELDS) {
58
+ const value = document[field];
59
+ if (value === void 0 && !required) continue;
60
+ if (typeof value !== "string") {
61
+ const requirement = required ? "is required and must be a string" : ", if present, must be a string";
62
+ throw new Error(`Discovery: ${field} ${requirement}`);
63
+ }
64
+ validateEndpointField(field, value, issuerOrigin);
51
65
  }
52
66
  }
53
67
  async function fetchDiscovery(issuerUrl) {
@@ -58,7 +72,10 @@ async function fetchDiscovery(issuerUrl) {
58
72
  }
59
73
  const pending = discoveryPending.get(issuerUrl);
60
74
  if (pending) return pending;
61
- const promise = fetch(discoveryUrl(issuerUrl), { headers: { Accept: "application/json" } }).then(
75
+ const promise = fetch(discoveryUrl(issuerUrl), {
76
+ headers: { Accept: "application/json" },
77
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
78
+ }).then(
62
79
  async (response) => {
63
80
  if (!response.ok) {
64
81
  throw new Error(`Discovery fetch for ${issuerUrl} failed: HTTP ${response.status}`);
@@ -115,6 +132,7 @@ function generateState() {
115
132
  }
116
133
 
117
134
  // src/token.ts
135
+ var FETCH_TIMEOUT_MS2 = 2500;
118
136
  var TokenRequestError = class extends Error {
119
137
  status;
120
138
  error;
@@ -163,14 +181,29 @@ async function postTokenRequest(tokenEndpoint, clientId, clientSecret, clientAut
163
181
  } else {
164
182
  body.set("client_id", clientId);
165
183
  }
166
- return fetch(tokenEndpoint, { method: "POST", headers, body: body.toString() });
184
+ return fetch(tokenEndpoint, {
185
+ method: "POST",
186
+ headers,
187
+ body: body.toString(),
188
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS2)
189
+ });
190
+ }
191
+ async function postTokenRequestOrThrow(tokenEndpoint, clientId, clientSecret, clientAuthMethod, params) {
192
+ try {
193
+ return await postTokenRequest(tokenEndpoint, clientId, clientSecret, clientAuthMethod, params);
194
+ } catch (error) {
195
+ throw new TokenRequestError({
196
+ error: "network_failure",
197
+ errorDescription: error instanceof Error ? error.message : "Failed to reach the token endpoint"
198
+ });
199
+ }
167
200
  }
168
201
  async function readTokenResponseBody(response) {
169
202
  return await response.json().catch(() => null);
170
203
  }
171
204
  async function exchangeAuthorizationCode(options) {
172
205
  const discovery = await fetchDiscoveryForTokenRequest(options.issuer);
173
- const response = await postTokenRequest(
206
+ const response = await postTokenRequestOrThrow(
174
207
  discovery.token_endpoint,
175
208
  options.clientId,
176
209
  options.clientSecret,
@@ -190,7 +223,7 @@ async function exchangeAuthorizationCode(options) {
190
223
  }
191
224
  async function clientCredentialsGrant(options) {
192
225
  const discovery = await fetchDiscoveryForTokenRequest(options.issuer);
193
- const response = await postTokenRequest(
226
+ const response = await postTokenRequestOrThrow(
194
227
  discovery.token_endpoint,
195
228
  options.clientId,
196
229
  options.clientSecret,
package/dist/index.cjs CHANGED
@@ -38,6 +38,7 @@ module.exports = __toCommonJS(index_exports);
38
38
  // src/discovery.ts
39
39
  var import_jose = require("jose");
40
40
  var DISCOVERY_CACHE_TTL_MS = 6e5;
41
+ var FETCH_TIMEOUT_MS = 2500;
41
42
  var CACHE_MAX_ENTRIES = 100;
42
43
  var discoveryCache = /* @__PURE__ */ new Map();
43
44
  var discoveryPending = /* @__PURE__ */ new Map();
@@ -68,23 +69,36 @@ function assertHttps(url, label) {
68
69
  throw new Error(`Discovery: ${label} "${url}" must be https (loopback hosts are the only http exception)`);
69
70
  }
70
71
  }
72
+ var ENDPOINT_FIELDS = [
73
+ { field: "jwks_uri", required: true },
74
+ { field: "token_endpoint", required: true },
75
+ { field: "authorization_endpoint", required: true },
76
+ { field: "userinfo_endpoint", required: false },
77
+ { field: "end_session_endpoint", required: false },
78
+ { field: "revocation_endpoint", required: false },
79
+ { field: "introspection_endpoint", required: false },
80
+ { field: "registration_endpoint", required: false }
81
+ ];
82
+ function validateEndpointField(field, url, issuerOrigin) {
83
+ assertHttps(url, field);
84
+ if (new URL(url).origin !== issuerOrigin) {
85
+ throw new Error(`Discovery: ${field} "${url}" is not same-origin as issuer`);
86
+ }
87
+ }
71
88
  function validateDiscoveryDocument(document, issuerUrl) {
72
89
  assertHttps(issuerUrl, "issuer");
73
- assertHttps(document.jwks_uri, "jwks_uri");
74
- assertHttps(document.token_endpoint, "token_endpoint");
75
- assertHttps(document.authorization_endpoint, "authorization_endpoint");
76
90
  if (document.issuer !== issuerUrl) {
77
91
  throw new Error(`Discovery: document issuer "${document.issuer}" does not match the configured issuer "${issuerUrl}"`);
78
92
  }
79
93
  const issuerOrigin = new URL(issuerUrl).origin;
80
- if (new URL(document.jwks_uri).origin !== issuerOrigin) {
81
- throw new Error(`Discovery: jwks_uri "${document.jwks_uri}" is not same-origin as issuer "${issuerUrl}"`);
82
- }
83
- if (new URL(document.token_endpoint).origin !== issuerOrigin) {
84
- throw new Error(`Discovery: token_endpoint "${document.token_endpoint}" is not same-origin as issuer "${issuerUrl}"`);
85
- }
86
- if (new URL(document.authorization_endpoint).origin !== issuerOrigin) {
87
- throw new Error(`Discovery: authorization_endpoint "${document.authorization_endpoint}" is not same-origin as issuer "${issuerUrl}"`);
94
+ for (const { field, required } of ENDPOINT_FIELDS) {
95
+ const value = document[field];
96
+ if (value === void 0 && !required) continue;
97
+ if (typeof value !== "string") {
98
+ const requirement = required ? "is required and must be a string" : ", if present, must be a string";
99
+ throw new Error(`Discovery: ${field} ${requirement}`);
100
+ }
101
+ validateEndpointField(field, value, issuerOrigin);
88
102
  }
89
103
  }
90
104
  async function fetchDiscovery(issuerUrl) {
@@ -95,7 +109,10 @@ async function fetchDiscovery(issuerUrl) {
95
109
  }
96
110
  const pending = discoveryPending.get(issuerUrl);
97
111
  if (pending) return pending;
98
- const promise = fetch(discoveryUrl(issuerUrl), { headers: { Accept: "application/json" } }).then(
112
+ const promise = fetch(discoveryUrl(issuerUrl), {
113
+ headers: { Accept: "application/json" },
114
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
115
+ }).then(
99
116
  async (response) => {
100
117
  if (!response.ok) {
101
118
  throw new Error(`Discovery fetch for ${issuerUrl} failed: HTTP ${response.status}`);
@@ -152,6 +169,7 @@ function generateState() {
152
169
  }
153
170
 
154
171
  // src/token.ts
172
+ var FETCH_TIMEOUT_MS2 = 2500;
155
173
  var TokenRequestError = class extends Error {
156
174
  status;
157
175
  error;
@@ -200,14 +218,29 @@ async function postTokenRequest(tokenEndpoint, clientId, clientSecret, clientAut
200
218
  } else {
201
219
  body.set("client_id", clientId);
202
220
  }
203
- return fetch(tokenEndpoint, { method: "POST", headers, body: body.toString() });
221
+ return fetch(tokenEndpoint, {
222
+ method: "POST",
223
+ headers,
224
+ body: body.toString(),
225
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS2)
226
+ });
227
+ }
228
+ async function postTokenRequestOrThrow(tokenEndpoint, clientId, clientSecret, clientAuthMethod, params) {
229
+ try {
230
+ return await postTokenRequest(tokenEndpoint, clientId, clientSecret, clientAuthMethod, params);
231
+ } catch (error) {
232
+ throw new TokenRequestError({
233
+ error: "network_failure",
234
+ errorDescription: error instanceof Error ? error.message : "Failed to reach the token endpoint"
235
+ });
236
+ }
204
237
  }
205
238
  async function readTokenResponseBody(response) {
206
239
  return await response.json().catch(() => null);
207
240
  }
208
241
  async function exchangeAuthorizationCode(options) {
209
242
  const discovery = await fetchDiscoveryForTokenRequest(options.issuer);
210
- const response = await postTokenRequest(
243
+ const response = await postTokenRequestOrThrow(
211
244
  discovery.token_endpoint,
212
245
  options.clientId,
213
246
  options.clientSecret,
@@ -227,7 +260,7 @@ async function exchangeAuthorizationCode(options) {
227
260
  }
228
261
  async function clientCredentialsGrant(options) {
229
262
  const discovery = await fetchDiscoveryForTokenRequest(options.issuer);
230
- const response = await postTokenRequest(
263
+ const response = await postTokenRequestOrThrow(
231
264
  discovery.token_endpoint,
232
265
  options.clientId,
233
266
  options.clientSecret,
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  refreshTokens,
12
12
  verifyAccessToken,
13
13
  verifyIdToken
14
- } from "./chunk-MB66JUMG.js";
14
+ } from "./chunk-36WOXP7V.js";
15
15
  export {
16
16
  CLOCK_SKEW_TOLERANCE_SECONDS,
17
17
  TokenRequestError,
package/dist/next.cjs CHANGED
@@ -29,6 +29,7 @@ var import_server = require("next/server");
29
29
  // src/discovery.ts
30
30
  var import_jose = require("jose");
31
31
  var DISCOVERY_CACHE_TTL_MS = 6e5;
32
+ var FETCH_TIMEOUT_MS = 2500;
32
33
  var CACHE_MAX_ENTRIES = 100;
33
34
  var discoveryCache = /* @__PURE__ */ new Map();
34
35
  var discoveryPending = /* @__PURE__ */ new Map();
@@ -59,23 +60,36 @@ function assertHttps(url, label) {
59
60
  throw new Error(`Discovery: ${label} "${url}" must be https (loopback hosts are the only http exception)`);
60
61
  }
61
62
  }
63
+ var ENDPOINT_FIELDS = [
64
+ { field: "jwks_uri", required: true },
65
+ { field: "token_endpoint", required: true },
66
+ { field: "authorization_endpoint", required: true },
67
+ { field: "userinfo_endpoint", required: false },
68
+ { field: "end_session_endpoint", required: false },
69
+ { field: "revocation_endpoint", required: false },
70
+ { field: "introspection_endpoint", required: false },
71
+ { field: "registration_endpoint", required: false }
72
+ ];
73
+ function validateEndpointField(field, url, issuerOrigin) {
74
+ assertHttps(url, field);
75
+ if (new URL(url).origin !== issuerOrigin) {
76
+ throw new Error(`Discovery: ${field} "${url}" is not same-origin as issuer`);
77
+ }
78
+ }
62
79
  function validateDiscoveryDocument(document, issuerUrl) {
63
80
  assertHttps(issuerUrl, "issuer");
64
- assertHttps(document.jwks_uri, "jwks_uri");
65
- assertHttps(document.token_endpoint, "token_endpoint");
66
- assertHttps(document.authorization_endpoint, "authorization_endpoint");
67
81
  if (document.issuer !== issuerUrl) {
68
82
  throw new Error(`Discovery: document issuer "${document.issuer}" does not match the configured issuer "${issuerUrl}"`);
69
83
  }
70
84
  const issuerOrigin = new URL(issuerUrl).origin;
71
- if (new URL(document.jwks_uri).origin !== issuerOrigin) {
72
- throw new Error(`Discovery: jwks_uri "${document.jwks_uri}" is not same-origin as issuer "${issuerUrl}"`);
73
- }
74
- if (new URL(document.token_endpoint).origin !== issuerOrigin) {
75
- throw new Error(`Discovery: token_endpoint "${document.token_endpoint}" is not same-origin as issuer "${issuerUrl}"`);
76
- }
77
- if (new URL(document.authorization_endpoint).origin !== issuerOrigin) {
78
- throw new Error(`Discovery: authorization_endpoint "${document.authorization_endpoint}" is not same-origin as issuer "${issuerUrl}"`);
85
+ for (const { field, required } of ENDPOINT_FIELDS) {
86
+ const value = document[field];
87
+ if (value === void 0 && !required) continue;
88
+ if (typeof value !== "string") {
89
+ const requirement = required ? "is required and must be a string" : ", if present, must be a string";
90
+ throw new Error(`Discovery: ${field} ${requirement}`);
91
+ }
92
+ validateEndpointField(field, value, issuerOrigin);
79
93
  }
80
94
  }
81
95
  async function fetchDiscovery(issuerUrl) {
@@ -86,7 +100,10 @@ async function fetchDiscovery(issuerUrl) {
86
100
  }
87
101
  const pending = discoveryPending.get(issuerUrl);
88
102
  if (pending) return pending;
89
- const promise = fetch(discoveryUrl(issuerUrl), { headers: { Accept: "application/json" } }).then(
103
+ const promise = fetch(discoveryUrl(issuerUrl), {
104
+ headers: { Accept: "application/json" },
105
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
106
+ }).then(
90
107
  async (response) => {
91
108
  if (!response.ok) {
92
109
  throw new Error(`Discovery fetch for ${issuerUrl} failed: HTTP ${response.status}`);
@@ -143,6 +160,7 @@ function generateState() {
143
160
  }
144
161
 
145
162
  // src/token.ts
163
+ var FETCH_TIMEOUT_MS2 = 2500;
146
164
  var TokenRequestError = class extends Error {
147
165
  status;
148
166
  error;
@@ -191,14 +209,29 @@ async function postTokenRequest(tokenEndpoint, clientId, clientSecret, clientAut
191
209
  } else {
192
210
  body.set("client_id", clientId);
193
211
  }
194
- return fetch(tokenEndpoint, { method: "POST", headers, body: body.toString() });
212
+ return fetch(tokenEndpoint, {
213
+ method: "POST",
214
+ headers,
215
+ body: body.toString(),
216
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS2)
217
+ });
218
+ }
219
+ async function postTokenRequestOrThrow(tokenEndpoint, clientId, clientSecret, clientAuthMethod, params) {
220
+ try {
221
+ return await postTokenRequest(tokenEndpoint, clientId, clientSecret, clientAuthMethod, params);
222
+ } catch (error) {
223
+ throw new TokenRequestError({
224
+ error: "network_failure",
225
+ errorDescription: error instanceof Error ? error.message : "Failed to reach the token endpoint"
226
+ });
227
+ }
195
228
  }
196
229
  async function readTokenResponseBody(response) {
197
230
  return await response.json().catch(() => null);
198
231
  }
199
232
  async function exchangeAuthorizationCode(options) {
200
233
  const discovery = await fetchDiscoveryForTokenRequest(options.issuer);
201
- const response = await postTokenRequest(
234
+ const response = await postTokenRequestOrThrow(
202
235
  discovery.token_endpoint,
203
236
  options.clientId,
204
237
  options.clientSecret,
package/dist/next.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  generateCodeVerifier,
6
6
  generateState,
7
7
  verifyIdToken
8
- } from "./chunk-MB66JUMG.js";
8
+ } from "./chunk-36WOXP7V.js";
9
9
  import {
10
10
  seal,
11
11
  unseal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tdacorp/identity-client",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "OIDC relying-party client for TDACorp Identity: discovery, PKCE, token exchange, token verification and Next.js route helpers.",
5
5
  "license": "MIT",
6
6
  "bugs": {