@draftlab/auth 0.15.0 → 0.16.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 (272) hide show
  1. package/dist/esm/allow.js +26 -0
  2. package/dist/esm/client.js +254 -0
  3. package/dist/esm/core.js +597 -0
  4. package/dist/esm/css.d.js +0 -0
  5. package/dist/esm/error.js +88 -0
  6. package/dist/esm/index.js +5 -0
  7. package/dist/esm/keys.js +126 -0
  8. package/dist/esm/mutex.js +53 -0
  9. package/dist/esm/pkce.js +87 -0
  10. package/dist/esm/provider/apple.js +15 -0
  11. package/dist/esm/provider/code.js +62 -0
  12. package/dist/esm/provider/discord.js +15 -0
  13. package/dist/esm/provider/facebook.js +15 -0
  14. package/dist/esm/provider/github.js +15 -0
  15. package/dist/esm/provider/gitlab.js +15 -0
  16. package/dist/esm/provider/google.js +16 -0
  17. package/dist/esm/provider/linkedin.js +15 -0
  18. package/dist/esm/provider/magiclink.js +83 -0
  19. package/dist/esm/provider/microsoft.js +15 -0
  20. package/dist/esm/provider/oauth2.js +130 -0
  21. package/dist/esm/provider/password.js +331 -0
  22. package/dist/esm/provider/provider.js +18 -0
  23. package/dist/esm/provider/reddit.js +15 -0
  24. package/dist/esm/provider/slack.js +15 -0
  25. package/dist/esm/provider/spotify.js +15 -0
  26. package/dist/esm/provider/twitch.js +15 -0
  27. package/dist/esm/provider/vercel.js +17 -0
  28. package/dist/esm/random.js +40 -0
  29. package/dist/esm/revocation.js +27 -0
  30. package/dist/esm/storage/memory.js +110 -0
  31. package/dist/esm/storage/storage.js +56 -0
  32. package/dist/esm/storage/turso.js +93 -0
  33. package/dist/esm/storage/unstorage.js +78 -0
  34. package/dist/esm/subject.js +7 -0
  35. package/dist/esm/themes/theme.js +115 -0
  36. package/dist/esm/toolkit/client.js +119 -0
  37. package/dist/esm/toolkit/index.js +25 -0
  38. package/dist/esm/toolkit/providers/facebook.js +11 -0
  39. package/dist/esm/toolkit/providers/github.js +11 -0
  40. package/dist/esm/toolkit/providers/google.js +11 -0
  41. package/dist/esm/toolkit/providers/strategy.js +0 -0
  42. package/dist/esm/toolkit/storage.js +81 -0
  43. package/dist/esm/toolkit/utils.js +18 -0
  44. package/dist/esm/types.js +0 -0
  45. package/dist/esm/ui/base.js +478 -0
  46. package/dist/esm/ui/code.js +186 -0
  47. package/dist/esm/ui/form.js +46 -0
  48. package/dist/esm/ui/icon.js +242 -0
  49. package/dist/esm/ui/magiclink.js +158 -0
  50. package/dist/esm/ui/password.js +435 -0
  51. package/dist/esm/ui/select.js +102 -0
  52. package/dist/esm/util.js +59 -0
  53. package/dist/{allow.d.mts → types/allow.d.ts} +9 -11
  54. package/dist/types/allow.d.ts.map +1 -0
  55. package/dist/types/client.d.ts +462 -0
  56. package/dist/types/client.d.ts.map +1 -0
  57. package/dist/types/core.d.ts +113 -0
  58. package/dist/types/core.d.ts.map +1 -0
  59. package/dist/{error.d.mts → types/error.d.ts} +95 -97
  60. package/dist/types/error.d.ts.map +1 -0
  61. package/dist/types/index.d.ts +2 -0
  62. package/dist/types/index.d.ts.map +1 -0
  63. package/dist/{keys.d.mts → types/keys.d.ts} +20 -24
  64. package/dist/types/keys.d.ts.map +1 -0
  65. package/dist/types/mutex.d.ts +42 -0
  66. package/dist/types/mutex.d.ts.map +1 -0
  67. package/dist/{pkce.d.mts → types/pkce.d.ts} +10 -11
  68. package/dist/types/pkce.d.ts.map +1 -0
  69. package/dist/types/provider/apple.d.ts +197 -0
  70. package/dist/types/provider/apple.d.ts.map +1 -0
  71. package/dist/types/provider/code.d.ts +288 -0
  72. package/dist/types/provider/code.d.ts.map +1 -0
  73. package/dist/types/provider/discord.d.ts +206 -0
  74. package/dist/types/provider/discord.d.ts.map +1 -0
  75. package/dist/types/provider/facebook.d.ts +200 -0
  76. package/dist/types/provider/facebook.d.ts.map +1 -0
  77. package/dist/types/provider/github.d.ts +220 -0
  78. package/dist/types/provider/github.d.ts.map +1 -0
  79. package/dist/types/provider/gitlab.d.ts +180 -0
  80. package/dist/types/provider/gitlab.d.ts.map +1 -0
  81. package/dist/types/provider/google.d.ts +158 -0
  82. package/dist/types/provider/google.d.ts.map +1 -0
  83. package/dist/types/provider/linkedin.d.ts +190 -0
  84. package/dist/types/provider/linkedin.d.ts.map +1 -0
  85. package/dist/types/provider/magiclink.d.ts +141 -0
  86. package/dist/types/provider/magiclink.d.ts.map +1 -0
  87. package/dist/types/provider/microsoft.d.ts +247 -0
  88. package/dist/types/provider/microsoft.d.ts.map +1 -0
  89. package/dist/types/provider/oauth2.d.ts +229 -0
  90. package/dist/types/provider/oauth2.d.ts.map +1 -0
  91. package/dist/types/provider/password.d.ts +408 -0
  92. package/dist/types/provider/password.d.ts.map +1 -0
  93. package/dist/types/provider/provider.d.ts +226 -0
  94. package/dist/types/provider/provider.d.ts.map +1 -0
  95. package/dist/types/provider/reddit.d.ts +159 -0
  96. package/dist/types/provider/reddit.d.ts.map +1 -0
  97. package/dist/types/provider/slack.d.ts +171 -0
  98. package/dist/types/provider/slack.d.ts.map +1 -0
  99. package/dist/types/provider/spotify.d.ts +168 -0
  100. package/dist/types/provider/spotify.d.ts.map +1 -0
  101. package/dist/types/provider/twitch.d.ts +163 -0
  102. package/dist/types/provider/twitch.d.ts.map +1 -0
  103. package/dist/types/provider/vercel.d.ts +294 -0
  104. package/dist/types/provider/vercel.d.ts.map +1 -0
  105. package/dist/{random.d.mts → types/random.d.ts} +4 -6
  106. package/dist/types/random.d.ts.map +1 -0
  107. package/dist/types/revocation.d.ts +76 -0
  108. package/dist/types/revocation.d.ts.map +1 -0
  109. package/dist/{storage/memory.d.mts → types/storage/memory.d.ts} +17 -21
  110. package/dist/types/storage/memory.d.ts.map +1 -0
  111. package/dist/types/storage/storage.d.ts +177 -0
  112. package/dist/types/storage/storage.d.ts.map +1 -0
  113. package/dist/{storage/turso.d.mts → types/storage/turso.d.ts} +4 -8
  114. package/dist/types/storage/turso.d.ts.map +1 -0
  115. package/dist/{storage/unstorage.d.mts → types/storage/unstorage.d.ts} +12 -11
  116. package/dist/types/storage/unstorage.d.ts.map +1 -0
  117. package/dist/types/subject.d.ts +115 -0
  118. package/dist/types/subject.d.ts.map +1 -0
  119. package/dist/types/themes/theme.d.ts +207 -0
  120. package/dist/types/themes/theme.d.ts.map +1 -0
  121. package/dist/types/toolkit/client.d.ts +235 -0
  122. package/dist/types/toolkit/client.d.ts.map +1 -0
  123. package/dist/types/toolkit/index.d.ts +45 -0
  124. package/dist/types/toolkit/index.d.ts.map +1 -0
  125. package/dist/types/toolkit/providers/facebook.d.ts +8 -0
  126. package/dist/types/toolkit/providers/facebook.d.ts.map +1 -0
  127. package/dist/types/toolkit/providers/github.d.ts +8 -0
  128. package/dist/types/toolkit/providers/github.d.ts.map +1 -0
  129. package/dist/types/toolkit/providers/google.d.ts +8 -0
  130. package/dist/types/toolkit/providers/google.d.ts.map +1 -0
  131. package/dist/types/toolkit/providers/strategy.d.ts +38 -0
  132. package/dist/types/toolkit/providers/strategy.d.ts.map +1 -0
  133. package/dist/{toolkit/storage.d.mts → types/toolkit/storage.d.ts} +37 -39
  134. package/dist/types/toolkit/storage.d.ts.map +1 -0
  135. package/dist/{toolkit/utils.d.mts → types/toolkit/utils.d.ts} +2 -4
  136. package/dist/types/toolkit/utils.d.ts.map +1 -0
  137. package/dist/types/types.d.ts +92 -0
  138. package/dist/types/types.d.ts.map +1 -0
  139. package/dist/types/ui/base.d.ts +18 -0
  140. package/dist/types/ui/base.d.ts.map +1 -0
  141. package/dist/types/ui/code.d.ts +43 -0
  142. package/dist/types/ui/code.d.ts.map +1 -0
  143. package/dist/types/ui/form.d.ts +24 -0
  144. package/dist/types/ui/form.d.ts.map +1 -0
  145. package/dist/types/ui/icon.d.ts +60 -0
  146. package/dist/types/ui/icon.d.ts.map +1 -0
  147. package/dist/types/ui/magiclink.d.ts +41 -0
  148. package/dist/types/ui/magiclink.d.ts.map +1 -0
  149. package/dist/types/ui/password.d.ts +43 -0
  150. package/dist/types/ui/password.d.ts.map +1 -0
  151. package/dist/types/ui/select.d.ts +33 -0
  152. package/dist/types/ui/select.d.ts.map +1 -0
  153. package/dist/{util.d.mts → types/util.d.ts} +11 -13
  154. package/dist/types/util.d.ts.map +1 -0
  155. package/package.json +10 -16
  156. package/dist/adapters/node.d.mts +0 -18
  157. package/dist/adapters/node.mjs +0 -69
  158. package/dist/allow.mjs +0 -63
  159. package/dist/client.d.mts +0 -456
  160. package/dist/client.mjs +0 -283
  161. package/dist/core.d.mts +0 -110
  162. package/dist/core.mjs +0 -595
  163. package/dist/error.mjs +0 -237
  164. package/dist/index.d.mts +0 -2
  165. package/dist/index.mjs +0 -3
  166. package/dist/keys.mjs +0 -146
  167. package/dist/mutex.d.mts +0 -44
  168. package/dist/mutex.mjs +0 -110
  169. package/dist/pkce.mjs +0 -157
  170. package/dist/provider/apple.d.mts +0 -111
  171. package/dist/provider/apple.mjs +0 -164
  172. package/dist/provider/code.d.mts +0 -228
  173. package/dist/provider/code.mjs +0 -246
  174. package/dist/provider/discord.d.mts +0 -146
  175. package/dist/provider/discord.mjs +0 -156
  176. package/dist/provider/facebook.d.mts +0 -142
  177. package/dist/provider/facebook.mjs +0 -150
  178. package/dist/provider/github.d.mts +0 -140
  179. package/dist/provider/github.mjs +0 -169
  180. package/dist/provider/gitlab.d.mts +0 -106
  181. package/dist/provider/gitlab.mjs +0 -147
  182. package/dist/provider/google.d.mts +0 -112
  183. package/dist/provider/google.mjs +0 -109
  184. package/dist/provider/linkedin.d.mts +0 -132
  185. package/dist/provider/linkedin.mjs +0 -142
  186. package/dist/provider/magiclink.d.mts +0 -89
  187. package/dist/provider/magiclink.mjs +0 -143
  188. package/dist/provider/microsoft.d.mts +0 -178
  189. package/dist/provider/microsoft.mjs +0 -177
  190. package/dist/provider/oauth2.d.mts +0 -176
  191. package/dist/provider/oauth2.mjs +0 -222
  192. package/dist/provider/passkey.d.mts +0 -104
  193. package/dist/provider/passkey.mjs +0 -320
  194. package/dist/provider/password.d.mts +0 -412
  195. package/dist/provider/password.mjs +0 -363
  196. package/dist/provider/provider.d.mts +0 -227
  197. package/dist/provider/provider.mjs +0 -44
  198. package/dist/provider/reddit.d.mts +0 -107
  199. package/dist/provider/reddit.mjs +0 -127
  200. package/dist/provider/slack.d.mts +0 -114
  201. package/dist/provider/slack.mjs +0 -138
  202. package/dist/provider/spotify.d.mts +0 -113
  203. package/dist/provider/spotify.mjs +0 -135
  204. package/dist/provider/totp.d.mts +0 -112
  205. package/dist/provider/totp.mjs +0 -191
  206. package/dist/provider/twitch.d.mts +0 -108
  207. package/dist/provider/twitch.mjs +0 -131
  208. package/dist/provider/vercel.d.mts +0 -177
  209. package/dist/provider/vercel.mjs +0 -230
  210. package/dist/random.mjs +0 -86
  211. package/dist/revocation.d.mts +0 -55
  212. package/dist/revocation.mjs +0 -63
  213. package/dist/router/context.d.mts +0 -21
  214. package/dist/router/context.mjs +0 -193
  215. package/dist/router/cookies.d.mts +0 -8
  216. package/dist/router/cookies.mjs +0 -13
  217. package/dist/router/index.d.mts +0 -21
  218. package/dist/router/index.mjs +0 -107
  219. package/dist/router/matcher.d.mts +0 -15
  220. package/dist/router/matcher.mjs +0 -76
  221. package/dist/router/middleware/cors.d.mts +0 -15
  222. package/dist/router/middleware/cors.mjs +0 -114
  223. package/dist/router/safe-request.d.mts +0 -52
  224. package/dist/router/safe-request.mjs +0 -160
  225. package/dist/router/types.d.mts +0 -67
  226. package/dist/router/types.mjs +0 -1
  227. package/dist/router/variables.d.mts +0 -12
  228. package/dist/router/variables.mjs +0 -20
  229. package/dist/storage/memory.mjs +0 -125
  230. package/dist/storage/storage.d.mts +0 -179
  231. package/dist/storage/storage.mjs +0 -104
  232. package/dist/storage/turso.mjs +0 -117
  233. package/dist/storage/unstorage.mjs +0 -103
  234. package/dist/subject.d.mts +0 -62
  235. package/dist/subject.mjs +0 -36
  236. package/dist/themes/theme.d.mts +0 -209
  237. package/dist/themes/theme.mjs +0 -120
  238. package/dist/toolkit/client.d.mts +0 -169
  239. package/dist/toolkit/client.mjs +0 -209
  240. package/dist/toolkit/index.d.mts +0 -9
  241. package/dist/toolkit/index.mjs +0 -9
  242. package/dist/toolkit/providers/facebook.d.mts +0 -12
  243. package/dist/toolkit/providers/facebook.mjs +0 -16
  244. package/dist/toolkit/providers/github.d.mts +0 -12
  245. package/dist/toolkit/providers/github.mjs +0 -16
  246. package/dist/toolkit/providers/google.d.mts +0 -12
  247. package/dist/toolkit/providers/google.mjs +0 -20
  248. package/dist/toolkit/providers/strategy.d.mts +0 -40
  249. package/dist/toolkit/providers/strategy.mjs +0 -1
  250. package/dist/toolkit/storage.mjs +0 -157
  251. package/dist/toolkit/utils.mjs +0 -30
  252. package/dist/types.d.mts +0 -94
  253. package/dist/types.mjs +0 -1
  254. package/dist/ui/base.d.mts +0 -30
  255. package/dist/ui/base.mjs +0 -407
  256. package/dist/ui/code.d.mts +0 -43
  257. package/dist/ui/code.mjs +0 -173
  258. package/dist/ui/form.d.mts +0 -32
  259. package/dist/ui/form.mjs +0 -49
  260. package/dist/ui/icon.d.mts +0 -58
  261. package/dist/ui/icon.mjs +0 -247
  262. package/dist/ui/magiclink.d.mts +0 -41
  263. package/dist/ui/magiclink.mjs +0 -152
  264. package/dist/ui/passkey.d.mts +0 -27
  265. package/dist/ui/passkey.mjs +0 -323
  266. package/dist/ui/password.d.mts +0 -42
  267. package/dist/ui/password.mjs +0 -402
  268. package/dist/ui/select.d.mts +0 -34
  269. package/dist/ui/select.mjs +0 -98
  270. package/dist/ui/totp.d.mts +0 -34
  271. package/dist/ui/totp.mjs +0 -270
  272. package/dist/util.mjs +0 -128
package/dist/core.mjs DELETED
@@ -1,595 +0,0 @@
1
- import { getRelativeUrl, lazy } from "./util.mjs";
2
- import { defaultAllowCheck } from "./allow.mjs";
3
- import { MissingParameterError, OauthError, UnauthorizedClientError, UnknownStateError } from "./error.mjs";
4
- import { validatePKCE } from "./pkce.mjs";
5
- import { generateSecureToken } from "./random.mjs";
6
- import { Storage } from "./storage/storage.mjs";
7
- import { encryptionKeys, signingKeys } from "./keys.mjs";
8
- import { Revocation } from "./revocation.mjs";
9
- import { Router } from "./router/index.mjs";
10
- import { deleteCookie, getCookie, setCookie } from "./router/cookies.mjs";
11
- import { cors } from "./router/middleware/cors.mjs";
12
- import { setTheme } from "./themes/theme.mjs";
13
- import { Select } from "./ui/select.mjs";
14
- import { CompactEncrypt, SignJWT, compactDecrypt } from "jose";
15
-
16
- //#region src/core.ts
17
- /**
18
- * Core issuer implementation.
19
- */
20
- /**
21
- * Performs an operation with guaranteed minimum execution time.
22
- * Adds random jitter to prevent timing-based attacks even if operation completes quickly.
23
- *
24
- * Used for validating sensitive data where timing differences could leak information
25
- * (e.g., authorization codes, refresh tokens).
26
- *
27
- * @param fn - Async function to execute
28
- * @param minTimeMs - Minimum execution time in milliseconds (default: 100ms)
29
- * @returns Result of the function, guaranteed to take at least minTimeMs
30
- */
31
- const normalizeTimingAsync = async (fn, minTimeMs = 100) => {
32
- const startTime = performance.now();
33
- const result = await fn();
34
- const elapsed = performance.now() - startTime;
35
- const remainingTime = Math.max(0, minTimeMs - elapsed);
36
- if (remainingTime > 0) {
37
- const jitterBuffer = new Uint32Array(1);
38
- crypto.getRandomValues(jitterBuffer);
39
- const totalDelay = remainingTime + (jitterBuffer[0] ?? 0) / 4294967295 * 20;
40
- await new Promise((resolve) => setTimeout(resolve, totalDelay));
41
- }
42
- return result;
43
- };
44
- /**
45
- * Determines if the incoming request is using HTTPS protocol.
46
- * Checks multiple proxy headers to handle load balancers and reverse proxies.
47
- *
48
- * @param ctx - Router context containing request headers and URL
49
- * @returns True if request is HTTPS, false otherwise
50
- *
51
- * @example
52
- * ```ts
53
- * if (isHttpsRequest(ctx)) {
54
- * setCookie(ctx, 'secure-cookie', value, { secure: true })
55
- * }
56
- * ```
57
- */
58
- const isHttpsRequest = (ctx) => {
59
- return ctx.header("x-forwarded-proto") === "https" || ctx.header("x-forwarded-ssl") === "on" || ctx.request.url.startsWith("https://");
60
- };
61
- /**
62
- * Create an Draft Auth server, a Router app that handles OAuth 2.0 flows.
63
- */
64
- const issuer = (input) => {
65
- const error = input.error ?? ((err) => {
66
- return new Response(err.message, {
67
- status: 400,
68
- headers: { "Content-Type": "text/plain" }
69
- });
70
- });
71
- const ttlAccess = input.ttl?.access ?? 3600 * 24 * 30;
72
- const ttlRefresh = input.ttl?.refresh ?? 3600 * 24 * 365;
73
- const ttlRefreshReuse = input.ttl?.reuse ?? 60;
74
- const ttlRefreshRetention = input.ttl?.retention ?? 0;
75
- if (input.theme) setTheme(input.theme);
76
- const storage = input.storage;
77
- const select = lazy(() => input.select ?? Select());
78
- const allSigning = lazy(() => signingKeys(storage));
79
- const allEncryption = lazy(() => encryptionKeys(storage));
80
- const allow = lazy(() => input.allow ?? defaultAllowCheck);
81
- const signingKey = lazy(() => allSigning().then((all) => all[0]));
82
- const encryptionKey = lazy(() => allEncryption().then((all) => all[0]));
83
- /**
84
- * Resolves issuer URL from context.
85
- * Returns the base URL for the OAuth issuer, considering basePath configuration.
86
- */
87
- const issuer$1 = (ctx) => {
88
- const baseUrl = new URL(getRelativeUrl(ctx, "/"));
89
- if (input.basePath) {
90
- baseUrl.pathname = (input.basePath.startsWith("/") ? input.basePath : `/${input.basePath}`).replace(/\/$/, "");
91
- return baseUrl.href;
92
- }
93
- return baseUrl.origin;
94
- };
95
- /**
96
- * Encrypts value for secure cookie storage.
97
- */
98
- const encrypt = async (value) => {
99
- const key = await encryptionKey();
100
- if (!key) throw new Error("Encryption key not available");
101
- return await new CompactEncrypt(new TextEncoder().encode(JSON.stringify(value))).setProtectedHeader({
102
- alg: "RSA-OAEP-512",
103
- enc: "A256GCM"
104
- }).encrypt(key.public);
105
- };
106
- /**
107
- * Decrypts value from secure cookie storage.
108
- */
109
- const decrypt = async (value) => {
110
- const key = await encryptionKey();
111
- if (!key) throw new Error("Encryption key not available");
112
- return JSON.parse(new TextDecoder().decode(await compactDecrypt(value, key.private).then((result) => result.plaintext)));
113
- };
114
- /**
115
- * Resolves unique subject identifier from type and properties.
116
- */
117
- const resolveSubject = async (type, properties) => {
118
- const jsonString = JSON.stringify(properties);
119
- const data = new TextEncoder().encode(jsonString);
120
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
121
- return `${type}:${Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16)}`;
122
- };
123
- /**
124
- * Generates access and refresh tokens for OAuth 2.0.
125
- */
126
- const generateTokens = async (ctx, value, opts) => {
127
- const refreshToken = value.nextToken ?? generateSecureToken();
128
- if (opts?.generateRefreshToken ?? true) {
129
- /**
130
- * Generate and store the next refresh token after the one we are currently returning.
131
- * Reserving these in advance avoids concurrency issues with multiple refreshes.
132
- * Similar treatment should be given to any other values that may have race conditions,
133
- * for example if a jti claim was added to the access token.
134
- */
135
- const refreshPayload = {
136
- type: value.type,
137
- properties: value.properties,
138
- clientID: value.clientID,
139
- subject: value.subject,
140
- ttl: value.ttl,
141
- nextToken: generateSecureToken(),
142
- timeUsed: value.timeUsed
143
- };
144
- const refreshKey = [
145
- "oauth:refresh",
146
- value.subject,
147
- refreshToken
148
- ];
149
- await Storage.set(storage, refreshKey, refreshPayload, value.ttl.refresh);
150
- }
151
- const signingKeyData = await signingKey();
152
- if (!signingKeyData) throw new Error("Signing key not available");
153
- const now = Math.floor(Date.now() / 1e3);
154
- if (!value.clientID.trim()) throw new Error("Invalid audience: client ID cannot be empty");
155
- return {
156
- access: await new SignJWT({
157
- type: value.type,
158
- properties: value.properties,
159
- sub: value.subject,
160
- aud: value.clientID,
161
- iss: issuer$1(ctx),
162
- exp: now + value.ttl.access,
163
- iat: now,
164
- mode: "access"
165
- }).setExpirationTime(Math.floor(now + value.ttl.access)).setProtectedHeader({
166
- alg: signingKeyData.alg,
167
- kid: signingKeyData.id,
168
- typ: "JWT"
169
- }).sign(signingKeyData.private),
170
- refresh: [value.subject, refreshToken].join(":"),
171
- expiresIn: Math.floor(now + value.ttl.access - Date.now() / 1e3)
172
- };
173
- };
174
- /**
175
- * Gets authorization state from context.
176
- */
177
- const getAuthorization = async (ctx) => {
178
- const match = await auth.get(ctx, "authorization") || ctx.get("authorization");
179
- if (!match) throw new UnknownStateError();
180
- return match;
181
- };
182
- /**
183
- * Authentication utilities for providers.
184
- */
185
- const auth = {
186
- async success(ctx, properties, successOpts) {
187
- const authorization = await getAuthorization(ctx);
188
- const currentProvider = ctx.get("provider") || "unknown";
189
- if (!authorization.client_id) throw new Error("client_id is required");
190
- return await input.success({ async subject(type, properties$1, subjectOpts) {
191
- const subject = subjectOpts?.subject ?? await resolveSubject(type, properties$1);
192
- await successOpts?.invalidate?.(await resolveSubject(type, properties$1));
193
- if (authorization.response_type === "token") {
194
- if (!authorization.redirect_uri) throw new Error("redirect_uri is required");
195
- const location$1 = new URL(authorization.redirect_uri);
196
- if (!authorization.client_id) throw new Error("client_id is required");
197
- const tokens = await generateTokens(ctx, {
198
- type,
199
- subject,
200
- properties: properties$1,
201
- clientID: authorization.client_id,
202
- ttl: {
203
- access: subjectOpts?.ttl?.access ?? ttlAccess,
204
- refresh: subjectOpts?.ttl?.refresh ?? ttlRefresh
205
- }
206
- });
207
- location$1.hash = new URLSearchParams({
208
- access_token: tokens.access,
209
- token_type: "Bearer",
210
- expires_in: tokens.expiresIn.toString(),
211
- ...authorization.state && { state: authorization.state }
212
- }).toString();
213
- await auth.unset(ctx, "authorization");
214
- return ctx.redirect(location$1.toString(), 302);
215
- }
216
- if (!authorization.redirect_uri) throw new Error("redirect_uri is required");
217
- if (!authorization.client_id) throw new Error("client_id is required");
218
- const code = generateSecureToken();
219
- const codePayload = {
220
- type,
221
- properties: properties$1,
222
- subject,
223
- redirectURI: authorization.redirect_uri,
224
- clientID: authorization.client_id,
225
- scopes: authorization.scopes,
226
- pkce: authorization.pkce,
227
- ttl: {
228
- access: subjectOpts?.ttl?.access ?? ttlAccess,
229
- refresh: subjectOpts?.ttl?.refresh ?? ttlRefresh
230
- }
231
- };
232
- await Storage.set(storage, ["oauth:code", code], codePayload, 60);
233
- if (!authorization.redirect_uri) throw new Error("redirect_uri is required");
234
- const location = new URL(authorization.redirect_uri);
235
- location.searchParams.set("code", code);
236
- if (authorization.state) location.searchParams.set("state", authorization.state);
237
- await auth.unset(ctx, "authorization");
238
- return ctx.redirect(location.toString(), 302);
239
- } }, {
240
- provider: currentProvider,
241
- ...properties && typeof properties === "object" ? properties : {}
242
- }, ctx.request, authorization.client_id);
243
- },
244
- forward(ctx, response) {
245
- return ctx.newResponse(response.body ?? void 0, {
246
- status: response.status,
247
- statusText: response.statusText,
248
- headers: response.headers
249
- });
250
- },
251
- async set(ctx, key, maxAge, value) {
252
- const isHttps = isHttpsRequest(ctx);
253
- setCookie(ctx, key, await encrypt(value), {
254
- maxAge,
255
- httpOnly: true,
256
- secure: isHttps,
257
- sameSite: isHttps ? "None" : "Lax",
258
- path: input.basePath || "/"
259
- });
260
- },
261
- async get(ctx, key) {
262
- const raw = getCookie(ctx, key);
263
- if (!raw) return;
264
- try {
265
- return await decrypt(raw);
266
- } catch {
267
- deleteCookie(ctx, key, { path: input.basePath || "/" });
268
- return;
269
- }
270
- },
271
- async unset(ctx, key) {
272
- deleteCookie(ctx, key, { path: input.basePath || "/" });
273
- },
274
- async invalidate(subject) {
275
- for await (const [key] of Storage.scan(storage, ["oauth:refresh", subject])) await Storage.remove(storage, key);
276
- },
277
- storage
278
- };
279
- const app = new Router({ basePath: input.basePath });
280
- for (const [name, value] of Object.entries(input.providers)) {
281
- const route = new Router();
282
- route.use(async (c, next) => {
283
- c.set("provider", name);
284
- return await next();
285
- });
286
- value.init(route, {
287
- name,
288
- ...auth
289
- });
290
- app.mount(`/${name}`, route);
291
- }
292
- app.get("/.well-known/jwks.json", {
293
- middleware: [cors({
294
- origin: "*",
295
- allowHeaders: ["*"],
296
- allowMethods: ["GET"],
297
- credentials: false
298
- })],
299
- handler: async (c) => {
300
- const jwksDocument = { keys: (await allSigning()).map((keyInfo) => ({
301
- ...keyInfo.jwk,
302
- alg: keyInfo.alg,
303
- exp: keyInfo.expired ? Math.floor(keyInfo.expired.getTime() / 1e3) : void 0
304
- })) };
305
- return c.json(jwksDocument);
306
- }
307
- });
308
- app.get("/.well-known/oauth-authorization-server", {
309
- middleware: [cors({
310
- origin: "*",
311
- allowHeaders: ["*"],
312
- allowMethods: ["GET"],
313
- credentials: false
314
- })],
315
- handler: (c) => {
316
- const iss = issuer$1(c);
317
- const oauth2Document = {
318
- issuer: iss,
319
- authorization_endpoint: `${iss}/authorize`,
320
- token_endpoint: `${iss}/token`,
321
- jwks_uri: `${iss}/.well-known/jwks.json`,
322
- response_types_supported: ["code", "token"]
323
- };
324
- return c.json(oauth2Document);
325
- }
326
- });
327
- app.post("/token", {
328
- middleware: [cors({
329
- origin: "*",
330
- allowHeaders: ["*"],
331
- allowMethods: ["POST"],
332
- credentials: false
333
- })],
334
- handler: async (c) => {
335
- const form = await c.formData();
336
- const grantType = form.get("grant_type");
337
- if (grantType === "authorization_code") {
338
- const code = form.get("code");
339
- if (!code) {
340
- const error$1 = new OauthError("invalid_request", "Missing code");
341
- return c.json(error$1.toJSON(), { status: 400 });
342
- }
343
- const key = ["oauth:code", code.toString()];
344
- const { isValid, payload } = await normalizeTimingAsync(async () => {
345
- const data = await Storage.get(storage, key);
346
- const redirectUri = form.get("redirect_uri");
347
- const clientId = form.get("client_id");
348
- const valid = !!(data && data.redirectURI === redirectUri && data.clientID === clientId);
349
- return {
350
- isValid: valid,
351
- payload: valid ? data : void 0
352
- };
353
- });
354
- if (!isValid || !payload) {
355
- const error$1 = new OauthError("invalid_grant", "Authorization code has been used or expired");
356
- return c.json(error$1.toJSON(), { status: 400 });
357
- }
358
- if (payload.pkce) {
359
- const codeVerifier = form.get("code_verifier")?.toString();
360
- if (!codeVerifier) {
361
- const error$1 = new OauthError("invalid_grant", "Missing code_verifier");
362
- return c.json(error$1.toJSON(), { status: 400 });
363
- }
364
- if (!await validatePKCE(codeVerifier, payload.pkce.challenge, payload.pkce.method)) {
365
- const error$1 = new OauthError("invalid_grant", "Code verifier does not match");
366
- return c.json(error$1.toJSON(), { status: 400 });
367
- }
368
- }
369
- const tokens = await generateTokens(c, payload);
370
- await Storage.remove(storage, key);
371
- const response = {
372
- access_token: tokens.access,
373
- token_type: "Bearer",
374
- expires_in: tokens.expiresIn,
375
- refresh_token: tokens.refresh
376
- };
377
- return c.json(response);
378
- }
379
- if (grantType === "refresh_token") {
380
- const refreshToken = form.get("refresh_token");
381
- if (!refreshToken) {
382
- const error$1 = new OauthError("invalid_request", "Missing refresh_token");
383
- return c.json(error$1.toJSON(), { status: 400 });
384
- }
385
- const refreshTokenStr = refreshToken.toString();
386
- if (await Revocation.isRevoked(storage, refreshTokenStr)) {
387
- const error$1 = new OauthError("invalid_grant", "Refresh token has been revoked");
388
- return c.json(error$1.toJSON(), { status: 400 });
389
- }
390
- const splits = refreshTokenStr.split(":");
391
- const token = splits.pop();
392
- if (!token) throw new Error("Invalid refresh token format");
393
- const subject = splits.join(":");
394
- const key = [
395
- "oauth:refresh",
396
- subject,
397
- token
398
- ];
399
- const payload = await Storage.get(storage, key);
400
- if (!payload) {
401
- const error$1 = new OauthError("invalid_grant", "Refresh token has been used or expired");
402
- return c.json(error$1.toJSON(), { status: 400 });
403
- }
404
- if (input.refresh) try {
405
- const refreshResult = await input.refresh({
406
- type: payload.type,
407
- properties: payload.properties,
408
- subject: payload.subject,
409
- clientID: payload.clientID,
410
- scopes: payload.scopes
411
- }, c.request);
412
- if (!refreshResult) {
413
- await auth.invalidate(subject);
414
- return c.json({
415
- error: "invalid_grant",
416
- error_description: "Refresh token is no longer valid"
417
- }, { status: 400 });
418
- }
419
- payload.type = refreshResult.type;
420
- payload.properties = refreshResult.properties;
421
- if (refreshResult.subject) payload.subject = refreshResult.subject;
422
- if (refreshResult.scopes) payload.scopes = refreshResult.scopes;
423
- } catch {
424
- return c.json({
425
- error: "server_error",
426
- error_description: "Internal server error during token refresh"
427
- }, { status: 500 });
428
- }
429
- const generateRefreshToken = !payload.timeUsed;
430
- if (ttlRefreshReuse <= 0) await Storage.remove(storage, key);
431
- else if (!payload.timeUsed) {
432
- payload.timeUsed = Date.now();
433
- await Storage.set(storage, key, payload, ttlRefreshReuse + ttlRefreshRetention);
434
- } else if (Date.now() > payload.timeUsed + ttlRefreshReuse * 1e3) {
435
- await auth.invalidate(subject);
436
- return c.json({
437
- error: "invalid_grant",
438
- error_description: "Refresh token has been used or expired"
439
- }, { status: 400 });
440
- }
441
- const tokens = await generateTokens(c, {
442
- type: payload.type,
443
- properties: payload.properties,
444
- subject: payload.subject,
445
- clientID: payload.clientID,
446
- ttl: {
447
- access: ttlAccess,
448
- refresh: ttlRefresh
449
- }
450
- }, { generateRefreshToken });
451
- const response = {
452
- access_token: tokens.access,
453
- token_type: "Bearer",
454
- refresh_token: tokens.refresh,
455
- expires_in: tokens.expiresIn
456
- };
457
- return c.json(response);
458
- }
459
- return c.json({
460
- error: "unsupported_grant_type",
461
- error_description: "The authorization grant type is not supported by the authorization server"
462
- }, { status: 400 });
463
- }
464
- });
465
- app.post("/revoke", {
466
- middleware: [cors({
467
- origin: "*",
468
- allowHeaders: ["Content-Type"],
469
- allowMethods: ["POST"],
470
- credentials: false
471
- })],
472
- handler: async (c) => {
473
- const form = await c.formData();
474
- const token = form.get("token")?.toString();
475
- const tokenTypeHint = form.get("token_type_hint")?.toString();
476
- if (!token) {
477
- const error$1 = new OauthError("invalid_request", "Missing token parameter");
478
- return c.json(error$1.toJSON(), { status: 400 });
479
- }
480
- try {
481
- if (tokenTypeHint === "refresh_token") {
482
- const splits$1 = token.split(":");
483
- const tokenPart$1 = splits$1.pop();
484
- if (tokenPart$1 && splits$1.length > 0) {
485
- const key = [
486
- "oauth:refresh",
487
- splits$1.join(":"),
488
- tokenPart$1
489
- ];
490
- if (await Storage.get(storage, key)) {
491
- await Storage.remove(storage, key);
492
- const expiresAt$1 = Date.now() + ttlRefreshRetention * 1e3;
493
- await Revocation.revoke(storage, token, expiresAt$1);
494
- return c.json({});
495
- }
496
- }
497
- } else if (tokenTypeHint === "access_token") {
498
- const expiresAt$1 = Date.now() + ttlAccess * 1e3;
499
- await Revocation.revoke(storage, token, expiresAt$1);
500
- return c.json({});
501
- }
502
- const splits = token.split(":");
503
- const tokenPart = splits.pop();
504
- if (tokenPart && splits.length > 0) {
505
- const key = [
506
- "oauth:refresh",
507
- splits.join(":"),
508
- tokenPart
509
- ];
510
- if (await Storage.get(storage, key)) {
511
- await Storage.remove(storage, key);
512
- const expiresAt$1 = Date.now() + ttlRefreshRetention * 1e3;
513
- await Revocation.revoke(storage, token, expiresAt$1);
514
- return c.json({});
515
- }
516
- }
517
- const expiresAt = Date.now() + ttlAccess * 1e3;
518
- await Revocation.revoke(storage, token, expiresAt);
519
- return c.json({});
520
- } catch (_err) {
521
- return c.json({
522
- error: "server_error",
523
- error_description: "Token revocation failed"
524
- }, { status: 500 });
525
- }
526
- }
527
- });
528
- app.get("/authorize", async (c) => {
529
- const provider = c.query("provider");
530
- const response_type = c.query("response_type");
531
- const redirect_uri = c.query("redirect_uri");
532
- const state = c.query("state");
533
- const client_id = c.query("client_id");
534
- const audience = c.query("audience");
535
- const code_challenge = c.query("code_challenge");
536
- const code_challenge_method = c.query("code_challenge_method");
537
- const scope = c.query("scope");
538
- const authorization = {
539
- response_type,
540
- redirect_uri,
541
- state,
542
- client_id,
543
- audience,
544
- scope,
545
- scopes: scope ? scope.split(" ").filter(Boolean) : void 0,
546
- ...code_challenge && code_challenge_method && { pkce: {
547
- challenge: code_challenge,
548
- method: code_challenge_method
549
- } }
550
- };
551
- c.set("authorization", authorization);
552
- if (!redirect_uri) return c.text("Missing redirect_uri", { status: 400 });
553
- try {
554
- const uri = new URL(redirect_uri);
555
- if (!uri.protocol || !uri.host) return c.text("Invalid redirect_uri format", { status: 400 });
556
- } catch {
557
- return c.text("Invalid redirect_uri format", { status: 400 });
558
- }
559
- if (!response_type) throw new MissingParameterError("response_type");
560
- if (!client_id) throw new MissingParameterError("client_id");
561
- if (input.start) await input.start(c.request);
562
- if (!await allow()({
563
- clientID: client_id,
564
- redirectURI: redirect_uri,
565
- audience
566
- }, c.request)) throw new UnauthorizedClientError(client_id, redirect_uri);
567
- await auth.set(c, "authorization", 900, authorization);
568
- if (provider) return c.redirect(`${provider}/authorize`);
569
- const availableProviders = Object.keys(input.providers);
570
- if (availableProviders.length === 1) return c.redirect(`${availableProviders[0]}/authorize`);
571
- return auth.forward(c, await select()(Object.fromEntries(Object.entries(input.providers).map(([key, value]) => [key, value.type])), c.request));
572
- });
573
- app.onError(async (err, c) => {
574
- if (err instanceof UnknownStateError) return auth.forward(c, await error(err, c.request));
575
- try {
576
- const authorization = await getAuthorization(c);
577
- if (!authorization.redirect_uri) throw new Error("redirect_uri is required");
578
- const url = new URL(authorization.redirect_uri);
579
- const oauth = err instanceof OauthError ? err : new OauthError("server_error", err.message);
580
- url.searchParams.set("error", oauth.error);
581
- url.searchParams.set("error_description", oauth.description);
582
- if (authorization.state) url.searchParams.set("state", authorization.state);
583
- return c.redirect(url.toString());
584
- } catch {
585
- return c.json({
586
- error: "server_error",
587
- error_description: err.message
588
- }, { status: 500 });
589
- }
590
- });
591
- return app;
592
- };
593
-
594
- //#endregion
595
- export { issuer };