@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
@@ -1,363 +0,0 @@
1
- import { getRelativeUrl } from "../util.mjs";
2
- import { UnknownStateError } from "../error.mjs";
3
- import { generateUnbiasedDigits, timingSafeCompare } from "../random.mjs";
4
- import { Storage } from "../storage/storage.mjs";
5
- import * as jose from "jose";
6
- import { randomBytes, scrypt, timingSafeEqual } from "node:crypto";
7
- import { TextEncoder } from "node:util";
8
-
9
- //#region src/provider/password.ts
10
- /**
11
- * Creates a password authentication provider with email verification.
12
- * Implements secure registration, login, and password change flows.
13
- *
14
- * @param config - Provider configuration including UI handlers and email service
15
- * @returns Provider instance implementing password authentication
16
- *
17
- * @example
18
- * ```ts
19
- * const provider = PasswordProvider({
20
- * login: async (req, form, error) => {
21
- * return new Response(renderLogin(form, error))
22
- * },
23
- * register: async (req, state, form, error) => {
24
- * return new Response(renderRegister(state, form, error))
25
- * },
26
- * change: async (req, state, form, error) => {
27
- * return new Response(renderChange(state, form, error))
28
- * },
29
- * sendCode: async (email, code) => {
30
- * await emailService.send(email, `Code: ${code}`)
31
- * },
32
- * validatePassword: (pwd) => {
33
- * return pwd.length >= 8 ? undefined : "Too short"
34
- * }
35
- * })
36
- * ```
37
- */
38
- const PasswordProvider = (config) => {
39
- const hasher = config.hasher ?? ScryptHasher();
40
- /**
41
- * Generates a cryptographically secure verification code.
42
- */
43
- const generateCode = () => {
44
- return generateUnbiasedDigits(config.length ?? 6);
45
- };
46
- return {
47
- type: "password",
48
- init(routes, ctx) {
49
- /**
50
- * GET /authorize - Display login form
51
- */
52
- routes.get("/authorize", async (c) => ctx.forward(c, await config.login(c.request)));
53
- /**
54
- * POST /authorize - Process login attempt
55
- */
56
- routes.post("/authorize", async (c) => {
57
- const formData = await c.formData();
58
- const error = async (err) => {
59
- return ctx.forward(c, await config.login(c.request, formData, err));
60
- };
61
- const email = formData.get("email")?.toString()?.toLowerCase();
62
- if (!email) return error({ type: "invalid_email" });
63
- const storedHash = await Storage.get(ctx.storage, [
64
- "email",
65
- email,
66
- "password"
67
- ]);
68
- const password = formData.get("password")?.toString();
69
- if (!(password && storedHash && await hasher.verify(password, storedHash))) return error({ type: "invalid_password" });
70
- return ctx.success(c, { email }, { invalidate: async (subject) => {
71
- await Storage.set(ctx.storage, [
72
- "email",
73
- email,
74
- "subject"
75
- ], subject);
76
- } });
77
- });
78
- /**
79
- * GET /register - Display registration form
80
- */
81
- routes.get("/register", async (c) => {
82
- const state = { type: "start" };
83
- await ctx.set(c, "provider", 3600 * 24, state);
84
- return ctx.forward(c, await config.register(c.request, state));
85
- });
86
- /**
87
- * POST /register - Process registration steps
88
- */
89
- routes.post("/register", async (c) => {
90
- const formData = await c.formData();
91
- const email = formData.get("email")?.toString()?.toLowerCase();
92
- const action = formData.get("action")?.toString();
93
- let provider = await ctx.get(c, "provider");
94
- if (!provider) {
95
- const state = { type: "start" };
96
- await ctx.set(c, "provider", 3600 * 24, state);
97
- if (action === "register") provider = state;
98
- else return ctx.forward(c, await config.register(c.request, state));
99
- }
100
- const transition = async (next, err) => {
101
- await ctx.set(c, "provider", 3600 * 24, next);
102
- return ctx.forward(c, await config.register(c.request, next, formData, err));
103
- };
104
- if (action === "register" && provider.type === "start") {
105
- const password = formData.get("password")?.toString();
106
- const repeat = formData.get("repeat")?.toString();
107
- if (!email) return transition(provider, { type: "invalid_email" });
108
- if (!password) return transition(provider, { type: "invalid_password" });
109
- if (password !== repeat) return transition(provider, { type: "password_mismatch" });
110
- if (config.validatePassword) {
111
- let validationError;
112
- try {
113
- if (typeof config.validatePassword === "function") validationError = await config.validatePassword(password);
114
- else {
115
- const result = await config.validatePassword["~standard"].validate(password);
116
- if (result.issues?.length) throw new Error(result.issues.map((issue) => issue.message).join(", "));
117
- }
118
- } catch (error) {
119
- validationError = error instanceof Error ? error.message : void 0;
120
- }
121
- if (validationError) return transition(provider, {
122
- type: "validation_error",
123
- message: validationError
124
- });
125
- }
126
- if (await Storage.get(ctx.storage, [
127
- "email",
128
- email,
129
- "password"
130
- ])) return transition(provider, { type: "email_taken" });
131
- const code = generateCode();
132
- await config.sendCode(email, code, "register");
133
- return transition({
134
- type: "code",
135
- code,
136
- password: await hasher.hash(password),
137
- email
138
- });
139
- }
140
- if (action === "register" && provider.type === "code") {
141
- const code = generateCode();
142
- await config.sendCode(provider.email, code, "register:resend");
143
- return transition({
144
- type: "code",
145
- code,
146
- password: provider.password,
147
- email: provider.email
148
- });
149
- }
150
- if (action === "verify" && provider.type === "code") {
151
- const code = formData.get("code")?.toString();
152
- if (!(code && timingSafeCompare(code, provider.code))) return transition(provider, { type: "invalid_code" });
153
- if (await Storage.get(ctx.storage, [
154
- "email",
155
- provider.email,
156
- "password"
157
- ])) return transition({ type: "start" }, { type: "email_taken" });
158
- await Storage.set(ctx.storage, [
159
- "email",
160
- provider.email,
161
- "password"
162
- ], provider.password);
163
- return ctx.success(c, { email: provider.email });
164
- }
165
- return transition({ type: "start" });
166
- });
167
- /**
168
- * GET /change - Display password change form
169
- */
170
- routes.get("/change", async (c) => {
171
- const state = {
172
- type: "start",
173
- redirect: c.query("redirect_uri") || getRelativeUrl(c, "./authorize")
174
- };
175
- await ctx.set(c, "provider", 3600 * 24, state);
176
- return ctx.forward(c, await config.change(c.request, state));
177
- });
178
- /**
179
- * POST /change - Process password change steps
180
- */
181
- routes.post("/change", async (c) => {
182
- const formData = await c.formData();
183
- const action = formData.get("action")?.toString();
184
- const provider = await ctx.get(c, "provider");
185
- if (!provider) throw new UnknownStateError();
186
- const transition = async (next, err) => {
187
- await ctx.set(c, "provider", 3600 * 24, next);
188
- return ctx.forward(c, await config.change(c.request, next, formData, err));
189
- };
190
- if (action === "code") {
191
- const email = formData.get("email")?.toString()?.toLowerCase();
192
- if (!email) return transition({
193
- type: "start",
194
- redirect: provider.redirect
195
- }, { type: "invalid_email" });
196
- if (!await Storage.get(ctx.storage, [
197
- "email",
198
- email,
199
- "password"
200
- ])) return transition({
201
- type: "start",
202
- redirect: provider.redirect
203
- }, { type: "invalid_email" });
204
- const code = generateCode();
205
- const context = provider.type === "code" && provider.email === email ? "reset:resend" : "reset";
206
- await config.sendCode(email, code, context);
207
- return transition({
208
- type: "code",
209
- code,
210
- email,
211
- redirect: provider.redirect
212
- });
213
- }
214
- if (action === "verify" && provider.type === "code") {
215
- const code = formData.get("code")?.toString();
216
- if (!(code && timingSafeCompare(code, provider.code))) return transition(provider, { type: "invalid_code" });
217
- return transition({
218
- type: "update",
219
- email: provider.email,
220
- redirect: provider.redirect
221
- });
222
- }
223
- if (action === "update" && provider.type === "update") {
224
- if (!await Storage.get(ctx.storage, [
225
- "email",
226
- provider.email,
227
- "password"
228
- ])) return c.redirect(provider.redirect, 302);
229
- const password = formData.get("password")?.toString();
230
- const repeat = formData.get("repeat")?.toString();
231
- if (!password) return transition(provider, { type: "invalid_password" });
232
- if (!repeat) return transition(provider, { type: "invalid_password" });
233
- if (password !== repeat) return transition(provider, { type: "password_mismatch" });
234
- if (config.validatePassword) {
235
- let validationError;
236
- try {
237
- if (typeof config.validatePassword === "function") validationError = await config.validatePassword(password);
238
- else {
239
- const result = await config.validatePassword["~standard"].validate(password);
240
- if (result.issues?.length) throw new Error(result.issues.map((issue) => issue.message).join(", "));
241
- }
242
- } catch (error) {
243
- validationError = error instanceof Error ? error.message : void 0;
244
- }
245
- if (validationError) return transition(provider, {
246
- type: "validation_error",
247
- message: validationError
248
- });
249
- }
250
- await Storage.set(ctx.storage, [
251
- "email",
252
- provider.email,
253
- "password"
254
- ], await hasher.hash(password));
255
- const subject = await Storage.get(ctx.storage, [
256
- "email",
257
- provider.email,
258
- "subject"
259
- ]);
260
- if (subject) await ctx.invalidate(subject);
261
- return c.redirect(provider.redirect, 302);
262
- }
263
- return transition({
264
- type: "start",
265
- redirect: provider.redirect
266
- });
267
- });
268
- }
269
- };
270
- };
271
- /**
272
- * PBKDF2 password hasher with configurable iterations.
273
- * Good choice for compatibility but slower than Scrypt.
274
- *
275
- * @param opts - Configuration options
276
- * @returns Password hasher using PBKDF2 algorithm
277
- * @internal
278
- */
279
- const PBKDF2Hasher = (opts) => {
280
- const iterations = opts?.iterations ?? 6e5;
281
- return {
282
- async hash(password) {
283
- const passwordBytes = new TextEncoder().encode(password);
284
- const salt = crypto.getRandomValues(new Uint8Array(16));
285
- const keyMaterial = await crypto.subtle.importKey("raw", passwordBytes, "PBKDF2", false, ["deriveBits"]);
286
- const hashBuffer = await crypto.subtle.deriveBits({
287
- name: "PBKDF2",
288
- hash: "SHA-256",
289
- salt,
290
- iterations
291
- }, keyMaterial, 256);
292
- return {
293
- hash: jose.base64url.encode(new Uint8Array(hashBuffer)),
294
- salt: jose.base64url.encode(salt),
295
- iterations
296
- };
297
- },
298
- async verify(password, compare) {
299
- const passwordBytes = new TextEncoder().encode(password);
300
- const salt = jose.base64url.decode(compare.salt);
301
- const keyMaterial = await crypto.subtle.importKey("raw", passwordBytes, "PBKDF2", false, ["deriveBits"]);
302
- const hashBuffer = await crypto.subtle.deriveBits({
303
- name: "PBKDF2",
304
- hash: "SHA-256",
305
- salt,
306
- iterations: compare.iterations
307
- }, keyMaterial, 256);
308
- return timingSafeCompare(jose.base64url.encode(new Uint8Array(hashBuffer)), compare.hash);
309
- }
310
- };
311
- };
312
- /**
313
- * Scrypt password hasher with secure defaults.
314
- * Recommended choice for new applications due to memory-hard properties.
315
- *
316
- * @param opts - Scrypt parameters (N, r, p)
317
- * @returns Password hasher using Scrypt algorithm
318
- * @internal
319
- */
320
- const ScryptHasher = (opts) => {
321
- const N = opts?.N ?? 16384;
322
- const r = opts?.r ?? 8;
323
- const p = opts?.p ?? 1;
324
- return {
325
- async hash(password) {
326
- const salt = randomBytes(16);
327
- const keyLength = 32;
328
- return {
329
- hash: (await new Promise((resolve, reject) => {
330
- scrypt(password, salt, keyLength, {
331
- N,
332
- r,
333
- p
334
- }, (err, derivedKey) => {
335
- if (err) reject(err);
336
- else resolve(derivedKey);
337
- });
338
- })).toString("base64"),
339
- salt: salt.toString("base64"),
340
- N,
341
- r,
342
- p
343
- };
344
- },
345
- async verify(password, compare) {
346
- const salt = Buffer.from(compare.salt, "base64");
347
- const keyLength = 32;
348
- return timingSafeEqual(await new Promise((resolve, reject) => {
349
- scrypt(password, salt, keyLength, {
350
- N: compare.N,
351
- r: compare.r,
352
- p: compare.p
353
- }, (err, derivedKey) => {
354
- if (err) reject(err);
355
- else resolve(derivedKey);
356
- });
357
- }), Buffer.from(compare.hash, "base64"));
358
- }
359
- };
360
- };
361
-
362
- //#endregion
363
- export { PBKDF2Hasher, PasswordProvider, ScryptHasher };
@@ -1,227 +0,0 @@
1
- import { RouterContext } from "../router/types.mjs";
2
- import { Router } from "../router/index.mjs";
3
- import { StorageAdapter } from "../storage/storage.mjs";
4
-
5
- //#region src/provider/provider.d.ts
6
-
7
- /**
8
- * OAuth provider system for Draft Auth.
9
- * Defines the interfaces and utilities for implementing authentication providers
10
- * that integrate with various OAuth 2.0 services.
11
- *
12
- * ## Creating a Provider
13
- *
14
- * ```ts
15
- * export const MyProvider = (config: MyConfig): Provider<MyUserData> => ({
16
- * type: "my-provider",
17
- *
18
- * init(routes, ctx) {
19
- * routes.get("/authorize", async (c) => {
20
- * // Redirect to provider's auth URL
21
- * return c.redirect(authUrl)
22
- * })
23
- *
24
- * routes.get("/callback", async (c) => {
25
- * // Handle callback and extract user data
26
- * const userData = await processCallback(c)
27
- * return await ctx.success(c, userData)
28
- * })
29
- * }
30
- * })
31
- * ```
32
- *
33
- * ## Using Providers
34
- *
35
- * ```ts
36
- * export default issuer({
37
- * providers: {
38
- * github: GithubProvider({ ... }),
39
- * google: GoogleProvider({ ... })
40
- * }
41
- * })
42
- * ```
43
- */
44
- /**
45
- * Router instance used for provider route definitions.
46
- * Providers use this to register their authorization and callback endpoints.
47
- */
48
- type ProviderRoute = Router;
49
- /**
50
- * Authentication provider interface that handles OAuth flows.
51
- * Each provider implements authentication with a specific service (GitHub, Google, etc.).
52
- *
53
- * @template Properties - Type of user data returned by successful authentication
54
- */
55
- interface Provider<Properties = Record<string, unknown>> {
56
- /**
57
- * Unique identifier for this provider type.
58
- * Used in URLs and provider selection UI.
59
- *
60
- * @example "github", "google", "steam"
61
- */
62
- readonly type: string;
63
- /**
64
- * Initializes the provider by registering required routes.
65
- * Called during issuer setup to configure authorization and callback endpoints.
66
- *
67
- * @param route - Router instance for registering provider endpoints
68
- * @param options - Provider utilities and configuration
69
- *
70
- * @example
71
- * ```ts
72
- * init(routes, ctx) {
73
- * routes.get("/authorize", async (c) => {
74
- * // Redirect to OAuth provider
75
- * return c.redirect(buildAuthUrl())
76
- * })
77
- *
78
- * routes.get("/callback", async (c) => {
79
- * // Process callback and return user data
80
- * const userData = await handleCallback(c)
81
- * return await ctx.success(c, userData)
82
- * })
83
- * }
84
- * ```
85
- */
86
- init: (route: ProviderRoute, options: ProviderOptions<Properties>) => void;
87
- }
88
- /**
89
- * Utilities and callbacks provided to providers during initialization.
90
- * Contains methods for state management, user flow completion, and storage access.
91
- *
92
- * @template Properties - Type of user data handled by the provider
93
- */
94
- interface ProviderOptions<Properties> {
95
- /**
96
- * Name of the provider instance as configured in the issuer.
97
- * Corresponds to the key used in the providers object.
98
- */
99
- readonly name: string;
100
- /**
101
- * Completes the authentication flow with user data.
102
- * Called when the provider successfully authenticates a user.
103
- *
104
- * @param ctx - Router request context
105
- * @param properties - User data extracted from the provider
106
- * @param opts - Optional utilities for session management
107
- * @returns Response that completes the OAuth flow
108
- *
109
- * @example
110
- * ```ts
111
- * const userData = { userId: "123", email: "user@example.com" }
112
- * return await ctx.success(c, userData)
113
- * ```
114
- */
115
- success: (ctx: RouterContext, properties: Properties, opts?: {
116
- /** Function to invalidate existing user sessions */
117
- readonly invalidate?: (subject: string) => Promise<void>;
118
- }) => Promise<Response>;
119
- /**
120
- * Forwards a response through the provider context.
121
- * Used for redirects and custom responses within the OAuth flow.
122
- *
123
- * @param ctx - Router request context
124
- * @param response - Response to forward
125
- * @returns Forwarded response
126
- */
127
- forward: (ctx: RouterContext, response: Response) => Response;
128
- /**
129
- * Stores a temporary value with expiration for the current session.
130
- * Useful for storing OAuth state, PKCE verifiers, and other temporary data.
131
- *
132
- * @param ctx - Router request context
133
- * @param key - Storage key identifier
134
- * @param maxAge - TTL in seconds
135
- * @param value - Value to store
136
- *
137
- * @example
138
- * ```ts
139
- * // Store OAuth state for 10 minutes
140
- * await ctx.set(c, "oauth_state", 600, { state, redirectUri })
141
- * ```
142
- */
143
- set: <T>(ctx: RouterContext, key: string, maxAge: number, value: T) => Promise<void>;
144
- /**
145
- * Retrieves a previously stored temporary value.
146
- *
147
- * @param ctx - Router request context
148
- * @param key - Storage key identifier
149
- * @returns Promise resolving to the stored value or undefined if not found/expired
150
- *
151
- * @example
152
- * ```ts
153
- * const oauthState = await ctx.get<OAuthState>(c, "oauth_state")
154
- * if (!oauthState) {
155
- * throw new Error("OAuth state expired")
156
- * }
157
- * ```
158
- */
159
- get: <T>(ctx: RouterContext, key: string) => Promise<T | undefined>;
160
- /**
161
- * Removes a stored temporary value.
162
- *
163
- * @param ctx - Router request context
164
- * @param key - Storage key identifier
165
- *
166
- * @example
167
- * ```ts
168
- * // Clean up OAuth state after use
169
- * await ctx.unset(c, "oauth_state")
170
- * ```
171
- */
172
- unset: (ctx: RouterContext, key: string) => Promise<void>;
173
- /**
174
- * Invalidates all sessions for a given subject (user).
175
- * Forces logout across all devices and applications.
176
- *
177
- * @param subject - Subject identifier to invalidate
178
- *
179
- * @example
180
- * ```ts
181
- * // Force logout on password change
182
- * await ctx.invalidate(userId)
183
- * ```
184
- */
185
- invalidate: (subject: string) => Promise<void>;
186
- /**
187
- * Storage adapter for persistent data operations.
188
- * Provides access to the configured storage backend.
189
- */
190
- readonly storage: StorageAdapter;
191
- }
192
- /**
193
- * Base error class for provider-related errors.
194
- * Extend this class to create specific provider error types.
195
- *
196
- * @example
197
- * ```ts
198
- * export class GitHubApiError extends ProviderError {
199
- * constructor(message: string, public readonly statusCode: number) {
200
- * super(message)
201
- * }
202
- * }
203
- * ```
204
- */
205
- declare class ProviderError extends Error {
206
- constructor(message: string);
207
- }
208
- /**
209
- * Error thrown when a provider encounters an unknown or unexpected error.
210
- * Used as a fallback for unhandled error conditions.
211
- *
212
- * @example
213
- * ```ts
214
- * catch (error) {
215
- * if (error instanceof SomeSpecificError) {
216
- * // Handle specific error
217
- * } else {
218
- * throw new ProviderUnknownError(`Unexpected error: ${error}`)
219
- * }
220
- * }
221
- * ```
222
- */
223
- declare class ProviderUnknownError extends ProviderError {
224
- constructor(message?: string);
225
- }
226
- //#endregion
227
- export { Provider, ProviderError, ProviderOptions, ProviderRoute, ProviderUnknownError };
@@ -1,44 +0,0 @@
1
- //#region src/provider/provider.ts
2
- /**
3
- * Base error class for provider-related errors.
4
- * Extend this class to create specific provider error types.
5
- *
6
- * @example
7
- * ```ts
8
- * export class GitHubApiError extends ProviderError {
9
- * constructor(message: string, public readonly statusCode: number) {
10
- * super(message)
11
- * }
12
- * }
13
- * ```
14
- */
15
- var ProviderError = class extends Error {
16
- constructor(message) {
17
- super(message);
18
- this.name = "ProviderError";
19
- }
20
- };
21
- /**
22
- * Error thrown when a provider encounters an unknown or unexpected error.
23
- * Used as a fallback for unhandled error conditions.
24
- *
25
- * @example
26
- * ```ts
27
- * catch (error) {
28
- * if (error instanceof SomeSpecificError) {
29
- * // Handle specific error
30
- * } else {
31
- * throw new ProviderUnknownError(`Unexpected error: ${error}`)
32
- * }
33
- * }
34
- * ```
35
- */
36
- var ProviderUnknownError = class extends ProviderError {
37
- constructor(message) {
38
- super(message || "An unknown provider error occurred");
39
- this.name = "ProviderUnknownError";
40
- }
41
- };
42
-
43
- //#endregion
44
- export { ProviderError, ProviderUnknownError };