@pureq/auth 0.2.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.
Files changed (198) hide show
  1. package/README.md +293 -0
  2. package/dist/adapter/capabilities.d.ts +23 -0
  3. package/dist/adapter/capabilities.d.ts.map +1 -0
  4. package/dist/adapter/capabilities.js +77 -0
  5. package/dist/adapter/capabilities.js.map +1 -0
  6. package/dist/adapter/index.d.ts +12 -0
  7. package/dist/adapter/index.d.ts.map +1 -0
  8. package/dist/adapter/index.js +121 -0
  9. package/dist/adapter/index.js.map +1 -0
  10. package/dist/adapter/sql.d.ts +36 -0
  11. package/dist/adapter/sql.d.ts.map +1 -0
  12. package/dist/adapter/sql.js +268 -0
  13. package/dist/adapter/sql.js.map +1 -0
  14. package/dist/adapters/index.d.ts +4 -0
  15. package/dist/adapters/index.d.ts.map +1 -0
  16. package/dist/adapters/index.js +42 -0
  17. package/dist/adapters/index.js.map +1 -0
  18. package/dist/authorization/index.d.ts +8 -0
  19. package/dist/authorization/index.d.ts.map +1 -0
  20. package/dist/authorization/index.js +49 -0
  21. package/dist/authorization/index.js.map +1 -0
  22. package/dist/bridge/index.d.ts +23 -0
  23. package/dist/bridge/index.d.ts.map +1 -0
  24. package/dist/bridge/index.js +124 -0
  25. package/dist/bridge/index.js.map +1 -0
  26. package/dist/callbacks/index.d.ts +8 -0
  27. package/dist/callbacks/index.d.ts.map +1 -0
  28. package/dist/callbacks/index.js +53 -0
  29. package/dist/callbacks/index.js.map +1 -0
  30. package/dist/core/index.d.ts +12 -0
  31. package/dist/core/index.d.ts.map +1 -0
  32. package/dist/core/index.js +481 -0
  33. package/dist/core/index.js.map +1 -0
  34. package/dist/core/kit.d.ts +7 -0
  35. package/dist/core/kit.d.ts.map +1 -0
  36. package/dist/core/kit.js +145 -0
  37. package/dist/core/kit.js.map +1 -0
  38. package/dist/core/starter.d.ts +28 -0
  39. package/dist/core/starter.d.ts.map +1 -0
  40. package/dist/core/starter.js +67 -0
  41. package/dist/core/starter.js.map +1 -0
  42. package/dist/csrf/index.d.ts +7 -0
  43. package/dist/csrf/index.d.ts.map +1 -0
  44. package/dist/csrf/index.js +126 -0
  45. package/dist/csrf/index.js.map +1 -0
  46. package/dist/debug/index.d.ts +8 -0
  47. package/dist/debug/index.d.ts.map +1 -0
  48. package/dist/debug/index.js +21 -0
  49. package/dist/debug/index.js.map +1 -0
  50. package/dist/encryption/index.d.ts +8 -0
  51. package/dist/encryption/index.d.ts.map +1 -0
  52. package/dist/encryption/index.js +43 -0
  53. package/dist/encryption/index.js.map +1 -0
  54. package/dist/events/index.d.ts +22 -0
  55. package/dist/events/index.d.ts.map +1 -0
  56. package/dist/events/index.js +53 -0
  57. package/dist/events/index.js.map +1 -0
  58. package/dist/framework/index.d.ts +10 -0
  59. package/dist/framework/index.d.ts.map +1 -0
  60. package/dist/framework/index.js +68 -0
  61. package/dist/framework/index.js.map +1 -0
  62. package/dist/framework/packs.d.ts +54 -0
  63. package/dist/framework/packs.d.ts.map +1 -0
  64. package/dist/framework/packs.js +124 -0
  65. package/dist/framework/packs.js.map +1 -0
  66. package/dist/framework/recipes.d.ts +6 -0
  67. package/dist/framework/recipes.d.ts.map +1 -0
  68. package/dist/framework/recipes.js +108 -0
  69. package/dist/framework/recipes.js.map +1 -0
  70. package/dist/hooks/index.d.ts +11 -0
  71. package/dist/hooks/index.d.ts.map +1 -0
  72. package/dist/hooks/index.js +95 -0
  73. package/dist/hooks/index.js.map +1 -0
  74. package/dist/hooks/react.d.ts +9 -0
  75. package/dist/hooks/react.d.ts.map +1 -0
  76. package/dist/hooks/react.js +24 -0
  77. package/dist/hooks/react.js.map +1 -0
  78. package/dist/hooks/vue.d.ts +4 -0
  79. package/dist/hooks/vue.d.ts.map +1 -0
  80. package/dist/hooks/vue.js +32 -0
  81. package/dist/hooks/vue.js.map +1 -0
  82. package/dist/index.d.ts +36 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +31 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/jwt/index.d.ts +13 -0
  87. package/dist/jwt/index.d.ts.map +1 -0
  88. package/dist/jwt/index.js +82 -0
  89. package/dist/jwt/index.js.map +1 -0
  90. package/dist/middleware/authBasic.d.ts +5 -0
  91. package/dist/middleware/authBasic.d.ts.map +1 -0
  92. package/dist/middleware/authBasic.js +25 -0
  93. package/dist/middleware/authBasic.js.map +1 -0
  94. package/dist/middleware/authBearer.d.ts +4 -0
  95. package/dist/middleware/authBearer.d.ts.map +1 -0
  96. package/dist/middleware/authBearer.js +26 -0
  97. package/dist/middleware/authBearer.js.map +1 -0
  98. package/dist/middleware/authCustom.d.ts +4 -0
  99. package/dist/middleware/authCustom.d.ts.map +1 -0
  100. package/dist/middleware/authCustom.js +22 -0
  101. package/dist/middleware/authCustom.js.map +1 -0
  102. package/dist/middleware/authRefresh.d.ts +4 -0
  103. package/dist/middleware/authRefresh.d.ts.map +1 -0
  104. package/dist/middleware/authRefresh.js +68 -0
  105. package/dist/middleware/authRefresh.js.map +1 -0
  106. package/dist/middleware/authSession.d.ts +5 -0
  107. package/dist/middleware/authSession.d.ts.map +1 -0
  108. package/dist/middleware/authSession.js +35 -0
  109. package/dist/middleware/authSession.js.map +1 -0
  110. package/dist/middleware/broadcastSync.d.ts +7 -0
  111. package/dist/middleware/broadcastSync.d.ts.map +1 -0
  112. package/dist/middleware/broadcastSync.js +36 -0
  113. package/dist/middleware/broadcastSync.js.map +1 -0
  114. package/dist/middleware/common.d.ts +3 -0
  115. package/dist/middleware/common.d.ts.map +1 -0
  116. package/dist/middleware/common.js +10 -0
  117. package/dist/middleware/common.js.map +1 -0
  118. package/dist/middleware/index.d.ts +8 -0
  119. package/dist/middleware/index.d.ts.map +1 -0
  120. package/dist/middleware/index.js +8 -0
  121. package/dist/middleware/index.js.map +1 -0
  122. package/dist/middleware/tokenLifecycle.d.ts +4 -0
  123. package/dist/middleware/tokenLifecycle.d.ts.map +1 -0
  124. package/dist/middleware/tokenLifecycle.js +52 -0
  125. package/dist/middleware/tokenLifecycle.js.map +1 -0
  126. package/dist/migration/index.d.ts +40 -0
  127. package/dist/migration/index.d.ts.map +1 -0
  128. package/dist/migration/index.js +136 -0
  129. package/dist/migration/index.js.map +1 -0
  130. package/dist/oidc/index.d.ts +25 -0
  131. package/dist/oidc/index.d.ts.map +1 -0
  132. package/dist/oidc/index.js +392 -0
  133. package/dist/oidc/index.js.map +1 -0
  134. package/dist/oidc/providers.d.ts +21 -0
  135. package/dist/oidc/providers.d.ts.map +1 -0
  136. package/dist/oidc/providers.js +51 -0
  137. package/dist/oidc/providers.js.map +1 -0
  138. package/dist/presets/index.d.ts +13 -0
  139. package/dist/presets/index.d.ts.map +1 -0
  140. package/dist/presets/index.js +12 -0
  141. package/dist/presets/index.js.map +1 -0
  142. package/dist/providers/callbackContracts.d.ts +14 -0
  143. package/dist/providers/callbackContracts.d.ts.map +1 -0
  144. package/dist/providers/callbackContracts.js +14 -0
  145. package/dist/providers/callbackContracts.js.map +1 -0
  146. package/dist/providers/errors.d.ts +9 -0
  147. package/dist/providers/errors.d.ts.map +1 -0
  148. package/dist/providers/errors.js +66 -0
  149. package/dist/providers/errors.js.map +1 -0
  150. package/dist/providers/index.d.ts +28 -0
  151. package/dist/providers/index.d.ts.map +1 -0
  152. package/dist/providers/index.js +29 -0
  153. package/dist/providers/index.js.map +1 -0
  154. package/dist/providers/presets.d.ts +17 -0
  155. package/dist/providers/presets.d.ts.map +1 -0
  156. package/dist/providers/presets.js +84 -0
  157. package/dist/providers/presets.js.map +1 -0
  158. package/dist/revocation/index.d.ts +10 -0
  159. package/dist/revocation/index.d.ts.map +1 -0
  160. package/dist/revocation/index.js +182 -0
  161. package/dist/revocation/index.js.map +1 -0
  162. package/dist/session/exporters.d.ts +15 -0
  163. package/dist/session/exporters.d.ts.map +1 -0
  164. package/dist/session/exporters.js +62 -0
  165. package/dist/session/exporters.js.map +1 -0
  166. package/dist/session/index.d.ts +11 -0
  167. package/dist/session/index.d.ts.map +1 -0
  168. package/dist/session/index.js +324 -0
  169. package/dist/session/index.js.map +1 -0
  170. package/dist/shared/encoding.d.ts +5 -0
  171. package/dist/shared/encoding.d.ts.map +1 -0
  172. package/dist/shared/encoding.js +27 -0
  173. package/dist/shared/encoding.js.map +1 -0
  174. package/dist/shared/errors.d.ts +13 -0
  175. package/dist/shared/errors.d.ts.map +1 -0
  176. package/dist/shared/errors.js +12 -0
  177. package/dist/shared/errors.js.map +1 -0
  178. package/dist/shared/index.d.ts +5 -0
  179. package/dist/shared/index.d.ts.map +1 -0
  180. package/dist/shared/index.js +5 -0
  181. package/dist/shared/index.js.map +1 -0
  182. package/dist/shared/types.d.ts +585 -0
  183. package/dist/shared/types.d.ts.map +1 -0
  184. package/dist/shared/types.js +2 -0
  185. package/dist/shared/types.js.map +1 -0
  186. package/dist/shared/values.d.ts +3 -0
  187. package/dist/shared/values.d.ts.map +1 -0
  188. package/dist/shared/values.js +23 -0
  189. package/dist/shared/values.js.map +1 -0
  190. package/dist/storage/index.d.ts +44 -0
  191. package/dist/storage/index.d.ts.map +1 -0
  192. package/dist/storage/index.js +318 -0
  193. package/dist/storage/index.js.map +1 -0
  194. package/dist/templates/index.d.ts +9 -0
  195. package/dist/templates/index.d.ts.map +1 -0
  196. package/dist/templates/index.js +146 -0
  197. package/dist/templates/index.js.map +1 -0
  198. package/package.json +173 -0
@@ -0,0 +1,52 @@
1
+ import { markPolicyMiddleware } from "@pureq/pureq";
2
+ import { buildAuthError } from "../shared";
3
+ import { decodeJwt } from "../jwt";
4
+ export function withTokenLifecycle(options) {
5
+ const refreshThresholdMs = options.refreshThresholdMs ?? 5 * 60000;
6
+ let refreshPromise = null;
7
+ const ensureRefresh = async () => {
8
+ if (!refreshPromise) {
9
+ refreshPromise = options.onRefreshNeeded().finally(() => {
10
+ refreshPromise = null;
11
+ });
12
+ }
13
+ return refreshPromise;
14
+ };
15
+ const middleware = async (req, next) => {
16
+ const token = await options.storage.get();
17
+ if (token) {
18
+ let claims;
19
+ try {
20
+ claims = decodeJwt(token);
21
+ }
22
+ catch (error) {
23
+ throw buildAuthError("PUREQ_AUTH_INVALID_TOKEN", "pureq: failed to inspect token lifecycle", error);
24
+ }
25
+ if (typeof claims.exp === "number") {
26
+ const now = Date.now();
27
+ const expMs = claims.exp * 1000;
28
+ if (expMs <= now) {
29
+ options.onExpired?.();
30
+ try {
31
+ await options.storage.set(await ensureRefresh());
32
+ }
33
+ catch (error) {
34
+ throw buildAuthError("PUREQ_AUTH_EXPIRED", "pureq: token expired", error);
35
+ }
36
+ }
37
+ else if (expMs - now <= refreshThresholdMs) {
38
+ options.onStale?.();
39
+ try {
40
+ await options.storage.set(await ensureRefresh());
41
+ }
42
+ catch (error) {
43
+ throw buildAuthError("PUREQ_AUTH_REFRESH_FAILED", "pureq: token refresh failed", error);
44
+ }
45
+ }
46
+ }
47
+ }
48
+ return next(req);
49
+ };
50
+ return markPolicyMiddleware(middleware, { name: "withTokenLifecycle", kind: "auth" });
51
+ }
52
+ //# sourceMappingURL=tokenLifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenLifecycle.js","sourceRoot":"","sources":["../../src/middleware/tokenLifecycle.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAEnC,MAAM,UAAU,kBAAkB,CAAC,OAA8B;IAC/D,MAAM,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,IAAI,CAAC,GAAG,KAAM,CAAC;IACpE,IAAI,cAAc,GAA2B,IAAI,CAAC;IAElD,MAAM,aAAa,GAAG,KAAK,IAAqB,EAAE;QAChD,IAAI,CAAC,cAAc,EAAE,CAAC;YACpB,cAAc,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE;gBACtD,cAAc,GAAG,IAAI,CAAC;YACxB,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,cAAc,CAAC;IACxB,CAAC,CAAC;IAEF,MAAM,UAAU,GAAe,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QACjD,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QAC1C,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,MAAiC,CAAC;YAEtC,IAAI,CAAC;gBACH,MAAM,GAAG,SAAS,CAA4B,KAAK,CAAC,CAAC;YACvD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,cAAc,CAAC,0BAA0B,EAAE,0CAA0C,EAAE,KAAK,CAAC,CAAC;YACtG,CAAC;YAED,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;gBACnC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBACvB,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,GAAG,IAAI,CAAC;gBAChC,IAAI,KAAK,IAAI,GAAG,EAAE,CAAC;oBACjB,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC;oBACtB,IAAI,CAAC;wBACH,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,aAAa,EAAE,CAAC,CAAC;oBACnD,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,MAAM,cAAc,CAAC,oBAAoB,EAAE,sBAAsB,EAAE,KAAK,CAAC,CAAC;oBAC5E,CAAC;gBACH,CAAC;qBAAM,IAAI,KAAK,GAAG,GAAG,IAAI,kBAAkB,EAAE,CAAC;oBAC7C,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;oBACpB,IAAI,CAAC;wBACH,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,aAAa,EAAE,CAAC,CAAC;oBACnD,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,MAAM,cAAc,CAAC,2BAA2B,EAAE,6BAA6B,EAAE,KAAK,CAAC,CAAC;oBAC1F,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC,CAAC;IAEF,OAAO,oBAAoB,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;AACxF,CAAC"}
@@ -0,0 +1,40 @@
1
+ import type { AuthSessionManager, AuthSessionState, AuthStore, AuthTokens } from "../shared";
2
+ export interface AuthLegacyTokenSnapshot {
3
+ readonly accessToken?: string | null;
4
+ readonly access_token?: string | null;
5
+ readonly token?: string | null;
6
+ readonly refreshToken?: string | null;
7
+ readonly refresh_token?: string | null;
8
+ readonly refresh?: string | null;
9
+ readonly tokens?: AuthLegacyTokenSnapshot | null;
10
+ }
11
+ export interface AuthMigrationResult {
12
+ readonly tokens: AuthTokens | null;
13
+ readonly source: "legacy-object" | "legacy-string" | "legacy-nested" | "empty";
14
+ }
15
+ export type AuthMigrationParityStatus = "covered" | "partial" | "missing";
16
+ export interface AuthMigrationAnalysisInput {
17
+ readonly legacyInput?: unknown;
18
+ readonly hasProviders?: boolean;
19
+ readonly hasAdapter?: boolean;
20
+ readonly hasCallbacks?: boolean;
21
+ readonly hasSsrBridge?: boolean;
22
+ readonly enableCsrf?: boolean;
23
+ readonly enableRevocation?: boolean;
24
+ }
25
+ export interface AuthMigrationAnalysis {
26
+ readonly normalized: AuthMigrationResult;
27
+ readonly parity: Readonly<Record<string, AuthMigrationParityStatus>>;
28
+ readonly cutoverChecklist: readonly string[];
29
+ readonly rollbackChecklist: readonly string[];
30
+ }
31
+ export declare function normalizeLegacyAuthTokens(input: unknown): AuthMigrationResult;
32
+ export declare function migrateLegacyTokensToStore(store: AuthStore, input: unknown): Promise<AuthMigrationResult>;
33
+ export declare function hydrateSessionManagerFromLegacy(session: AuthSessionManager, input: unknown): Promise<AuthSessionState>;
34
+ export declare function analyzeAuthMigration(input?: AuthMigrationAnalysisInput): AuthMigrationAnalysis;
35
+ export declare function formatMigrationParityReport(analysis: AuthMigrationAnalysis): string;
36
+ export declare function generateMigrationChecklists(analysis: AuthMigrationAnalysis): {
37
+ readonly cutover: readonly string[];
38
+ readonly rollback: readonly string[];
39
+ };
40
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migration/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE7F,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,MAAM,EAAE,eAAe,GAAG,eAAe,GAAG,eAAe,GAAG,OAAO,CAAC;CAChF;AAED,MAAM,MAAM,yBAAyB,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAE1E,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAChC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACrC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC;IACrE,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,QAAQ,CAAC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/C;AAiBD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CA6C7E;AAED,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,mBAAmB,CAAC,CAiB9B;AAED,wBAAsB,+BAA+B,CACnD,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,OAAO,GACb,OAAO,CAAC,gBAAgB,CAAC,CAU3B;AAED,wBAAgB,oBAAoB,CAAC,KAAK,GAAE,0BAA+B,GAAG,qBAAqB,CA6ClG;AAED,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,qBAAqB,GAAG,MAAM,CAOnF;AAED,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,qBAAqB,GAAG;IAC5E,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC,CAKA"}
@@ -0,0 +1,136 @@
1
+ function isRecord(value) {
2
+ return typeof value === "object" && value !== null;
3
+ }
4
+ function readLegacyField(snapshot, keys) {
5
+ for (const key of keys) {
6
+ const candidate = snapshot[key];
7
+ if (typeof candidate === "string" && candidate.trim()) {
8
+ return candidate;
9
+ }
10
+ }
11
+ return null;
12
+ }
13
+ export function normalizeLegacyAuthTokens(input) {
14
+ if (typeof input === "string" && input.trim()) {
15
+ return {
16
+ tokens: {
17
+ accessToken: input,
18
+ },
19
+ source: "legacy-string",
20
+ };
21
+ }
22
+ if (!isRecord(input)) {
23
+ return {
24
+ tokens: null,
25
+ source: "empty",
26
+ };
27
+ }
28
+ const nested = input.tokens;
29
+ if (isRecord(nested)) {
30
+ const nestedResult = normalizeLegacyAuthTokens(nested);
31
+ if (nestedResult.tokens) {
32
+ return {
33
+ tokens: nestedResult.tokens,
34
+ source: "legacy-nested",
35
+ };
36
+ }
37
+ }
38
+ const accessToken = readLegacyField(input, ["accessToken", "access_token", "token"]);
39
+ const refreshToken = readLegacyField(input, ["refreshToken", "refresh_token", "refresh"]);
40
+ if (!accessToken) {
41
+ return {
42
+ tokens: null,
43
+ source: "empty",
44
+ };
45
+ }
46
+ return {
47
+ tokens: {
48
+ accessToken,
49
+ ...(refreshToken ? { refreshToken } : {}),
50
+ },
51
+ source: "legacy-object",
52
+ };
53
+ }
54
+ export async function migrateLegacyTokensToStore(store, input) {
55
+ const result = normalizeLegacyAuthTokens(input);
56
+ if (!result.tokens) {
57
+ await store.clear();
58
+ await store.clearRefresh();
59
+ return result;
60
+ }
61
+ await store.set(result.tokens.accessToken);
62
+ if (result.tokens.refreshToken) {
63
+ await store.setRefresh(result.tokens.refreshToken);
64
+ }
65
+ else {
66
+ await store.clearRefresh();
67
+ }
68
+ return result;
69
+ }
70
+ export async function hydrateSessionManagerFromLegacy(session, input) {
71
+ const result = normalizeLegacyAuthTokens(input);
72
+ if (!result.tokens) {
73
+ await session.clear();
74
+ return session.getState();
75
+ }
76
+ await session.setTokens(result.tokens);
77
+ return session.getState();
78
+ }
79
+ export function analyzeAuthMigration(input = {}) {
80
+ const normalized = normalizeLegacyAuthTokens(input.legacyInput ?? null);
81
+ const parity = {
82
+ providers: input.hasProviders ? "covered" : "missing",
83
+ adapter: input.hasAdapter ? "covered" : "missing",
84
+ callbacks: input.hasCallbacks ? "covered" : "partial",
85
+ ssrBridge: input.hasSsrBridge ? "covered" : "partial",
86
+ csrf: input.enableCsrf ? "covered" : "partial",
87
+ revocation: input.enableRevocation ? "covered" : "partial",
88
+ legacyTokens: normalized.tokens ? "covered" : "partial",
89
+ };
90
+ const cutoverChecklist = [];
91
+ if (parity.providers !== "covered") {
92
+ cutoverChecklist.push("Configure provider set and callback route mapping");
93
+ }
94
+ if (parity.adapter !== "covered") {
95
+ cutoverChecklist.push("Wire a production adapter and verify capability probe level");
96
+ }
97
+ if (parity.callbacks === "partial") {
98
+ cutoverChecklist.push("Define signIn/session/signOut callback policy contracts");
99
+ }
100
+ if (parity.ssrBridge === "partial") {
101
+ cutoverChecklist.push("Add SSR/BFF bridge bootstrap and response cookie handoff");
102
+ }
103
+ if (parity.csrf === "partial") {
104
+ cutoverChecklist.push("Enable CSRF protection for browser-mutating routes");
105
+ }
106
+ if (parity.revocation === "partial") {
107
+ cutoverChecklist.push("Enable revocation guard and jti/sid invalidation flow");
108
+ }
109
+ const rollbackChecklist = [
110
+ "Keep legacy token/session parser active behind feature flag",
111
+ "Retain previous auth route handlers for one release window",
112
+ "Gate AuthKit activation with environment toggle",
113
+ "Capture migration parity report in deployment artifact",
114
+ ];
115
+ return {
116
+ normalized,
117
+ parity,
118
+ cutoverChecklist,
119
+ rollbackChecklist,
120
+ };
121
+ }
122
+ export function formatMigrationParityReport(analysis) {
123
+ const lines = [
124
+ "| Area | Status |",
125
+ "| --- | --- |",
126
+ ...Object.entries(analysis.parity).map(([area, status]) => `| ${area} | ${status} |`),
127
+ ];
128
+ return lines.join("\n");
129
+ }
130
+ export function generateMigrationChecklists(analysis) {
131
+ return {
132
+ cutover: analysis.cutoverChecklist,
133
+ rollback: analysis.rollbackChecklist,
134
+ };
135
+ }
136
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migration/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import type { OIDCCallbackParams, OIDCFlow, OIDCFlowOptions, OIDCProviderDefinition } from "../shared";
2
+ import { oidcProviders } from "./providers";
3
+ export declare function parseOIDCCallbackParams(callback: string | URL | URLSearchParams, expectedState?: string): OIDCCallbackParams;
4
+ /**
5
+ * Create an OIDC authorization code flow.
6
+ *
7
+ * SEC-C5: getAuthorizationUrl returns { url, state, codeVerifier, nonce }.
8
+ * SEC-H1: No singleton codeVerifier — returned to caller for explicit management.
9
+ * SEC-H5: id_token validation (iss, aud, exp, nonce).
10
+ * SEC-H6: nonce support.
11
+ * SEC-M5: tokenEndpointAuthMethod support.
12
+ * FEAT-M4: Full OIDC metadata extraction.
13
+ * FEAT-M5: getLogoutUrl.
14
+ * FEAT-L4: introspect.
15
+ */
16
+ export declare function createOIDCFlow(options: OIDCFlowOptions): OIDCFlow;
17
+ /** @deprecated Use `createOIDCFlow` (capital F) instead. */
18
+ export declare const createOIDCflow: typeof createOIDCFlow;
19
+ export declare function createOIDCFlowFromProvider(provider: OIDCProviderDefinition, options: Omit<OIDCFlowOptions, "discoveryUrl" | "defaultScope"> & {
20
+ readonly defaultScope?: readonly string[];
21
+ }): OIDCFlow;
22
+ /** @deprecated Use `createOIDCFlowFromProvider` (capital F) instead. */
23
+ export declare const createOIDCflowFromProvider: typeof createOIDCFlowFromProvider;
24
+ export { oidcProviders };
25
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/oidc/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,kBAAkB,EAClB,QAAQ,EACR,eAAe,EACf,sBAAsB,EAGvB,MAAM,WAAW,CAAC;AAGnB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA2I5C,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,GAAG,GAAG,GAAG,eAAe,EACxC,aAAa,CAAC,EAAE,MAAM,GACrB,kBAAkB,CAmDpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,QAAQ,CA+NjE;AAED,4DAA4D;AAC5D,eAAO,MAAM,cAAc,uBAAiB,CAAC;AAE7C,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,sBAAsB,EAChC,OAAO,EAAE,IAAI,CAAC,eAAe,EAAE,cAAc,GAAG,cAAc,CAAC,GAAG;IAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAC9G,QAAQ,CAsCV;AAED,wEAAwE;AACxE,eAAO,MAAM,0BAA0B,mCAA6B,CAAC;AAErE,OAAO,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,392 @@
1
+ import { generateSecureId } from "@pureq/pureq";
2
+ import { base64UrlEncode, base64Encode, createAuthError } from "../shared";
3
+ import { decodeJwt } from "../jwt";
4
+ import { oidcProviders } from "./providers";
5
+ const MAX_CALLBACK_VALUE_LENGTH = 4096;
6
+ const REPLAY_TTL_MS = 10 * 60 * 1000; // 10 minutes
7
+ const MAX_REPLAY_ENTRIES = 10000;
8
+ function toTokenResponse(raw) {
9
+ const accessToken = raw.access_token ?? raw.accessToken;
10
+ if (typeof accessToken !== "string" || !accessToken.trim()) {
11
+ throw createAuthError("PUREQ_OIDC_INVALID_TOKEN_RESPONSE", "pureq: OIDC token response is missing access_token", {
12
+ details: { keys: Object.keys(raw).sort().join(",") },
13
+ });
14
+ }
15
+ return {
16
+ accessToken,
17
+ ...(typeof raw.id_token === "string" ? { idToken: raw.id_token } : {}),
18
+ ...(typeof raw.refresh_token === "string" ? { refreshToken: raw.refresh_token } : {}),
19
+ ...(typeof raw.token_type === "string" ? { tokenType: raw.token_type } : {}),
20
+ ...(typeof raw.expires_in === "number" ? { expiresIn: raw.expires_in } : {}),
21
+ ...(typeof raw.scope === "string" ? { scope: raw.scope } : {}),
22
+ raw,
23
+ };
24
+ }
25
+ async function fetchMetadata(discoveryUrl) {
26
+ const response = await fetch(discoveryUrl);
27
+ if (!response.ok) {
28
+ throw createAuthError("PUREQ_OIDC_DISCOVERY_FAILED", `pureq: failed to load OIDC metadata (${response.status})`, {
29
+ details: { status: response.status, discoveryUrl },
30
+ });
31
+ }
32
+ const json = (await response.json());
33
+ if (!json.authorization_endpoint || !json.token_endpoint) {
34
+ throw createAuthError("PUREQ_OIDC_INVALID_DISCOVERY_DOCUMENT", "pureq: invalid OIDC discovery document", {
35
+ details: { discoveryUrl },
36
+ });
37
+ }
38
+ return {
39
+ authorization_endpoint: json.authorization_endpoint,
40
+ token_endpoint: json.token_endpoint,
41
+ ...(json.userinfo_endpoint !== undefined ? { userinfo_endpoint: json.userinfo_endpoint } : {}),
42
+ ...(json.jwks_uri !== undefined ? { jwks_uri: json.jwks_uri } : {}),
43
+ ...(json.end_session_endpoint !== undefined ? { end_session_endpoint: json.end_session_endpoint } : {}),
44
+ ...(json.revocation_endpoint !== undefined ? { revocation_endpoint: json.revocation_endpoint } : {}),
45
+ ...(json.introspection_endpoint !== undefined ? { introspection_endpoint: json.introspection_endpoint } : {}),
46
+ ...(json.issuer !== undefined ? { issuer: json.issuer } : {}),
47
+ };
48
+ }
49
+ async function createCodeChallenge(verifier) {
50
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
51
+ return base64UrlEncode(new Uint8Array(digest));
52
+ }
53
+ function toSearchParams(callback) {
54
+ if (callback instanceof URLSearchParams) {
55
+ return callback;
56
+ }
57
+ if (callback instanceof URL) {
58
+ return callback.searchParams;
59
+ }
60
+ if (callback.startsWith("http://") || callback.startsWith("https://")) {
61
+ return new URL(callback).searchParams;
62
+ }
63
+ if (callback.startsWith("?")) {
64
+ return new URLSearchParams(callback.slice(1));
65
+ }
66
+ return new URLSearchParams(callback);
67
+ }
68
+ function sweepReplayCache(cache, now) {
69
+ if (cache.size <= MAX_REPLAY_ENTRIES) {
70
+ return;
71
+ }
72
+ for (const [key, ts] of cache) {
73
+ if (now - ts > REPLAY_TTL_MS || cache.size > MAX_REPLAY_ENTRIES) {
74
+ cache.delete(key);
75
+ }
76
+ }
77
+ }
78
+ /** Validate id_token claims (iss, aud, exp, nonce). */
79
+ function validateIdTokenClaims(idToken, expectedIssuer, expectedAudience, expectedNonce) {
80
+ let claims;
81
+ try {
82
+ claims = decodeJwt(idToken);
83
+ }
84
+ catch {
85
+ throw createAuthError("PUREQ_OIDC_INVALID_ID_TOKEN", "pureq: failed to decode id_token");
86
+ }
87
+ if (expectedIssuer && claims.iss !== expectedIssuer) {
88
+ throw createAuthError("PUREQ_OIDC_ID_TOKEN_ISSUER_MISMATCH", "pureq: id_token issuer mismatch", {
89
+ details: { expected: expectedIssuer, actual: claims.iss ?? "undefined" },
90
+ });
91
+ }
92
+ const aud = Array.isArray(claims.aud) ? claims.aud : [claims.aud];
93
+ if (!aud.includes(expectedAudience)) {
94
+ throw createAuthError("PUREQ_OIDC_ID_TOKEN_AUDIENCE_MISMATCH", "pureq: id_token audience mismatch", {
95
+ details: { expected: expectedAudience },
96
+ });
97
+ }
98
+ if (typeof claims.exp === "number" && claims.exp * 1000 < Date.now()) {
99
+ throw createAuthError("PUREQ_OIDC_ID_TOKEN_EXPIRED", "pureq: id_token has expired");
100
+ }
101
+ if (expectedNonce && claims.nonce !== expectedNonce) {
102
+ throw createAuthError("PUREQ_OIDC_ID_TOKEN_NONCE_MISMATCH", "pureq: id_token nonce mismatch");
103
+ }
104
+ }
105
+ function buildAuthHeader(clientId, clientSecret) {
106
+ return `Basic ${base64Encode(`${clientId}:${clientSecret}`)}`;
107
+ }
108
+ export function parseOIDCCallbackParams(callback, expectedState) {
109
+ const params = toSearchParams(callback);
110
+ const readSingle = (name) => {
111
+ const values = params.getAll(name).filter((value) => value.length > 0);
112
+ if (values.length > 1) {
113
+ throw createAuthError("PUREQ_OIDC_INVALID_CALLBACK", `pureq: OIDC callback has duplicated ${name} parameter`, {
114
+ details: { parameter: name },
115
+ });
116
+ }
117
+ const value = values[0] ?? null;
118
+ if (value !== null && value.length > MAX_CALLBACK_VALUE_LENGTH) {
119
+ throw createAuthError("PUREQ_OIDC_INVALID_CALLBACK", `pureq: OIDC callback ${name} is too large`, {
120
+ details: { parameter: name, length: value.length },
121
+ });
122
+ }
123
+ return value;
124
+ };
125
+ const error = readSingle("error");
126
+ if (error) {
127
+ const description = readSingle("error_description");
128
+ throw createAuthError("PUREQ_OIDC_CALLBACK_ERROR", description ? `pureq: OIDC callback error (${error}): ${description}` : `pureq: OIDC callback error (${error})`, {
129
+ details: {
130
+ error,
131
+ ...(description ? { description } : {}),
132
+ },
133
+ });
134
+ }
135
+ const code = readSingle("code");
136
+ if (!code) {
137
+ throw createAuthError("PUREQ_OIDC_MISSING_CODE", "pureq: OIDC callback is missing authorization code", {
138
+ details: { callback: String(callback) },
139
+ });
140
+ }
141
+ const state = readSingle("state") ?? undefined;
142
+ if (expectedState && state !== expectedState) {
143
+ throw createAuthError("PUREQ_OIDC_STATE_MISMATCH", "pureq: OIDC state mismatch", {
144
+ details: {
145
+ expectedState,
146
+ ...(state !== undefined ? { state } : {}),
147
+ },
148
+ });
149
+ }
150
+ return {
151
+ code,
152
+ ...(state ? { state } : {}),
153
+ };
154
+ }
155
+ /**
156
+ * Create an OIDC authorization code flow.
157
+ *
158
+ * SEC-C5: getAuthorizationUrl returns { url, state, codeVerifier, nonce }.
159
+ * SEC-H1: No singleton codeVerifier — returned to caller for explicit management.
160
+ * SEC-H5: id_token validation (iss, aud, exp, nonce).
161
+ * SEC-H6: nonce support.
162
+ * SEC-M5: tokenEndpointAuthMethod support.
163
+ * FEAT-M4: Full OIDC metadata extraction.
164
+ * FEAT-M5: getLogoutUrl.
165
+ * FEAT-L4: introspect.
166
+ */
167
+ export function createOIDCFlow(options) {
168
+ if (!options.clientId.trim()) {
169
+ throw createAuthError("PUREQ_OIDC_INVALID_CONFIGURATION", "pureq: OIDC clientId is required", {
170
+ details: { field: "clientId" },
171
+ });
172
+ }
173
+ if (!options.discoveryUrl.trim()) {
174
+ throw createAuthError("PUREQ_OIDC_INVALID_CONFIGURATION", "pureq: OIDC discoveryUrl is required", {
175
+ details: { field: "discoveryUrl" },
176
+ });
177
+ }
178
+ if (!options.redirectUri.trim()) {
179
+ throw createAuthError("PUREQ_OIDC_INVALID_CONFIGURATION", "pureq: OIDC redirectUri is required", {
180
+ details: { field: "redirectUri" },
181
+ });
182
+ }
183
+ let metadataPromise = null;
184
+ // SEC-H2: TTL-based replay detection
185
+ const consumedCallbackCodes = new Map();
186
+ const authMethod = options.tokenEndpointAuthMethod ?? "client_secret_post";
187
+ const getMetadata = () => {
188
+ if (!metadataPromise) {
189
+ metadataPromise = fetchMetadata(options.discoveryUrl);
190
+ }
191
+ return metadataPromise;
192
+ };
193
+ const buildTokenHeaders = () => {
194
+ const headers = { "Content-Type": "application/x-www-form-urlencoded" };
195
+ if (authMethod === "client_secret_basic" && options.clientSecret) {
196
+ headers["Authorization"] = buildAuthHeader(options.clientId, options.clientSecret);
197
+ }
198
+ return headers;
199
+ };
200
+ const appendClientCredentials = (body) => {
201
+ body.set("client_id", options.clientId);
202
+ if (authMethod === "client_secret_post" && options.clientSecret) {
203
+ body.set("client_secret", options.clientSecret);
204
+ }
205
+ };
206
+ return {
207
+ async getAuthorizationUrl(authorizationOptions = {}) {
208
+ const metadata = await getMetadata();
209
+ const url = new URL(metadata.authorization_endpoint);
210
+ const state = authorizationOptions.state ?? generateSecureId("oidc-state");
211
+ const nonce = authorizationOptions.nonce ?? generateSecureId("oidc-nonce");
212
+ const scope = authorizationOptions.scope ?? options.defaultScope ?? ["openid"];
213
+ const codeVerifier = generateSecureId("pkce");
214
+ const codeChallengeMethod = authorizationOptions.codeChallengeMethod ?? "S256";
215
+ const codeChallenge = authorizationOptions.codeChallenge ??
216
+ (codeChallengeMethod === "plain" ? codeVerifier : await createCodeChallenge(codeVerifier));
217
+ url.searchParams.set("response_type", "code");
218
+ url.searchParams.set("client_id", options.clientId);
219
+ url.searchParams.set("redirect_uri", options.redirectUri);
220
+ url.searchParams.set("scope", scope.join(" "));
221
+ url.searchParams.set("state", state);
222
+ url.searchParams.set("nonce", nonce);
223
+ url.searchParams.set("code_challenge", codeChallenge);
224
+ url.searchParams.set("code_challenge_method", codeChallengeMethod);
225
+ if (authorizationOptions.prompt) {
226
+ url.searchParams.set("prompt", authorizationOptions.prompt);
227
+ }
228
+ for (const [key, value] of Object.entries(authorizationOptions.extraParams ?? {})) {
229
+ url.searchParams.set(key, value);
230
+ }
231
+ return { url: url.toString(), state, codeVerifier, nonce };
232
+ },
233
+ async exchangeCode(code, requestOptions) {
234
+ const metadata = await getMetadata();
235
+ const body = new URLSearchParams({
236
+ grant_type: "authorization_code",
237
+ code,
238
+ redirect_uri: options.redirectUri,
239
+ code_verifier: requestOptions.codeVerifier,
240
+ });
241
+ appendClientCredentials(body);
242
+ const response = await fetch(metadata.token_endpoint, {
243
+ method: "POST",
244
+ headers: buildTokenHeaders(),
245
+ body: body.toString(),
246
+ });
247
+ if (!response.ok) {
248
+ throw createAuthError("PUREQ_OIDC_TOKEN_EXCHANGE_FAILED", `pureq: OIDC code exchange failed (${response.status})`, {
249
+ details: { status: response.status, tokenEndpoint: metadata.token_endpoint },
250
+ });
251
+ }
252
+ const tokenResponse = toTokenResponse((await response.json()));
253
+ // SEC-H5: validate id_token if present
254
+ if (tokenResponse.idToken) {
255
+ validateIdTokenClaims(tokenResponse.idToken, metadata.issuer, options.clientId, undefined);
256
+ }
257
+ return tokenResponse;
258
+ },
259
+ async exchangeCallback(callback, requestOptions) {
260
+ const params = parseOIDCCallbackParams(callback, requestOptions.expectedState);
261
+ // SEC-H2: TTL-based replay detection
262
+ const now = Date.now();
263
+ sweepReplayCache(consumedCallbackCodes, now);
264
+ const existingTs = consumedCallbackCodes.get(params.code);
265
+ if (existingTs !== undefined && now - existingTs < REPLAY_TTL_MS) {
266
+ throw createAuthError("PUREQ_OIDC_CALLBACK_REPLAY", "pureq: OIDC callback code replay detected", {
267
+ details: { code: params.code },
268
+ });
269
+ }
270
+ consumedCallbackCodes.set(params.code, now);
271
+ const tokenResponse = await this.exchangeCode(params.code, {
272
+ codeVerifier: requestOptions.codeVerifier,
273
+ });
274
+ // SEC-H5 + SEC-H6: validate id_token nonce
275
+ if (tokenResponse.idToken && requestOptions.expectedNonce) {
276
+ const metadata = await getMetadata();
277
+ validateIdTokenClaims(tokenResponse.idToken, metadata.issuer, options.clientId, requestOptions.expectedNonce);
278
+ }
279
+ return tokenResponse;
280
+ },
281
+ async refresh(refreshToken) {
282
+ const metadata = await getMetadata();
283
+ const body = new URLSearchParams({
284
+ grant_type: "refresh_token",
285
+ refresh_token: refreshToken,
286
+ });
287
+ appendClientCredentials(body);
288
+ const response = await fetch(metadata.token_endpoint, {
289
+ method: "POST",
290
+ headers: buildTokenHeaders(),
291
+ body: body.toString(),
292
+ });
293
+ if (!response.ok) {
294
+ throw createAuthError("PUREQ_OIDC_TOKEN_REFRESH_FAILED", `pureq: OIDC token refresh failed (${response.status})`, {
295
+ details: { status: response.status, tokenEndpoint: metadata.token_endpoint },
296
+ });
297
+ }
298
+ return toTokenResponse((await response.json()));
299
+ },
300
+ /** Fetch user profile from the OIDC userinfo endpoint (FEAT-M4). */
301
+ async getUserInfo(accessToken) {
302
+ const metadata = await getMetadata();
303
+ if (!metadata.userinfo_endpoint) {
304
+ throw createAuthError("PUREQ_OIDC_NO_USERINFO_ENDPOINT", "pureq: OIDC provider does not expose a userinfo endpoint");
305
+ }
306
+ const response = await fetch(metadata.userinfo_endpoint, {
307
+ headers: { Authorization: `Bearer ${accessToken}` },
308
+ });
309
+ if (!response.ok) {
310
+ throw createAuthError("PUREQ_OIDC_USERINFO_FAILED", `pureq: OIDC userinfo request failed (${response.status})`);
311
+ }
312
+ return (await response.json());
313
+ },
314
+ /** Build a logout URL for RP-initiated logout (FEAT-M5). */
315
+ async getLogoutUrl(logoutOptions) {
316
+ const metadata = await getMetadata();
317
+ if (!metadata.end_session_endpoint) {
318
+ throw createAuthError("PUREQ_OIDC_NO_END_SESSION_ENDPOINT", "pureq: OIDC provider does not expose an end_session_endpoint");
319
+ }
320
+ const url = new URL(metadata.end_session_endpoint);
321
+ if (logoutOptions?.idTokenHint) {
322
+ url.searchParams.set("id_token_hint", logoutOptions.idTokenHint);
323
+ }
324
+ if (logoutOptions?.postLogoutRedirectUri) {
325
+ url.searchParams.set("post_logout_redirect_uri", logoutOptions.postLogoutRedirectUri);
326
+ }
327
+ url.searchParams.set("client_id", options.clientId);
328
+ return url.toString();
329
+ },
330
+ /** Introspect a token via RFC 7662 (FEAT-L4). */
331
+ async introspect(token) {
332
+ const metadata = await getMetadata();
333
+ if (!metadata.introspection_endpoint) {
334
+ throw createAuthError("PUREQ_OIDC_NO_INTROSPECTION_ENDPOINT", "pureq: OIDC provider does not expose an introspection endpoint");
335
+ }
336
+ const body = new URLSearchParams({ token });
337
+ appendClientCredentials(body);
338
+ const response = await fetch(metadata.introspection_endpoint, {
339
+ method: "POST",
340
+ headers: buildTokenHeaders(),
341
+ body: body.toString(),
342
+ });
343
+ if (!response.ok) {
344
+ throw createAuthError("PUREQ_OIDC_INTROSPECTION_FAILED", `pureq: OIDC token introspection failed (${response.status})`);
345
+ }
346
+ return (await response.json());
347
+ },
348
+ };
349
+ }
350
+ /** @deprecated Use `createOIDCFlow` (capital F) instead. */
351
+ export const createOIDCflow = createOIDCFlow;
352
+ export function createOIDCFlowFromProvider(provider, options) {
353
+ if (!provider.name.trim()) {
354
+ throw createAuthError("PUREQ_OIDC_INVALID_PROVIDER", "pureq: OIDC provider name is required", {
355
+ details: { field: "name" },
356
+ });
357
+ }
358
+ if (!provider.discoveryUrl.trim()) {
359
+ throw createAuthError("PUREQ_OIDC_INVALID_PROVIDER", "pureq: OIDC provider discoveryUrl is required", {
360
+ details: { field: "discoveryUrl", provider: provider.name },
361
+ });
362
+ }
363
+ const flow = createOIDCFlow({
364
+ ...options,
365
+ discoveryUrl: provider.discoveryUrl,
366
+ ...(options.defaultScope !== undefined || provider.defaultScope !== undefined
367
+ ? { defaultScope: options.defaultScope ?? provider.defaultScope }
368
+ : {}),
369
+ });
370
+ return {
371
+ async getAuthorizationUrl(authorizationOptions = {}) {
372
+ provider.validateAuthorizationOptions?.(authorizationOptions);
373
+ return flow.getAuthorizationUrl({
374
+ ...authorizationOptions,
375
+ extraParams: {
376
+ ...(provider.authorizationDefaults ?? {}),
377
+ ...(authorizationOptions.extraParams ?? {}),
378
+ },
379
+ });
380
+ },
381
+ exchangeCode: flow.exchangeCode.bind(flow),
382
+ exchangeCallback: flow.exchangeCallback.bind(flow),
383
+ refresh: flow.refresh.bind(flow),
384
+ ...(flow.getUserInfo ? { getUserInfo: flow.getUserInfo.bind(flow) } : {}),
385
+ ...(flow.getLogoutUrl ? { getLogoutUrl: flow.getLogoutUrl.bind(flow) } : {}),
386
+ ...(flow.introspect ? { introspect: flow.introspect.bind(flow) } : {}),
387
+ };
388
+ }
389
+ /** @deprecated Use `createOIDCFlowFromProvider` (capital F) instead. */
390
+ export const createOIDCflowFromProvider = createOIDCFlowFromProvider;
391
+ export { oidcProviders };
392
+ //# sourceMappingURL=index.js.map