@nihplod/sso-sdk 1.4.0 → 1.4.1

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/README.md CHANGED
@@ -357,6 +357,15 @@ export const config = {
357
357
  > code (silent re-auth), and the callback handler issues new cookies — the user typically
358
358
  > only sees a quick redirect loop through the SSO center. No client-side action is needed.
359
359
 
360
+ > **Introspection outage behavior (since 1.4.1):** introspection requests time out after
361
+ > 5s. When introspection is *unreachable* (network error, timeout, or 5xx — as opposed to
362
+ > a confirmed-invalid token), the middleware fails open for requests that already carry an
363
+ > SSO `access_token` cookie: it lets the request through (with one `console.warn`) instead
364
+ > of redirecting to an SSO center that is likely down. This is safe because the middleware
365
+ > is only a UX gate — Route Handlers / Server Components must still re-verify the token.
366
+ > Confirmed-invalid tokens (401/403 or `active:false`) still trigger the redirect, and
367
+ > requests without any SSO cookie still redirect as before.
368
+
360
369
  ```typescript
361
370
  // src/app/api/auth/callback/route.ts
362
371
  import { createCallbackRouteHandler } from "@nihplod/sso-sdk/next";
@@ -122,6 +122,7 @@ function matchesPath(pathname, paths) {
122
122
  var introspectionCache = /* @__PURE__ */ new Map();
123
123
  var INTROSPECT_CACHE_TTL_MS = 3e4;
124
124
  var INTROSPECT_CACHE_MAX_ENTRIES = 500;
125
+ var INTROSPECT_TIMEOUT_MS = 5e3;
125
126
  async function introspectCacheKey(token, clientId) {
126
127
  const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(token));
127
128
  const hex = Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
@@ -154,7 +155,7 @@ async function introspectAccessToken(token, ssoBaseUrl, clientId, clientSecret)
154
155
  const cacheKey = await introspectCacheKey(token, clientId);
155
156
  const cached = introspectCacheGet(cacheKey);
156
157
  if (cached) {
157
- return cached.active;
158
+ return cached.active ? "active" : "inactive";
158
159
  }
159
160
  try {
160
161
  const body = new URLSearchParams({
@@ -168,21 +169,22 @@ async function introspectAccessToken(token, ssoBaseUrl, clientId, clientSecret)
168
169
  const res = await fetch(`${ssoBaseUrl}/api/oauth/introspect`, {
169
170
  method: "POST",
170
171
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
171
- body: body.toString()
172
+ body: body.toString(),
173
+ signal: AbortSignal.timeout(INTROSPECT_TIMEOUT_MS)
172
174
  });
173
175
  if (!res.ok) {
174
176
  if (res.status !== 401 && res.status !== 403) {
175
- return false;
177
+ return "unreachable";
176
178
  }
177
179
  introspectCacheSet(cacheKey, false);
178
- return false;
180
+ return "inactive";
179
181
  }
180
182
  const data = await res.json();
181
183
  const active = data.active === true;
182
184
  introspectCacheSet(cacheKey, active);
183
- return active;
185
+ return active ? "active" : "inactive";
184
186
  } catch {
185
- return false;
187
+ return "unreachable";
186
188
  }
187
189
  }
188
190
  function createSsoMiddleware(config) {
@@ -232,13 +234,13 @@ function createSsoMiddleware(config) {
232
234
  const ssoSession = request.cookies.get(ssoCookieName);
233
235
  if (ssoSession?.value) {
234
236
  if (validateSsoCookie) {
235
- const tokenActive = await introspectAccessToken(
237
+ const introspectResult = await introspectAccessToken(
236
238
  ssoSession.value,
237
239
  normalizedServerBase,
238
240
  clientId,
239
241
  clientSecret
240
242
  );
241
- if (tokenActive) {
243
+ if (introspectResult === "active") {
242
244
  return import_server.NextResponse.next();
243
245
  }
244
246
  } else {
@@ -247,13 +249,19 @@ function createSsoMiddleware(config) {
247
249
  }
248
250
  const accessTokenCookie = request.cookies.get(accessTokenCookieName);
249
251
  if (accessTokenCookie?.value) {
250
- const tokenActive = await introspectAccessToken(
252
+ const introspectResult = await introspectAccessToken(
251
253
  accessTokenCookie.value,
252
254
  normalizedServerBase,
253
255
  clientId,
254
256
  clientSecret
255
257
  );
256
- if (tokenActive) {
258
+ if (introspectResult === "active") {
259
+ return import_server.NextResponse.next();
260
+ }
261
+ if (introspectResult === "unreachable") {
262
+ console.warn(
263
+ "[SSO SDK] introspection \u4E0D\u53EF\u8FBE\uFF08\u7F51\u7EDC\u5F02\u5E38/\u8D85\u65F6/5xx\uFF09\uFF0C\u5BF9\u6301\u6709 SSO access token Cookie \u7684\u8BF7\u6C42 fail-open \u653E\u884C\uFF1B\u654F\u611F\u6570\u636E\u7684\u9274\u6743\u7531 Route Handler \u515C\u5E95\u3002"
264
+ );
257
265
  return import_server.NextResponse.next();
258
266
  }
259
267
  }