@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
@@ -0,0 +1,126 @@
1
+ // src/keys.ts
2
+ import {
3
+ exportJWK,
4
+ exportPKCS8,
5
+ exportSPKI,
6
+ generateKeyPair,
7
+ importPKCS8,
8
+ importSPKI
9
+ } from "jose";
10
+ import { Mutex } from "./mutex";
11
+ import { generateSecureToken } from "./random";
12
+ import { Storage } from "./storage/storage";
13
+ var signingAlg = "ES256";
14
+ var encryptionAlg = "RSA-OAEP-512";
15
+ var signingKeyMutex = new Mutex;
16
+ var encryptionKeyMutex = new Mutex;
17
+ var signingKeys = async (storage) => {
18
+ return signingKeyMutex.runExclusive(async () => {
19
+ const results = [];
20
+ const scanner = Storage.scan(storage, ["signing:key"]);
21
+ for await (const [, value] of scanner) {
22
+ try {
23
+ const publicKey = await importSPKI(value.publicKey, value.alg, {
24
+ extractable: true
25
+ });
26
+ const privateKey = await importPKCS8(value.privateKey, value.alg);
27
+ const jwk2 = await exportJWK(publicKey);
28
+ jwk2.kid = value.id;
29
+ jwk2.use = "sig";
30
+ results.push({
31
+ id: value.id,
32
+ alg: signingAlg,
33
+ created: new Date(value.created),
34
+ expired: value.expired ? new Date(value.expired) : undefined,
35
+ public: publicKey,
36
+ private: privateKey,
37
+ jwk: jwk2
38
+ });
39
+ } catch {}
40
+ }
41
+ results.sort((a, b) => b.created.getTime() - a.created.getTime());
42
+ if (results.filter((item) => !item.expired).length) {
43
+ return results;
44
+ }
45
+ const key = await generateKeyPair(signingAlg, {
46
+ extractable: true
47
+ });
48
+ const serialized = {
49
+ id: generateSecureToken(16),
50
+ publicKey: await exportSPKI(key.publicKey),
51
+ privateKey: await exportPKCS8(key.privateKey),
52
+ created: Date.now(),
53
+ alg: signingAlg
54
+ };
55
+ await Storage.set(storage, ["signing:key", serialized.id], serialized);
56
+ const jwk = await exportJWK(key.publicKey);
57
+ jwk.kid = serialized.id;
58
+ jwk.use = "sig";
59
+ const newKeyPair = {
60
+ id: serialized.id,
61
+ alg: signingAlg,
62
+ created: new Date(serialized.created),
63
+ expired: serialized.expired ? new Date(serialized.expired) : undefined,
64
+ public: key.publicKey,
65
+ private: key.privateKey,
66
+ jwk
67
+ };
68
+ return [newKeyPair, ...results];
69
+ });
70
+ };
71
+ var encryptionKeys = async (storage) => {
72
+ return encryptionKeyMutex.runExclusive(async () => {
73
+ const results = [];
74
+ const scanner = Storage.scan(storage, ["encryption:key"]);
75
+ for await (const [, value] of scanner) {
76
+ try {
77
+ const publicKey = await importSPKI(value.publicKey, value.alg, {
78
+ extractable: true
79
+ });
80
+ const privateKey = await importPKCS8(value.privateKey, value.alg);
81
+ const jwk2 = await exportJWK(publicKey);
82
+ jwk2.kid = value.id;
83
+ results.push({
84
+ id: value.id,
85
+ alg: encryptionAlg,
86
+ created: new Date(value.created),
87
+ expired: value.expired ? new Date(value.expired) : undefined,
88
+ public: publicKey,
89
+ private: privateKey,
90
+ jwk: jwk2
91
+ });
92
+ } catch {}
93
+ }
94
+ results.sort((a, b) => b.created.getTime() - a.created.getTime());
95
+ if (results.filter((item) => !item.expired).length) {
96
+ return results;
97
+ }
98
+ const key = await generateKeyPair(encryptionAlg, {
99
+ extractable: true
100
+ });
101
+ const serialized = {
102
+ id: generateSecureToken(16),
103
+ publicKey: await exportSPKI(key.publicKey),
104
+ privateKey: await exportPKCS8(key.privateKey),
105
+ created: Date.now(),
106
+ alg: encryptionAlg
107
+ };
108
+ await Storage.set(storage, ["encryption:key", serialized.id], serialized);
109
+ const jwk = await exportJWK(key.publicKey);
110
+ jwk.kid = serialized.id;
111
+ const newKeyPair = {
112
+ id: serialized.id,
113
+ alg: encryptionAlg,
114
+ created: new Date(serialized.created),
115
+ expired: serialized.expired ? new Date(serialized.expired) : undefined,
116
+ public: key.publicKey,
117
+ private: key.privateKey,
118
+ jwk
119
+ };
120
+ return [newKeyPair, ...results];
121
+ });
122
+ };
123
+ export {
124
+ signingKeys,
125
+ encryptionKeys
126
+ };
@@ -0,0 +1,53 @@
1
+ // src/mutex.ts
2
+ class Semaphore {
3
+ capacity;
4
+ available;
5
+ deferredTasks = [];
6
+ constructor(capacity) {
7
+ this.capacity = capacity;
8
+ this.available = capacity;
9
+ }
10
+ async acquire() {
11
+ if (this.available > 0) {
12
+ this.available--;
13
+ return;
14
+ }
15
+ return new Promise((resolve) => {
16
+ this.deferredTasks.push(resolve);
17
+ });
18
+ }
19
+ release() {
20
+ const deferredTask = this.deferredTasks.shift();
21
+ if (deferredTask != null) {
22
+ deferredTask();
23
+ return;
24
+ }
25
+ if (this.available < this.capacity) {
26
+ this.available++;
27
+ }
28
+ }
29
+ }
30
+
31
+ class Mutex {
32
+ semaphore = new Semaphore(1);
33
+ get isLocked() {
34
+ return this.semaphore.available === 0;
35
+ }
36
+ async acquire() {
37
+ return this.semaphore.acquire();
38
+ }
39
+ release() {
40
+ this.semaphore.release();
41
+ }
42
+ async runExclusive(fn) {
43
+ await this.acquire();
44
+ try {
45
+ return await fn();
46
+ } finally {
47
+ this.release();
48
+ }
49
+ }
50
+ }
51
+ export {
52
+ Mutex
53
+ };
@@ -0,0 +1,87 @@
1
+ // src/pkce.ts
2
+ import { base64url } from "jose";
3
+ var timingSafeCompare = (a, b) => {
4
+ if (typeof a !== "string" || typeof b !== "string") {
5
+ return false;
6
+ }
7
+ const encoder = new TextEncoder;
8
+ const aBytes = encoder.encode(a);
9
+ const bBytes = encoder.encode(b);
10
+ let diff = aBytes.length ^ bBytes.length;
11
+ for (const [i, aByte] of aBytes.entries()) {
12
+ diff |= aByte ^ (bBytes[i] ?? 0);
13
+ }
14
+ return diff === 0;
15
+ };
16
+ var generateVerifier = (length) => {
17
+ const buffer = new Uint8Array(length);
18
+ crypto.getRandomValues(buffer);
19
+ return base64url.encode(buffer);
20
+ };
21
+ var generateChallenge = async (verifier, method) => {
22
+ if (method === "plain") {
23
+ return verifier;
24
+ }
25
+ const encoder = new TextEncoder;
26
+ const data = encoder.encode(verifier);
27
+ const hash = await crypto.subtle.digest("SHA-256", data);
28
+ return base64url.encode(new Uint8Array(hash));
29
+ };
30
+ var generatePKCE = async (length = 48) => {
31
+ if (!Number.isInteger(length) || length < 32 || length > 96) {
32
+ throw new RangeError("Random buffer length must be between 32 and 96 bytes (generates 43-128 character verifier)");
33
+ }
34
+ const verifier = generateVerifier(length);
35
+ if (verifier.length < 43 || verifier.length > 128) {
36
+ throw new Error("Generated verifier does not meet requirements");
37
+ }
38
+ if (!/^[A-Za-z0-9_-]+$/.test(verifier)) {
39
+ throw new Error("Generated verifier is not valid base64url format");
40
+ }
41
+ const challenge = await generateChallenge(verifier, "S256");
42
+ return {
43
+ verifier,
44
+ challenge,
45
+ method: "S256"
46
+ };
47
+ };
48
+ var validatePKCE = async (verifier, challenge, method = "S256") => {
49
+ const MIN_PROCESSING_TIME = 50;
50
+ const RANDOM_JITTER_MAX = 20;
51
+ const startTime = performance.now();
52
+ let isValid = false;
53
+ let hasEarlyFailure = false;
54
+ const normalizedVerifier = String(verifier || "");
55
+ const normalizedChallenge = String(challenge || "");
56
+ const validations = [
57
+ typeof verifier === "string" && typeof challenge === "string" && verifier && challenge,
58
+ normalizedVerifier.length >= 43 && normalizedVerifier.length <= 128,
59
+ normalizedChallenge.length >= 43 && normalizedChallenge.length <= 128,
60
+ /^[A-Za-z0-9_-]+$/.test(normalizedVerifier),
61
+ /^[A-Za-z0-9_-]+$/.test(normalizedChallenge)
62
+ ];
63
+ hasEarlyFailure = !validations.every(Boolean);
64
+ const verifierToUse = hasEarlyFailure ? "dummyverifier_".repeat(6) : normalizedVerifier;
65
+ try {
66
+ const generatedChallenge = await generateChallenge(verifierToUse, method);
67
+ const challengeToCompare = hasEarlyFailure ? "dummychallenge_".repeat(6) : normalizedChallenge;
68
+ const comparisonResult = timingSafeCompare(generatedChallenge, challengeToCompare);
69
+ isValid = !hasEarlyFailure && comparisonResult;
70
+ } catch {
71
+ isValid = false;
72
+ }
73
+ const elapsed = performance.now() - startTime;
74
+ const remainingTime = Math.max(0, MIN_PROCESSING_TIME - elapsed);
75
+ if (remainingTime > 0 || elapsed < MIN_PROCESSING_TIME) {
76
+ const jitterArray = new Uint32Array(1);
77
+ crypto.getRandomValues(jitterArray);
78
+ const jitter = (jitterArray[0] ?? 0) / 4294967295 * RANDOM_JITTER_MAX;
79
+ const totalDelay = Math.max(remainingTime, MIN_PROCESSING_TIME - elapsed) + jitter;
80
+ await new Promise((resolve) => setTimeout(resolve, totalDelay));
81
+ }
82
+ return isValid;
83
+ };
84
+ export {
85
+ validatePKCE,
86
+ generatePKCE
87
+ };
@@ -0,0 +1,15 @@
1
+ // src/provider/apple.ts
2
+ import { Oauth2Provider } from "./oauth2";
3
+ var AppleProvider = (config) => {
4
+ return Oauth2Provider({
5
+ ...config,
6
+ type: "apple",
7
+ endpoint: {
8
+ authorization: "https://appleid.apple.com/auth/authorize",
9
+ token: "https://appleid.apple.com/auth/token"
10
+ }
11
+ });
12
+ };
13
+ export {
14
+ AppleProvider
15
+ };
@@ -0,0 +1,62 @@
1
+ // src/provider/code.ts
2
+ import { generateUnbiasedDigits, timingSafeCompare } from "../random";
3
+ var CodeProvider = (config) => {
4
+ const codeLength = config.length || 6;
5
+ const generateCode = () => {
6
+ return generateUnbiasedDigits(codeLength);
7
+ };
8
+ return {
9
+ type: "code",
10
+ init(routes, ctx) {
11
+ const transition = async (c, nextState, formData, error) => {
12
+ await ctx.set(c, "provider", 60 * 60 * 24, nextState);
13
+ const response = await config.request(c.req.raw, nextState, formData, error);
14
+ return ctx.forward(c, response);
15
+ };
16
+ routes.get("/authorize", (c) => {
17
+ return transition(c, { type: "start" });
18
+ });
19
+ routes.post("/authorize", async (c) => {
20
+ const formData = await c.req.formData();
21
+ const currentState = await ctx.get(c, "provider");
22
+ const action = formData.get("action")?.toString();
23
+ if (action === "request" || action === "resend") {
24
+ const code = generateCode();
25
+ const formEntries = Object.fromEntries(formData);
26
+ const { action: _, ...claims } = formEntries;
27
+ await config.sendCode(claims, code);
28
+ return transition(c, {
29
+ type: "code",
30
+ resend: action === "resend",
31
+ claims,
32
+ code
33
+ }, formData);
34
+ } else if (action === "verify" && currentState?.type === "code") {
35
+ const enteredCode = formData.get("code")?.toString();
36
+ if (!(currentState.code && enteredCode && timingSafeCompare(currentState.code, enteredCode))) {
37
+ return transition(c, {
38
+ ...currentState,
39
+ resend: false
40
+ }, formData, { type: "invalid_code" });
41
+ }
42
+ const authorization = await ctx.get(c, "authorization");
43
+ if (!authorization) {
44
+ return transition(c, { type: "start" }, formData, {
45
+ type: "invalid_claim",
46
+ key: "session",
47
+ value: "Authentication session expired"
48
+ });
49
+ }
50
+ await ctx.unset(c, "provider");
51
+ return await ctx.success(c, {
52
+ claims: currentState.claims
53
+ });
54
+ }
55
+ return transition(c, { type: "start" });
56
+ });
57
+ }
58
+ };
59
+ };
60
+ export {
61
+ CodeProvider
62
+ };
@@ -0,0 +1,15 @@
1
+ // src/provider/discord.ts
2
+ import { Oauth2Provider } from "./oauth2";
3
+ var DiscordProvider = (config) => {
4
+ return Oauth2Provider({
5
+ ...config,
6
+ type: "discord",
7
+ endpoint: {
8
+ authorization: "https://discord.com/oauth2/authorize",
9
+ token: "https://discord.com/api/oauth2/token"
10
+ }
11
+ });
12
+ };
13
+ export {
14
+ DiscordProvider
15
+ };
@@ -0,0 +1,15 @@
1
+ // src/provider/facebook.ts
2
+ import { Oauth2Provider } from "./oauth2";
3
+ var FacebookProvider = (config) => {
4
+ return Oauth2Provider({
5
+ ...config,
6
+ type: "facebook",
7
+ endpoint: {
8
+ authorization: "https://www.facebook.com/v18.0/dialog/oauth",
9
+ token: "https://graph.facebook.com/v18.0/oauth/access_token"
10
+ }
11
+ });
12
+ };
13
+ export {
14
+ FacebookProvider
15
+ };
@@ -0,0 +1,15 @@
1
+ // src/provider/github.ts
2
+ import { Oauth2Provider } from "./oauth2";
3
+ var GithubProvider = (config) => {
4
+ return Oauth2Provider({
5
+ ...config,
6
+ type: "github",
7
+ endpoint: {
8
+ authorization: "https://github.com/login/oauth/authorize",
9
+ token: "https://github.com/login/oauth/access_token"
10
+ }
11
+ });
12
+ };
13
+ export {
14
+ GithubProvider
15
+ };
@@ -0,0 +1,15 @@
1
+ // src/provider/gitlab.ts
2
+ import { Oauth2Provider } from "./oauth2";
3
+ var GitlabProvider = (config) => {
4
+ return Oauth2Provider({
5
+ ...config,
6
+ type: "gitlab",
7
+ endpoint: {
8
+ authorization: "https://gitlab.com/oauth/authorize",
9
+ token: "https://gitlab.com/oauth/token"
10
+ }
11
+ });
12
+ };
13
+ export {
14
+ GitlabProvider
15
+ };
@@ -0,0 +1,16 @@
1
+ // src/provider/google.ts
2
+ import { Oauth2Provider } from "./oauth2";
3
+ var GoogleProvider = (config) => {
4
+ return Oauth2Provider({
5
+ ...config,
6
+ type: "google",
7
+ endpoint: {
8
+ authorization: "https://accounts.google.com/o/oauth2/v2/auth",
9
+ token: "https://oauth2.googleapis.com/token",
10
+ jwks: "https://www.googleapis.com/oauth2/v3/certs"
11
+ }
12
+ });
13
+ };
14
+ export {
15
+ GoogleProvider
16
+ };
@@ -0,0 +1,15 @@
1
+ // src/provider/linkedin.ts
2
+ import { Oauth2Provider } from "./oauth2";
3
+ var LinkedInProvider = (config) => {
4
+ return Oauth2Provider({
5
+ ...config,
6
+ type: "linkedin",
7
+ endpoint: {
8
+ authorization: "https://www.linkedin.com/oauth/v2/authorization",
9
+ token: "https://www.linkedin.com/oauth/v2/accessToken"
10
+ }
11
+ });
12
+ };
13
+ export {
14
+ LinkedInProvider
15
+ };
@@ -0,0 +1,83 @@
1
+ // src/provider/magiclink.ts
2
+ import { generateUnbiasedDigits, timingSafeCompare } from "../random";
3
+ var MagicLinkProvider = (config) => {
4
+ const generateToken = () => {
5
+ return generateUnbiasedDigits(32);
6
+ };
7
+ return {
8
+ type: "magiclink",
9
+ init(routes, ctx) {
10
+ const transition = async (c, nextState, formData, error) => {
11
+ await ctx.set(c, "provider", 60 * 60 * 24, nextState);
12
+ const response = await config.request(c.req.raw, nextState, formData, error);
13
+ return ctx.forward(c, response);
14
+ };
15
+ routes.get("/authorize", async (c) => {
16
+ return transition(c, { type: "start" });
17
+ });
18
+ routes.post("/authorize", async (c) => {
19
+ const formData = await c.req.formData();
20
+ const action = formData.get("action")?.toString();
21
+ if (action === "request" || action === "resend") {
22
+ const token = generateToken();
23
+ const formEntries = Object.fromEntries(formData);
24
+ const { action: _, ...claims } = formEntries;
25
+ const baseUrl = new URL(c.req.url).origin;
26
+ const magicUrl = new URL(`/auth/${ctx.name}/verify`, baseUrl);
27
+ magicUrl.searchParams.set("token", token);
28
+ for (const [key, value] of Object.entries(claims)) {
29
+ if (typeof value === "string") {
30
+ magicUrl.searchParams.set(key, value);
31
+ }
32
+ }
33
+ await config.sendLink(claims, magicUrl.toString());
34
+ return transition(c, {
35
+ type: "sent",
36
+ resend: action === "resend",
37
+ claims,
38
+ token
39
+ }, formData);
40
+ }
41
+ return transition(c, { type: "start" });
42
+ });
43
+ routes.get("/verify", async (c) => {
44
+ const url = new URL(c.req.url);
45
+ const token = url.searchParams.get("token");
46
+ const storedState = await ctx.get(c, "provider");
47
+ if (!token || !storedState || storedState.type !== "sent") {
48
+ return transition(c, { type: "start" }, undefined, { type: "invalid_link" });
49
+ }
50
+ if (!timingSafeCompare(storedState.token, token)) {
51
+ return transition(c, { type: "start" }, undefined, { type: "invalid_link" });
52
+ }
53
+ const urlClaims = {};
54
+ for (const [key, value] of url.searchParams) {
55
+ if (key !== "token" && value) {
56
+ urlClaims[key] = value;
57
+ }
58
+ }
59
+ const claimsMatch = Object.keys(storedState.claims).every((key) => {
60
+ const urlValue = urlClaims[key];
61
+ const storedValue = storedState.claims[key];
62
+ if (!urlValue || !storedValue)
63
+ return false;
64
+ return timingSafeCompare(storedValue, urlValue);
65
+ });
66
+ if (!claimsMatch) {
67
+ return transition(c, { type: "start" }, undefined, { type: "invalid_link" });
68
+ }
69
+ const authorization = await ctx.get(c, "authorization");
70
+ if (!authorization) {
71
+ return transition(c, { type: "start" }, undefined, { type: "invalid_link" });
72
+ }
73
+ await ctx.unset(c, "provider");
74
+ return await ctx.success(c, {
75
+ claims: storedState.claims
76
+ });
77
+ });
78
+ }
79
+ };
80
+ };
81
+ export {
82
+ MagicLinkProvider
83
+ };
@@ -0,0 +1,15 @@
1
+ // src/provider/microsoft.ts
2
+ import { Oauth2Provider } from "./oauth2";
3
+ var MicrosoftProvider = (config) => {
4
+ return Oauth2Provider({
5
+ ...config,
6
+ type: "microsoft",
7
+ endpoint: {
8
+ authorization: `https://login.microsoftonline.com/${config.tenant}/oauth2/v2.0/authorize`,
9
+ token: `https://login.microsoftonline.com/${config.tenant}/oauth2/v2.0/token`
10
+ }
11
+ });
12
+ };
13
+ export {
14
+ MicrosoftProvider
15
+ };
@@ -0,0 +1,130 @@
1
+ // src/provider/oauth2.ts
2
+ import { createRemoteJWKSet, jwtVerify } from "jose";
3
+ import { OauthError } from "../error";
4
+ import { generatePKCE } from "../pkce";
5
+ import { generateSecureToken, timingSafeCompare } from "../random";
6
+ import { getRelativeUrl } from "../util";
7
+ var Oauth2Provider = (config) => {
8
+ const authQuery = config.query || {};
9
+ const handleCallbackLogic = async (c, ctx, provider, code) => {
10
+ if (!(provider && code)) {
11
+ return c.redirect(getRelativeUrl(c, "./authorize"));
12
+ }
13
+ const tokenRequestBody = new URLSearchParams({
14
+ client_id: config.clientID,
15
+ client_secret: config.clientSecret,
16
+ code,
17
+ grant_type: "authorization_code",
18
+ redirect_uri: provider.redirect,
19
+ ...provider.codeVerifier ? { code_verifier: provider.codeVerifier } : {}
20
+ });
21
+ try {
22
+ const response = await fetch(config.endpoint.token, {
23
+ method: "POST",
24
+ headers: {
25
+ "Content-Type": "application/x-www-form-urlencoded",
26
+ Accept: "application/json"
27
+ },
28
+ body: tokenRequestBody.toString()
29
+ });
30
+ if (!response.ok) {
31
+ throw new Error(`Token request failed with status ${response.status}`);
32
+ }
33
+ const tokenData = await response.json();
34
+ if (tokenData.error) {
35
+ throw new OauthError(tokenData.error, tokenData.error_description || "");
36
+ }
37
+ if (tokenData.id_token && config.endpoint.jwks) {
38
+ try {
39
+ const jwks = createRemoteJWKSet(new URL(config.endpoint.jwks));
40
+ await jwtVerify(tokenData.id_token, jwks, {
41
+ issuer: config.endpoint.authorization.split("/").slice(0, 3).join("/"),
42
+ clockTolerance: 60
43
+ });
44
+ } catch (error) {
45
+ throw new OauthError("invalid_request", `ID token validation failed: ${error instanceof Error ? error.message : "Unknown error"}`);
46
+ }
47
+ }
48
+ return await ctx.success(c, {
49
+ clientID: config.clientID,
50
+ tokenset: {
51
+ get access() {
52
+ return tokenData.access_token;
53
+ },
54
+ get refresh() {
55
+ return tokenData.refresh_token || "";
56
+ },
57
+ get expiry() {
58
+ return tokenData.expires_in || 0;
59
+ },
60
+ get raw() {
61
+ return tokenData;
62
+ }
63
+ }
64
+ });
65
+ } catch (error) {
66
+ if (error instanceof OauthError) {
67
+ throw error;
68
+ }
69
+ throw new OauthError("server_error", `Token exchange failed: ${error instanceof Error ? error.message : "Unknown error"}`);
70
+ }
71
+ };
72
+ return {
73
+ type: config.type || "oauth2",
74
+ init(routes, ctx) {
75
+ routes.get("/authorize", async (c) => {
76
+ const state = generateSecureToken();
77
+ const pkce = config.pkce ? await generatePKCE() : undefined;
78
+ await ctx.set(c, "provider", 60 * 10, {
79
+ state,
80
+ redirect: getRelativeUrl(c, "./callback"),
81
+ codeVerifier: pkce?.verifier
82
+ });
83
+ const authorizationUrl = new URL(config.endpoint.authorization);
84
+ authorizationUrl.searchParams.set("client_id", config.clientID);
85
+ authorizationUrl.searchParams.set("redirect_uri", getRelativeUrl(c, "./callback"));
86
+ authorizationUrl.searchParams.set("response_type", "code");
87
+ authorizationUrl.searchParams.set("state", state);
88
+ authorizationUrl.searchParams.set("scope", config.scopes.join(" "));
89
+ if (pkce) {
90
+ authorizationUrl.searchParams.set("code_challenge", pkce.challenge);
91
+ authorizationUrl.searchParams.set("code_challenge_method", pkce.method);
92
+ }
93
+ for (const [key, value] of Object.entries(authQuery)) {
94
+ authorizationUrl.searchParams.set(key, value);
95
+ }
96
+ return c.redirect(authorizationUrl.toString());
97
+ });
98
+ routes.get("/callback", async (c) => {
99
+ const provider = await ctx.get(c, "provider");
100
+ const code = c.req.query("code");
101
+ const state = c.req.query("state");
102
+ const error = c.req.query("error");
103
+ if (error) {
104
+ throw new OauthError(error, c.req.query("error_description") || "");
105
+ }
106
+ if (!(provider && code) || provider.state && !timingSafeCompare(state || "", provider.state)) {
107
+ return c.redirect(getRelativeUrl(c, "./authorize"));
108
+ }
109
+ return await handleCallbackLogic(c, ctx, provider, code);
110
+ });
111
+ routes.post("/callback", async (c) => {
112
+ const provider = await ctx.get(c, "provider");
113
+ const formData = await c.req.formData();
114
+ const code = formData.get("code")?.toString();
115
+ const state = formData.get("state")?.toString();
116
+ const error = formData.get("error")?.toString();
117
+ if (error) {
118
+ throw new OauthError(error, formData.get("error_description")?.toString() || "");
119
+ }
120
+ if (!(provider && code) || provider.state && !timingSafeCompare(state || "", provider.state)) {
121
+ return c.redirect(getRelativeUrl(c, "./authorize"));
122
+ }
123
+ return await handleCallbackLogic(c, ctx, provider, code);
124
+ });
125
+ }
126
+ };
127
+ };
128
+ export {
129
+ Oauth2Provider
130
+ };