@nihplod/sso-sdk 1.5.0 → 1.7.0

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.
@@ -36,7 +36,12 @@ interface SsoMiddlewareConfig {
36
36
  redirectUri: string;
37
37
  /** 请求的 scope(空格分隔),默认 "openid profile" */
38
38
  scopes?: string;
39
- /** 公开路由前缀(不需要认证) */
39
+ /**
40
+ * 公开路由前缀(不需要认证)。
41
+ *
42
+ * 注意:/api/ 下的路径永不按静态资源扩展名(.js/.css/...)放行;
43
+ * 如需公开某个 API 路径,必须在此显式列出。
44
+ */
40
45
  publicPaths?: string[];
41
46
  /** 回调路径(不触发重定向),默认 "/api/auth/callback" */
42
47
  callbackPath?: string;
@@ -203,6 +208,55 @@ interface CallbackRouteConfig {
203
208
  }
204
209
  declare function createCallbackRouteHandler(config: CallbackRouteConfig): (request: NextRequest) => Promise<NextResponse<unknown>>;
205
210
 
211
+ /**
212
+ * App Router Refresh Route Handler
213
+ *
214
+ * 使用 httpOnly refresh cookie 在服务端换取新的 access/refresh/id token,
215
+ * 并更新 Cookie。适用于 Next.js BFF/Confidential Client:token 不暴露给浏览器 JS。
216
+ *
217
+ * 用法 (src/app/api/auth/refresh/route.ts):
218
+ * ```ts
219
+ * import { createRefreshRouteHandler } from "@nihplod/sso-sdk/next";
220
+ *
221
+ * export const POST = createRefreshRouteHandler({
222
+ * clientId: process.env.SSO_CLIENT_ID!,
223
+ * clientSecret: process.env.SSO_CLIENT_SECRET,
224
+ * ssoBaseUrl: "https://nihplod.cn",
225
+ * redirectUri: "https://myapp.com/api/auth/callback",
226
+ * });
227
+ * ```
228
+ */
229
+
230
+ interface RefreshRouteConfig {
231
+ /** OAuth Client ID */
232
+ clientId: string;
233
+ /** SSO 中心地址 */
234
+ ssoBaseUrl: string;
235
+ /** 回调 URL(用于同源校验,必须与注册的 redirect_uri 一致) */
236
+ redirectUri: string;
237
+ /** OAuth Client Secret(Confidential Client 必填) */
238
+ clientSecret?: string;
239
+ /** Access Token Cookie 名称,默认 __Host-nihplod_sso_at */
240
+ accessTokenCookieName?: string;
241
+ /** Refresh Token Cookie 名称,默认 __Host-nihplod_sso_rt */
242
+ refreshTokenCookieName?: string;
243
+ /** ID Token Cookie 名称,默认 __Host-nihplod_sso_id */
244
+ idTokenCookieName?: string;
245
+ /**
246
+ * 本地 HTTP 开发模式(默认 false)。必须与 middleware / callback / logout 配置一致。
247
+ * 生产环境(NODE_ENV=production 且 ssoBaseUrl 为 https)下会被强制忽略并告警。
248
+ */
249
+ insecureLocalDev?: boolean;
250
+ /**
251
+ * 服务端到服务端调用的内网地址(可选,如 http://127.0.0.1:3000)。
252
+ * 仅用于 token 交换;浏览器跳转仍使用 ssoBaseUrl 公网地址。
253
+ */
254
+ serverBaseUrl?: string;
255
+ /** 调试日志开关(默认 false) */
256
+ debug?: boolean;
257
+ }
258
+ declare function createRefreshRouteHandler(config: RefreshRouteConfig): (request: NextRequest) => Promise<NextResponse>;
259
+
206
260
  /**
207
261
  * App Router 登出 Route Handler
208
262
  *
@@ -298,6 +352,28 @@ interface LogoutTokenPayload {
298
352
  sub?: string;
299
353
  sid?: string;
300
354
  }
355
+ /**
356
+ * jti 防重放存储接口(可注入)。
357
+ *
358
+ * 默认实现是进程内 Map(per-instance):多实例 / Serverless 部署下各实例
359
+ * 互不可见,重放检查形同虚设,必须注入共享存储(如 Redis)。
360
+ * key 为 `${issuer}:${jti}`,ttlSeconds 为建议过期时间(logout_token exp + 60s 宽限)。
361
+ */
362
+ interface LogoutJtiStore {
363
+ has(key: string): Promise<boolean> | boolean;
364
+ add(key: string, ttlSeconds: number): Promise<void> | void;
365
+ /**
366
+ * 原子 check-and-set(如 Redis `SET key 1 NX EX ttl`):返回 false 表示
367
+ * key 已存在(判重放)。提供时优先使用;缺失时回退 has+add
368
+ * (非原子,仅单实例部署可接受:并发窗口内同一 jti 可能双双通过)。
369
+ */
370
+ addIfAbsent?(key: string, ttlSeconds: number): Promise<boolean> | boolean;
371
+ /**
372
+ * 释放已记录的 key。onLogout 钩子失败后需要允许 IdP 用同一 logout_token
373
+ * 重投,SDK 会调用此方法;未实现时 IdP 重投将被重放检查以 400 拒绝。
374
+ */
375
+ remove?(key: string): Promise<void> | void;
376
+ }
301
377
 
302
378
  /**
303
379
  * App Router Backchannel Logout 接收端 Route Handler
@@ -340,6 +416,15 @@ interface BackchannelLogoutRouteConfig {
340
416
  refreshTokenCookieName?: string;
341
417
  /** ID Token Cookie 名称,默认 __Host-nihplod_sso_id */
342
418
  idTokenCookieName?: string;
419
+ /**
420
+ * jti 防重放存储(可选)。
421
+ *
422
+ * 默认实现是进程内 Map(per-instance):多实例 / Serverless 部署下各实例
423
+ * 互不可见,重放保护形同虚设(IdP 重投/攻击者重放到另一实例会被当作首次)。
424
+ * 这类部署 MUST 注入共享存储(如 Redis:addIfAbsent = `SET key 1 NX EX ttl`,
425
+ * remove = `DEL key`——onLogout 失败时用于释放 jti 允许 IdP 重投)。
426
+ */
427
+ jtiStore?: LogoutJtiStore;
343
428
  /**
344
429
  * 本地 HTTP 开发模式(默认 false)。关闭 Cookie 的 Secure 属性并去除
345
430
  * __Host-/__Secure- 前缀;必须与 middleware / callback / logout 的配置保持一致。
@@ -391,4 +476,4 @@ declare function getSecureCookieOptions(maxAge?: number, path?: string, secure?:
391
476
  maxAge?: number;
392
477
  };
393
478
 
394
- export { type BackchannelLogoutRouteConfig, type CallbackRouteConfig, DEFAULT_ACCESS_TOKEN_COOKIE_NAME, DEFAULT_ID_TOKEN_COOKIE_NAME, DEFAULT_LOGOUT_STATE_COOKIE_NAME, DEFAULT_NONCE_COOKIE_NAME, DEFAULT_REFRESH_TOKEN_COOKIE_NAME, DEFAULT_RETURN_COOKIE_NAME, DEFAULT_STATE_COOKIE_NAME, DEFAULT_VERIFIER_COOKIE_NAME, type LogoutRouteConfig, type SsoMiddlewareConfig, createBackchannelLogoutRouteHandler, createCallbackRouteHandler, createLogoutRouteHandler, createSsoMiddleware, getHostCookieOptions, getSecureCookieOptions, toInsecureCookieName };
479
+ export { type BackchannelLogoutRouteConfig, type CallbackRouteConfig, DEFAULT_ACCESS_TOKEN_COOKIE_NAME, DEFAULT_ID_TOKEN_COOKIE_NAME, DEFAULT_LOGOUT_STATE_COOKIE_NAME, DEFAULT_NONCE_COOKIE_NAME, DEFAULT_REFRESH_TOKEN_COOKIE_NAME, DEFAULT_RETURN_COOKIE_NAME, DEFAULT_STATE_COOKIE_NAME, DEFAULT_VERIFIER_COOKIE_NAME, type LogoutJtiStore, type LogoutRouteConfig, type RefreshRouteConfig, type SsoMiddlewareConfig, createBackchannelLogoutRouteHandler, createCallbackRouteHandler, createLogoutRouteHandler, createRefreshRouteHandler, createSsoMiddleware, getHostCookieOptions, getSecureCookieOptions, toInsecureCookieName };
@@ -36,7 +36,12 @@ interface SsoMiddlewareConfig {
36
36
  redirectUri: string;
37
37
  /** 请求的 scope(空格分隔),默认 "openid profile" */
38
38
  scopes?: string;
39
- /** 公开路由前缀(不需要认证) */
39
+ /**
40
+ * 公开路由前缀(不需要认证)。
41
+ *
42
+ * 注意:/api/ 下的路径永不按静态资源扩展名(.js/.css/...)放行;
43
+ * 如需公开某个 API 路径,必须在此显式列出。
44
+ */
40
45
  publicPaths?: string[];
41
46
  /** 回调路径(不触发重定向),默认 "/api/auth/callback" */
42
47
  callbackPath?: string;
@@ -203,6 +208,55 @@ interface CallbackRouteConfig {
203
208
  }
204
209
  declare function createCallbackRouteHandler(config: CallbackRouteConfig): (request: NextRequest) => Promise<NextResponse<unknown>>;
205
210
 
211
+ /**
212
+ * App Router Refresh Route Handler
213
+ *
214
+ * 使用 httpOnly refresh cookie 在服务端换取新的 access/refresh/id token,
215
+ * 并更新 Cookie。适用于 Next.js BFF/Confidential Client:token 不暴露给浏览器 JS。
216
+ *
217
+ * 用法 (src/app/api/auth/refresh/route.ts):
218
+ * ```ts
219
+ * import { createRefreshRouteHandler } from "@nihplod/sso-sdk/next";
220
+ *
221
+ * export const POST = createRefreshRouteHandler({
222
+ * clientId: process.env.SSO_CLIENT_ID!,
223
+ * clientSecret: process.env.SSO_CLIENT_SECRET,
224
+ * ssoBaseUrl: "https://nihplod.cn",
225
+ * redirectUri: "https://myapp.com/api/auth/callback",
226
+ * });
227
+ * ```
228
+ */
229
+
230
+ interface RefreshRouteConfig {
231
+ /** OAuth Client ID */
232
+ clientId: string;
233
+ /** SSO 中心地址 */
234
+ ssoBaseUrl: string;
235
+ /** 回调 URL(用于同源校验,必须与注册的 redirect_uri 一致) */
236
+ redirectUri: string;
237
+ /** OAuth Client Secret(Confidential Client 必填) */
238
+ clientSecret?: string;
239
+ /** Access Token Cookie 名称,默认 __Host-nihplod_sso_at */
240
+ accessTokenCookieName?: string;
241
+ /** Refresh Token Cookie 名称,默认 __Host-nihplod_sso_rt */
242
+ refreshTokenCookieName?: string;
243
+ /** ID Token Cookie 名称,默认 __Host-nihplod_sso_id */
244
+ idTokenCookieName?: string;
245
+ /**
246
+ * 本地 HTTP 开发模式(默认 false)。必须与 middleware / callback / logout 配置一致。
247
+ * 生产环境(NODE_ENV=production 且 ssoBaseUrl 为 https)下会被强制忽略并告警。
248
+ */
249
+ insecureLocalDev?: boolean;
250
+ /**
251
+ * 服务端到服务端调用的内网地址(可选,如 http://127.0.0.1:3000)。
252
+ * 仅用于 token 交换;浏览器跳转仍使用 ssoBaseUrl 公网地址。
253
+ */
254
+ serverBaseUrl?: string;
255
+ /** 调试日志开关(默认 false) */
256
+ debug?: boolean;
257
+ }
258
+ declare function createRefreshRouteHandler(config: RefreshRouteConfig): (request: NextRequest) => Promise<NextResponse>;
259
+
206
260
  /**
207
261
  * App Router 登出 Route Handler
208
262
  *
@@ -298,6 +352,28 @@ interface LogoutTokenPayload {
298
352
  sub?: string;
299
353
  sid?: string;
300
354
  }
355
+ /**
356
+ * jti 防重放存储接口(可注入)。
357
+ *
358
+ * 默认实现是进程内 Map(per-instance):多实例 / Serverless 部署下各实例
359
+ * 互不可见,重放检查形同虚设,必须注入共享存储(如 Redis)。
360
+ * key 为 `${issuer}:${jti}`,ttlSeconds 为建议过期时间(logout_token exp + 60s 宽限)。
361
+ */
362
+ interface LogoutJtiStore {
363
+ has(key: string): Promise<boolean> | boolean;
364
+ add(key: string, ttlSeconds: number): Promise<void> | void;
365
+ /**
366
+ * 原子 check-and-set(如 Redis `SET key 1 NX EX ttl`):返回 false 表示
367
+ * key 已存在(判重放)。提供时优先使用;缺失时回退 has+add
368
+ * (非原子,仅单实例部署可接受:并发窗口内同一 jti 可能双双通过)。
369
+ */
370
+ addIfAbsent?(key: string, ttlSeconds: number): Promise<boolean> | boolean;
371
+ /**
372
+ * 释放已记录的 key。onLogout 钩子失败后需要允许 IdP 用同一 logout_token
373
+ * 重投,SDK 会调用此方法;未实现时 IdP 重投将被重放检查以 400 拒绝。
374
+ */
375
+ remove?(key: string): Promise<void> | void;
376
+ }
301
377
 
302
378
  /**
303
379
  * App Router Backchannel Logout 接收端 Route Handler
@@ -340,6 +416,15 @@ interface BackchannelLogoutRouteConfig {
340
416
  refreshTokenCookieName?: string;
341
417
  /** ID Token Cookie 名称,默认 __Host-nihplod_sso_id */
342
418
  idTokenCookieName?: string;
419
+ /**
420
+ * jti 防重放存储(可选)。
421
+ *
422
+ * 默认实现是进程内 Map(per-instance):多实例 / Serverless 部署下各实例
423
+ * 互不可见,重放保护形同虚设(IdP 重投/攻击者重放到另一实例会被当作首次)。
424
+ * 这类部署 MUST 注入共享存储(如 Redis:addIfAbsent = `SET key 1 NX EX ttl`,
425
+ * remove = `DEL key`——onLogout 失败时用于释放 jti 允许 IdP 重投)。
426
+ */
427
+ jtiStore?: LogoutJtiStore;
343
428
  /**
344
429
  * 本地 HTTP 开发模式(默认 false)。关闭 Cookie 的 Secure 属性并去除
345
430
  * __Host-/__Secure- 前缀;必须与 middleware / callback / logout 的配置保持一致。
@@ -391,4 +476,4 @@ declare function getSecureCookieOptions(maxAge?: number, path?: string, secure?:
391
476
  maxAge?: number;
392
477
  };
393
478
 
394
- export { type BackchannelLogoutRouteConfig, type CallbackRouteConfig, DEFAULT_ACCESS_TOKEN_COOKIE_NAME, DEFAULT_ID_TOKEN_COOKIE_NAME, DEFAULT_LOGOUT_STATE_COOKIE_NAME, DEFAULT_NONCE_COOKIE_NAME, DEFAULT_REFRESH_TOKEN_COOKIE_NAME, DEFAULT_RETURN_COOKIE_NAME, DEFAULT_STATE_COOKIE_NAME, DEFAULT_VERIFIER_COOKIE_NAME, type LogoutRouteConfig, type SsoMiddlewareConfig, createBackchannelLogoutRouteHandler, createCallbackRouteHandler, createLogoutRouteHandler, createSsoMiddleware, getHostCookieOptions, getSecureCookieOptions, toInsecureCookieName };
479
+ export { type BackchannelLogoutRouteConfig, type CallbackRouteConfig, DEFAULT_ACCESS_TOKEN_COOKIE_NAME, DEFAULT_ID_TOKEN_COOKIE_NAME, DEFAULT_LOGOUT_STATE_COOKIE_NAME, DEFAULT_NONCE_COOKIE_NAME, DEFAULT_REFRESH_TOKEN_COOKIE_NAME, DEFAULT_RETURN_COOKIE_NAME, DEFAULT_STATE_COOKIE_NAME, DEFAULT_VERIFIER_COOKIE_NAME, type LogoutJtiStore, type LogoutRouteConfig, type RefreshRouteConfig, type SsoMiddlewareConfig, createBackchannelLogoutRouteHandler, createCallbackRouteHandler, createLogoutRouteHandler, createRefreshRouteHandler, createSsoMiddleware, getHostCookieOptions, getSecureCookieOptions, toInsecureCookieName };