@notionx/core 0.1.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 (208) hide show
  1. package/dist/admin/index.d.ts +137 -0
  2. package/dist/admin/index.js +206 -0
  3. package/dist/admin/index.js.map +1 -0
  4. package/dist/admin/pages/index.d.ts +324 -0
  5. package/dist/admin/pages/index.js +827 -0
  6. package/dist/admin/pages/index.js.map +1 -0
  7. package/dist/auth/auth-pages/forgot-password.d.ts +20 -0
  8. package/dist/auth/auth-pages/forgot-password.js +70 -0
  9. package/dist/auth/auth-pages/forgot-password.js.map +1 -0
  10. package/dist/auth/auth-pages/index.d.ts +6 -0
  11. package/dist/auth/auth-pages/index.js +342 -0
  12. package/dist/auth/auth-pages/index.js.map +1 -0
  13. package/dist/auth/auth-pages/login.d.ts +30 -0
  14. package/dist/auth/auth-pages/login.js +125 -0
  15. package/dist/auth/auth-pages/login.js.map +1 -0
  16. package/dist/auth/auth-pages/register.d.ts +17 -0
  17. package/dist/auth/auth-pages/register.js +81 -0
  18. package/dist/auth/auth-pages/register.js.map +1 -0
  19. package/dist/auth/auth-pages/reset-password.d.ts +18 -0
  20. package/dist/auth/auth-pages/reset-password.js +72 -0
  21. package/dist/auth/auth-pages/reset-password.js.map +1 -0
  22. package/dist/auth/index.d.ts +72 -0
  23. package/dist/auth/index.js +1011 -0
  24. package/dist/auth/index.js.map +1 -0
  25. package/dist/auth/passwords.d.ts +6 -0
  26. package/dist/auth/passwords.js +79 -0
  27. package/dist/auth/passwords.js.map +1 -0
  28. package/dist/auth/rate-limit.d.ts +28 -0
  29. package/dist/auth/rate-limit.js +245 -0
  30. package/dist/auth/rate-limit.js.map +1 -0
  31. package/dist/auth/routes/google-callback.d.ts +6 -0
  32. package/dist/auth/routes/google-callback.js +404 -0
  33. package/dist/auth/routes/google-callback.js.map +1 -0
  34. package/dist/auth/routes/google.d.ts +6 -0
  35. package/dist/auth/routes/google.js +250 -0
  36. package/dist/auth/routes/google.js.map +1 -0
  37. package/dist/auth/routes/index.d.ts +22 -0
  38. package/dist/auth/routes/index.js +619 -0
  39. package/dist/auth/routes/index.js.map +1 -0
  40. package/dist/auth/routes/verify-email.d.ts +6 -0
  41. package/dist/auth/routes/verify-email.js +317 -0
  42. package/dist/auth/routes/verify-email.js.map +1 -0
  43. package/dist/auth/routes/viewer.d.ts +6 -0
  44. package/dist/auth/routes/viewer.js +372 -0
  45. package/dist/auth/routes/viewer.js.map +1 -0
  46. package/dist/auth/session.d.ts +9 -0
  47. package/dist/auth/session.js +1 -0
  48. package/dist/auth/session.js.map +1 -0
  49. package/dist/auth/turnstile.d.ts +20 -0
  50. package/dist/auth/turnstile.js +301 -0
  51. package/dist/auth/turnstile.js.map +1 -0
  52. package/dist/auth/user-session.d.ts +42 -0
  53. package/dist/auth/user-session.js +419 -0
  54. package/dist/auth/user-session.js.map +1 -0
  55. package/dist/auth/users.d.ts +112 -0
  56. package/dist/auth/users.js +558 -0
  57. package/dist/auth/users.js.map +1 -0
  58. package/dist/bootstrap-CN2g76M6.d.ts +67 -0
  59. package/dist/cache/index.d.ts +6 -0
  60. package/dist/cache/index.js +47 -0
  61. package/dist/cache/index.js.map +1 -0
  62. package/dist/content/admin-summary.d.ts +24 -0
  63. package/dist/content/admin-summary.js +36 -0
  64. package/dist/content/admin-summary.js.map +1 -0
  65. package/dist/content/index.d.ts +9 -0
  66. package/dist/content/index.js +473 -0
  67. package/dist/content/index.js.map +1 -0
  68. package/dist/content/models.d.ts +69 -0
  69. package/dist/content/models.js +24 -0
  70. package/dist/content/models.js.map +1 -0
  71. package/dist/content/prewarm.d.ts +28 -0
  72. package/dist/content/prewarm.js +56 -0
  73. package/dist/content/prewarm.js.map +1 -0
  74. package/dist/content/revalidate.d.ts +37 -0
  75. package/dist/content/revalidate.js +170 -0
  76. package/dist/content/revalidate.js.map +1 -0
  77. package/dist/content/search-index.d.ts +54 -0
  78. package/dist/content/search-index.js +172 -0
  79. package/dist/content/search-index.js.map +1 -0
  80. package/dist/content/search.d.ts +8 -0
  81. package/dist/content/search.js +57 -0
  82. package/dist/content/search.js.map +1 -0
  83. package/dist/doctor/cli.d.ts +1 -0
  84. package/dist/doctor/cli.js +360 -0
  85. package/dist/doctor/cli.js.map +1 -0
  86. package/dist/doctor/index.d.ts +139 -0
  87. package/dist/doctor/index.js +289 -0
  88. package/dist/doctor/index.js.map +1 -0
  89. package/dist/email/index.d.ts +38 -0
  90. package/dist/email/index.js +126 -0
  91. package/dist/email/index.js.map +1 -0
  92. package/dist/env-C5qu-0R-.d.ts +35 -0
  93. package/dist/hooks/index.d.ts +2 -0
  94. package/dist/hooks/index.js +1 -0
  95. package/dist/hooks/index.js.map +1 -0
  96. package/dist/i18n/index.d.ts +26 -0
  97. package/dist/i18n/index.js +73 -0
  98. package/dist/i18n/index.js.map +1 -0
  99. package/dist/index.d.ts +8 -0
  100. package/dist/index.js +1281 -0
  101. package/dist/index.js.map +1 -0
  102. package/dist/internal/admin/index.d.ts +75 -0
  103. package/dist/internal/admin/index.js +365 -0
  104. package/dist/internal/admin/index.js.map +1 -0
  105. package/dist/media/index.d.ts +24 -0
  106. package/dist/media/index.js +86 -0
  107. package/dist/media/index.js.map +1 -0
  108. package/dist/media/routes/index.d.ts +1 -0
  109. package/dist/media/routes/index.js +585 -0
  110. package/dist/media/routes/index.js.map +1 -0
  111. package/dist/media/routes/notion-media.d.ts +19 -0
  112. package/dist/media/routes/notion-media.js +588 -0
  113. package/dist/media/routes/notion-media.js.map +1 -0
  114. package/dist/middleware.d.ts +95 -0
  115. package/dist/middleware.js +79 -0
  116. package/dist/middleware.js.map +1 -0
  117. package/dist/notion/block-text.d.ts +5 -0
  118. package/dist/notion/block-text.js +37 -0
  119. package/dist/notion/block-text.js.map +1 -0
  120. package/dist/notion/blocks.d.ts +24 -0
  121. package/dist/notion/blocks.js +46 -0
  122. package/dist/notion/blocks.js.map +1 -0
  123. package/dist/notion/client.d.ts +7 -0
  124. package/dist/notion/client.js +13 -0
  125. package/dist/notion/client.js.map +1 -0
  126. package/dist/notion/config.d.ts +25 -0
  127. package/dist/notion/config.js +147 -0
  128. package/dist/notion/config.js.map +1 -0
  129. package/dist/notion/content-cache.d.ts +45 -0
  130. package/dist/notion/content-cache.js +166 -0
  131. package/dist/notion/content-cache.js.map +1 -0
  132. package/dist/notion/generic-source.d.ts +61 -0
  133. package/dist/notion/generic-source.js +408 -0
  134. package/dist/notion/generic-source.js.map +1 -0
  135. package/dist/notion/index.d.ts +13 -0
  136. package/dist/notion/index.js +1278 -0
  137. package/dist/notion/index.js.map +1 -0
  138. package/dist/notion/mappers.d.ts +1 -0
  139. package/dist/notion/mappers.js +152 -0
  140. package/dist/notion/mappers.js.map +1 -0
  141. package/dist/notion/media.d.ts +22 -0
  142. package/dist/notion/media.js +209 -0
  143. package/dist/notion/media.js.map +1 -0
  144. package/dist/notion/property-mappers.d.ts +24 -0
  145. package/dist/notion/property-mappers.js +152 -0
  146. package/dist/notion/property-mappers.js.map +1 -0
  147. package/dist/notion/routes/index.d.ts +8 -0
  148. package/dist/notion/routes/index.js +428 -0
  149. package/dist/notion/routes/index.js.map +1 -0
  150. package/dist/notion/routes/webhook.d.ts +98 -0
  151. package/dist/notion/routes/webhook.js +428 -0
  152. package/dist/notion/routes/webhook.js.map +1 -0
  153. package/dist/notion/types.d.ts +152 -0
  154. package/dist/notion/types.js +1 -0
  155. package/dist/notion/types.js.map +1 -0
  156. package/dist/notion/webhook.d.ts +83 -0
  157. package/dist/notion/webhook.js +490 -0
  158. package/dist/notion/webhook.js.map +1 -0
  159. package/dist/platform/capabilities.d.ts +34 -0
  160. package/dist/platform/capabilities.js +42 -0
  161. package/dist/platform/capabilities.js.map +1 -0
  162. package/dist/platform/current.d.ts +13 -0
  163. package/dist/platform/current.js +181 -0
  164. package/dist/platform/current.js.map +1 -0
  165. package/dist/platform/index.d.ts +5 -0
  166. package/dist/platform/index.js +269 -0
  167. package/dist/platform/index.js.map +1 -0
  168. package/dist/platform/runtime.d.ts +118 -0
  169. package/dist/platform/runtime.js +160 -0
  170. package/dist/platform/runtime.js.map +1 -0
  171. package/dist/platform/selection.d.ts +10 -0
  172. package/dist/platform/selection.js +22 -0
  173. package/dist/platform/selection.js.map +1 -0
  174. package/dist/storage/index.d.ts +17 -0
  175. package/dist/storage/index.js +218 -0
  176. package/dist/storage/index.js.map +1 -0
  177. package/dist/storage/routes/cdn.d.ts +19 -0
  178. package/dist/storage/routes/cdn.js +289 -0
  179. package/dist/storage/routes/cdn.js.map +1 -0
  180. package/dist/storage/routes/files.d.ts +27 -0
  181. package/dist/storage/routes/files.js +216 -0
  182. package/dist/storage/routes/files.js.map +1 -0
  183. package/dist/storage/routes/index.d.ts +2 -0
  184. package/dist/storage/routes/index.js +352 -0
  185. package/dist/storage/routes/index.js.map +1 -0
  186. package/dist/types-BsAcZSNX.d.ts +94 -0
  187. package/dist/types.d.ts +78 -0
  188. package/dist/types.js +1 -0
  189. package/dist/types.js.map +1 -0
  190. package/dist/util/index.d.ts +18 -0
  191. package/dist/util/index.js +48 -0
  192. package/dist/util/index.js.map +1 -0
  193. package/dist/worker/index.d.ts +6 -0
  194. package/dist/worker/index.js +1026 -0
  195. package/dist/worker/index.js.map +1 -0
  196. package/dist/worker/routes/content-prewarm.d.ts +34 -0
  197. package/dist/worker/routes/content-prewarm.js +38 -0
  198. package/dist/worker/routes/content-prewarm.js.map +1 -0
  199. package/dist/worker/routes/content-revalidate.d.ts +81 -0
  200. package/dist/worker/routes/content-revalidate.js +64 -0
  201. package/dist/worker/routes/content-revalidate.js.map +1 -0
  202. package/dist/worker/routes/health.d.ts +14 -0
  203. package/dist/worker/routes/health.js +278 -0
  204. package/dist/worker/routes/health.js.map +1 -0
  205. package/dist/worker/routes/index.d.ts +6 -0
  206. package/dist/worker/routes/index.js +373 -0
  207. package/dist/worker/routes/index.js.map +1 -0
  208. package/package.json +124 -0
@@ -0,0 +1,1011 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/util/env.ts
9
+ import { env } from "cloudflare:workers";
10
+ var workerEnv = env;
11
+
12
+ // src/platform/runtime.ts
13
+ function cacheRequestForKey(key) {
14
+ return new Request(key, { method: "GET" });
15
+ }
16
+ function createCloudflarePublicCacheAdapter(cache2) {
17
+ return {
18
+ kind: "cloudflare-cache",
19
+ async match(key) {
20
+ return await cache2.match(cacheRequestForKey(key)) ?? null;
21
+ },
22
+ put(key, response) {
23
+ return cache2.put(cacheRequestForKey(key), response);
24
+ },
25
+ delete(key) {
26
+ return cache2.delete(cacheRequestForKey(key));
27
+ }
28
+ };
29
+ }
30
+ function createCloudflareKeyValueCacheAdapter(namespace) {
31
+ return {
32
+ kind: "workers-kv",
33
+ async get(key, options) {
34
+ return await namespace.get(key, {
35
+ type: "json",
36
+ cacheTtl: options?.cacheTtl
37
+ });
38
+ },
39
+ async put(key, value, options) {
40
+ await namespace.put(key, JSON.stringify(value), {
41
+ expirationTtl: options?.expirationTtl,
42
+ metadata: options?.metadata
43
+ });
44
+ },
45
+ delete(key) {
46
+ return namespace.delete(key);
47
+ },
48
+ async list(options) {
49
+ const result = await namespace.list({
50
+ prefix: options?.prefix,
51
+ limit: options?.limit,
52
+ cursor: options?.cursor
53
+ });
54
+ return {
55
+ keys: result.keys.map((key) => ({ name: key.name })),
56
+ cursor: result.list_complete ? void 0 : result.cursor,
57
+ listComplete: result.list_complete
58
+ };
59
+ }
60
+ };
61
+ }
62
+ function r2ObjectToStoredObject(object) {
63
+ return {
64
+ body: object.body,
65
+ size: object.size,
66
+ etag: object.etag,
67
+ contentType: object.httpMetadata?.contentType
68
+ };
69
+ }
70
+ function createCloudflareRuntimePlatform(env2, options) {
71
+ const database = env2.DB ? {
72
+ kind: "d1",
73
+ prepare(query) {
74
+ return env2.DB.prepare(query);
75
+ },
76
+ async batch(statements) {
77
+ return await env2.DB.batch(
78
+ statements
79
+ );
80
+ }
81
+ } : null;
82
+ const objectStorage = env2.ASSETS_BUCKET ? {
83
+ kind: "r2",
84
+ async get(key) {
85
+ const object = await env2.ASSETS_BUCKET?.get(key);
86
+ return object ? r2ObjectToStoredObject(object) : null;
87
+ },
88
+ async put(key, value, options2) {
89
+ await env2.ASSETS_BUCKET?.put(key, value, {
90
+ httpMetadata: {
91
+ contentType: options2?.contentType,
92
+ cacheControl: options2?.cacheControl
93
+ },
94
+ customMetadata: options2?.metadata
95
+ });
96
+ },
97
+ async delete(key) {
98
+ await env2.ASSETS_BUCKET?.delete(key);
99
+ },
100
+ async list(options2) {
101
+ const listed = await env2.ASSETS_BUCKET?.list({
102
+ prefix: options2?.prefix,
103
+ limit: options2?.limit
104
+ });
105
+ return listed?.objects.map((object) => ({
106
+ key: object.key,
107
+ size: object.size,
108
+ uploaded: object.uploaded
109
+ })) ?? [];
110
+ }
111
+ } : null;
112
+ const imageTransformer = env2.IMAGES ? {
113
+ kind: "cloudflare-images",
114
+ async transform(body, options2) {
115
+ const result = await env2.IMAGES.input(body).transform(options2.width ? { width: options2.width } : {}).output({
116
+ format: options2.format,
117
+ quality: options2.quality
118
+ });
119
+ return {
120
+ body: result.image(),
121
+ contentType: result.contentType(),
122
+ response: () => result.response()
123
+ };
124
+ }
125
+ } : null;
126
+ const keyValueCache = env2.CONTENT_CACHE ? createCloudflareKeyValueCacheAdapter(env2.CONTENT_CACHE) : null;
127
+ return {
128
+ id: "cloudflare-workers",
129
+ database,
130
+ objectStorage,
131
+ imageTransformer,
132
+ keyValueCache,
133
+ publicCache: options?.publicCache ? createCloudflarePublicCacheAdapter(options.publicCache) : null
134
+ };
135
+ }
136
+
137
+ // src/platform/cloudflare-runtime.ts
138
+ function getDefaultCloudflareCache() {
139
+ const globalWithCaches = globalThis;
140
+ return globalWithCaches.caches?.default ?? null;
141
+ }
142
+ function getRuntimePlatform() {
143
+ return createCloudflareRuntimePlatform(workerEnv, {
144
+ publicCache: getDefaultCloudflareCache()
145
+ });
146
+ }
147
+
148
+ // src/platform/current.ts
149
+ function getRuntimePlatform2() {
150
+ return getRuntimePlatform();
151
+ }
152
+ function getDatabase() {
153
+ const platform = getRuntimePlatform2();
154
+ const database = platform.database;
155
+ if (!database) {
156
+ throw new Error(`SQL database adapter not configured for ${platform.id}`);
157
+ }
158
+ return database;
159
+ }
160
+
161
+ // src/auth/auth.ts
162
+ function notImplemented(method) {
163
+ throw new Error(
164
+ `createAuth: ${method} is not implemented yet. It will be wired up in Task 3.2 (move auth internals).`
165
+ );
166
+ }
167
+ function createAuth(config) {
168
+ const getRuntime = () => getRuntimePlatform2();
169
+ void config;
170
+ return {
171
+ async requireViewer(_request) {
172
+ void getRuntime();
173
+ notImplemented("requireViewer");
174
+ },
175
+ async requireRole(_request, _role) {
176
+ void getRuntime();
177
+ notImplemented("requireRole");
178
+ },
179
+ async listUsers() {
180
+ void getRuntime();
181
+ notImplemented("listUsers");
182
+ },
183
+ async setUserRole(_userId, _role) {
184
+ void getRuntime();
185
+ notImplemented("setUserRole");
186
+ },
187
+ async checkRateLimit(_key, _limit, _windowMs) {
188
+ void getRuntime();
189
+ notImplemented("checkRateLimit");
190
+ },
191
+ async verifyTurnstile(_token, _ip) {
192
+ void getRuntime();
193
+ notImplemented("verifyTurnstile");
194
+ }
195
+ };
196
+ }
197
+
198
+ // src/auth/user-session.ts
199
+ import { cookies } from "next/headers";
200
+
201
+ // src/auth/passwords.ts
202
+ var HASH_PREFIX = "pbkdf2_sha256";
203
+ var PBKDF2_ITERATIONS = 1e5;
204
+ var SALT_BYTES = 16;
205
+ function bytesToBase64(bytes) {
206
+ let bin = "";
207
+ for (const b of bytes) bin += String.fromCharCode(b);
208
+ return btoa(bin);
209
+ }
210
+ function base64ToBytes(input) {
211
+ const bin = atob(input);
212
+ const bytes = new Uint8Array(bin.length);
213
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
214
+ return bytes;
215
+ }
216
+ async function deriveKey(password, salt, iterations) {
217
+ const normalizedSalt = Uint8Array.from(salt);
218
+ const baseKey = await crypto.subtle.importKey(
219
+ "raw",
220
+ new TextEncoder().encode(password),
221
+ "PBKDF2",
222
+ false,
223
+ ["deriveBits"]
224
+ );
225
+ const derived = await crypto.subtle.deriveBits(
226
+ {
227
+ name: "PBKDF2",
228
+ hash: "SHA-256",
229
+ salt: normalizedSalt,
230
+ iterations
231
+ },
232
+ baseKey,
233
+ 256
234
+ );
235
+ return new Uint8Array(derived);
236
+ }
237
+ async function constantTimeEqual(left, right) {
238
+ if (left.length !== right.length) return false;
239
+ let diff = 0;
240
+ for (let i = 0; i < left.length; i++) {
241
+ diff |= left[i] ^ right[i];
242
+ }
243
+ return diff === 0;
244
+ }
245
+ async function hashPassword(password) {
246
+ const salt = crypto.getRandomValues(new Uint8Array(SALT_BYTES));
247
+ const derived = await deriveKey(password, salt, PBKDF2_ITERATIONS);
248
+ return [
249
+ HASH_PREFIX,
250
+ String(PBKDF2_ITERATIONS),
251
+ bytesToBase64(salt),
252
+ bytesToBase64(derived)
253
+ ].join("$");
254
+ }
255
+ function validatePasswordStrength(password) {
256
+ if (password.length < 8) return "\u5BC6\u7801\u81F3\u5C11\u9700\u8981 8 \u4F4D";
257
+ if (!/[a-zA-Z]/.test(password) || !/\d/.test(password)) {
258
+ return "\u5BC6\u7801\u9700\u8981\u540C\u65F6\u5305\u542B\u5B57\u6BCD\u548C\u6570\u5B57";
259
+ }
260
+ return null;
261
+ }
262
+ async function verifyPassword(password, storedHash) {
263
+ const [prefix, iterationsStr, saltB64, hashB64] = storedHash.split("$");
264
+ if (prefix !== HASH_PREFIX || !iterationsStr || !saltB64 || !hashB64) {
265
+ return false;
266
+ }
267
+ const iterations = Number(iterationsStr);
268
+ if (!Number.isFinite(iterations) || iterations <= 0) return false;
269
+ const salt = base64ToBytes(saltB64);
270
+ const expected = base64ToBytes(hashB64);
271
+ const derived = await deriveKey(password, salt, iterations);
272
+ return constantTimeEqual(derived, expected);
273
+ }
274
+
275
+ // src/internal/admin/settings.ts
276
+ import { cache } from "react";
277
+
278
+ // src/internal/admin/schema-guard.ts
279
+ var DEFAULT_TURNSTILE_PUBLIC_CONFIG = {
280
+ enabled: false,
281
+ siteKey: null,
282
+ secretConfigured: false
283
+ };
284
+ function isSchemaDriftError(error) {
285
+ const message = error instanceof Error ? error.message : String(error ?? "");
286
+ return message.includes("no such column") || message.includes("no such table");
287
+ }
288
+ function buildTurnstilePublicConfig(settings, envLike) {
289
+ const envSiteKey = envLike.TURNSTILE_SITE_KEY?.trim() || null;
290
+ const siteKey = settings.turnstile_site_key?.trim() || envSiteKey || null;
291
+ const secretConfigured = Boolean(envLike.TURNSTILE_SECRET_KEY?.trim());
292
+ const enabled = (settings.turnstile_enabled === 1 || Boolean(envSiteKey)) && Boolean(siteKey) && secretConfigured;
293
+ return {
294
+ enabled,
295
+ siteKey,
296
+ secretConfigured
297
+ };
298
+ }
299
+
300
+ // src/internal/admin/settings.ts
301
+ var DEFAULT_ADMIN_EMAIL = "zhaofilms@gmail.com";
302
+ function rowToSettings(r) {
303
+ return {
304
+ site_title: r.site_title,
305
+ google_enabled: r.google_enabled === 1 ? 1 : 0,
306
+ google_client_id: r.google_client_id,
307
+ google_client_secret: r.google_client_secret,
308
+ google_updated_at: r.google_updated_at,
309
+ turnstile_enabled: r.turnstile_enabled === 1 ? 1 : 0,
310
+ turnstile_site_key: r.turnstile_site_key,
311
+ turnstile_updated_at: r.turnstile_updated_at,
312
+ admin_email: r.admin_email,
313
+ updated_at: r.updated_at
314
+ };
315
+ }
316
+ var getAppSettingsCached = cache(async () => {
317
+ const row = await getDatabase().prepare(
318
+ `SELECT site_title, google_enabled, google_client_id, google_client_secret,
319
+ google_updated_at, turnstile_enabled, turnstile_site_key,
320
+ turnstile_updated_at, admin_email, updated_at
321
+ FROM app_settings WHERE id = 1`
322
+ ).first();
323
+ if (!row) {
324
+ return {
325
+ site_title: "vinext Blog",
326
+ google_enabled: 0,
327
+ google_client_id: null,
328
+ google_client_secret: null,
329
+ google_updated_at: null,
330
+ turnstile_enabled: 0,
331
+ turnstile_site_key: null,
332
+ turnstile_updated_at: null,
333
+ admin_email: DEFAULT_ADMIN_EMAIL,
334
+ updated_at: ""
335
+ };
336
+ }
337
+ return rowToSettings(row);
338
+ });
339
+ async function getAppSettings() {
340
+ return getAppSettingsCached();
341
+ }
342
+ async function getTurnstilePublicConfig() {
343
+ try {
344
+ const s = await getAppSettings();
345
+ return buildTurnstilePublicConfig(s, workerEnv);
346
+ } catch (error) {
347
+ if (isSchemaDriftError(error)) {
348
+ console.error(
349
+ "[settings] turnstile config unavailable due to schema drift; falling back to disabled state",
350
+ error
351
+ );
352
+ return { ...DEFAULT_TURNSTILE_PUBLIC_CONFIG };
353
+ }
354
+ throw error;
355
+ }
356
+ }
357
+
358
+ // src/internal/admin/admin.ts
359
+ function normalizeEmail(email) {
360
+ return email.trim().toLowerCase();
361
+ }
362
+ async function isAdminEmail(email) {
363
+ if (!email) return false;
364
+ const settings = await getAppSettings();
365
+ return normalizeEmail(email) === normalizeEmail(settings.admin_email);
366
+ }
367
+
368
+ // src/auth/users.ts
369
+ function normalizeEmail2(email) {
370
+ return email.trim().toLowerCase();
371
+ }
372
+ async function defaultRoleFor(email) {
373
+ return await isAdminEmail(email) ? "admin" : "user";
374
+ }
375
+ function normalizeUserRole(role) {
376
+ if (role === "admin" || role === "vip") return role;
377
+ return "user";
378
+ }
379
+ function createRandomToken() {
380
+ return [...crypto.getRandomValues(new Uint8Array(24))].map((b) => b.toString(16).padStart(2, "0")).join("");
381
+ }
382
+ function userToSession(user) {
383
+ return {
384
+ uid: user.id,
385
+ email: user.email,
386
+ name: user.name,
387
+ picture: user.picture,
388
+ rev: user.session_rev ?? 0
389
+ };
390
+ }
391
+ async function upsertGoogleUser(input) {
392
+ const db = getDatabase();
393
+ const email = normalizeEmail2(input.email);
394
+ const existing = await db.prepare(
395
+ `SELECT * FROM users WHERE google_sub = ? OR email = ? LIMIT 1`
396
+ ).bind(input.googleSub, email).first();
397
+ if (existing) {
398
+ await db.prepare(
399
+ `UPDATE users
400
+ SET email = ?, name = ?, picture = ?, google_sub = ?, email_verified = 1,
401
+ email_verify_token = NULL, email_verify_expires_at = NULL,
402
+ last_seen_at = datetime('now')
403
+ WHERE id = ?`
404
+ ).bind(email, input.name, input.picture, input.googleSub, existing.id).run();
405
+ } else {
406
+ const role = await defaultRoleFor(email);
407
+ await db.prepare(
408
+ `INSERT INTO users (
409
+ email, name, picture, google_sub, email_verified, role, last_seen_at
410
+ ) VALUES (?, ?, ?, ?, 1, ?, datetime('now'))`
411
+ ).bind(email, input.name, input.picture, input.googleSub, role).run();
412
+ }
413
+ if (await isAdminEmail(email)) {
414
+ await db.prepare(
415
+ `UPDATE users SET role = 'admin' WHERE email = ?`
416
+ ).bind(email).run();
417
+ }
418
+ const user = await getUserByEmail(email);
419
+ if (!user) throw new Error("User upsert failed");
420
+ return user;
421
+ }
422
+ async function createEmailUser(input) {
423
+ const email = normalizeEmail2(input.email);
424
+ const existing = await getUserByEmail(email);
425
+ if (existing) {
426
+ return { ok: false, reason: "exists" };
427
+ }
428
+ const passwordHash = await hashPassword(input.password);
429
+ const verifyToken = createRandomToken();
430
+ const verifyExpiresAt = new Date(Date.now() + 1e3 * 60 * 60 * 24).toISOString();
431
+ const role = await defaultRoleFor(email);
432
+ const db = getDatabase();
433
+ await db.prepare(
434
+ `INSERT INTO users (
435
+ email, password_hash, email_verified, email_verify_token,
436
+ email_verify_expires_at, role, last_seen_at
437
+ ) VALUES (?, ?, 0, ?, ?, ?, datetime('now'))`
438
+ ).bind(email, passwordHash, verifyToken, verifyExpiresAt, role).run();
439
+ if (role === "admin") {
440
+ await db.prepare(
441
+ `UPDATE users SET role = 'admin' WHERE email = ?`
442
+ ).bind(email).run();
443
+ }
444
+ const user = await getUserByEmail(email);
445
+ if (!user) throw new Error("User creation failed");
446
+ return { ok: true, user, verifyToken };
447
+ }
448
+ async function verifyEmailUser(token) {
449
+ const user = await getDatabase().prepare(
450
+ `SELECT * FROM users WHERE email_verify_token = ?`
451
+ ).bind(token).first();
452
+ if (!user || !user.email_verify_expires_at) return null;
453
+ if (new Date(user.email_verify_expires_at).getTime() < Date.now()) {
454
+ return null;
455
+ }
456
+ await getDatabase().prepare(
457
+ `UPDATE users
458
+ SET email_verified = 1,
459
+ email_verify_token = NULL,
460
+ email_verify_expires_at = NULL,
461
+ last_seen_at = datetime('now')
462
+ WHERE id = ?`
463
+ ).bind(user.id).run();
464
+ return getUserByEmail(user.email);
465
+ }
466
+ async function issueVerificationToken(email) {
467
+ const user = await getUserByEmail(email);
468
+ if (!user) return { ok: false, reason: "not_found" };
469
+ if (!user.password_hash) return { ok: false, reason: "no_password" };
470
+ if (user.email_verified) return { ok: false, reason: "already_verified" };
471
+ const verifyToken = createRandomToken();
472
+ const verifyExpiresAt = new Date(Date.now() + 1e3 * 60 * 60 * 24).toISOString();
473
+ await getDatabase().prepare(
474
+ `UPDATE users
475
+ SET email_verify_token = ?, email_verify_expires_at = ?
476
+ WHERE id = ?`
477
+ ).bind(verifyToken, verifyExpiresAt, user.id).run();
478
+ const updated = await getUserByEmail(email);
479
+ if (!updated) return { ok: false, reason: "not_found" };
480
+ return { ok: true, token: verifyToken, user: updated };
481
+ }
482
+ async function issuePasswordResetToken(email) {
483
+ const user = await getUserByEmail(email);
484
+ if (!user || !user.password_hash) {
485
+ return { ok: false, reason: "not_found" };
486
+ }
487
+ if (!user.email_verified) {
488
+ return { ok: false, reason: "unverified" };
489
+ }
490
+ const resetToken = createRandomToken();
491
+ const resetExpiresAt = new Date(Date.now() + 1e3 * 60 * 60).toISOString();
492
+ await getDatabase().prepare(
493
+ `UPDATE users
494
+ SET password_reset_token = ?, password_reset_expires_at = ?
495
+ WHERE id = ?`
496
+ ).bind(resetToken, resetExpiresAt, user.id).run();
497
+ const updated = await getUserByEmail(email);
498
+ if (!updated) return { ok: false, reason: "not_found" };
499
+ return { ok: true, token: resetToken, user: updated };
500
+ }
501
+ async function resetPasswordWithToken(input) {
502
+ const user = await getDatabase().prepare(
503
+ `SELECT * FROM users WHERE password_reset_token = ?`
504
+ ).bind(input.token).first();
505
+ if (!user || !user.password_reset_expires_at) {
506
+ return { ok: false, reason: "invalid" };
507
+ }
508
+ if (new Date(user.password_reset_expires_at).getTime() < Date.now()) {
509
+ return { ok: false, reason: "invalid" };
510
+ }
511
+ const passwordHash = await hashPassword(input.password);
512
+ await getDatabase().prepare(
513
+ `UPDATE users
514
+ SET password_hash = ?,
515
+ password_reset_token = NULL,
516
+ password_reset_expires_at = NULL,
517
+ session_rev = session_rev + 1,
518
+ last_seen_at = datetime('now')
519
+ WHERE id = ?`
520
+ ).bind(passwordHash, user.id).run();
521
+ const updated = await getUserById(user.id);
522
+ if (!updated) return { ok: false, reason: "invalid" };
523
+ return { ok: true, user: updated };
524
+ }
525
+ async function changeUserPassword(input) {
526
+ const user = await getUserById(input.userId);
527
+ if (!user || !user.password_hash) {
528
+ return { ok: false, reason: "no_password" };
529
+ }
530
+ const matches = await verifyPassword(
531
+ input.currentPassword,
532
+ user.password_hash
533
+ );
534
+ if (!matches) {
535
+ return { ok: false, reason: "invalid" };
536
+ }
537
+ const passwordHash = await hashPassword(input.newPassword);
538
+ await getDatabase().prepare(
539
+ `UPDATE users
540
+ SET password_hash = ?,
541
+ session_rev = session_rev + 1,
542
+ last_seen_at = datetime('now')
543
+ WHERE id = ?`
544
+ ).bind(passwordHash, user.id).run();
545
+ const updated = await getUserById(user.id);
546
+ if (!updated) return { ok: false, reason: "invalid" };
547
+ return { ok: true, user: updated };
548
+ }
549
+ async function authenticateEmailUser(input) {
550
+ const email = normalizeEmail2(input.email);
551
+ const user = await getUserByEmail(email);
552
+ if (!user || !user.password_hash) {
553
+ return { ok: false, reason: "invalid" };
554
+ }
555
+ const matches = await verifyPassword(input.password, user.password_hash);
556
+ if (!matches) {
557
+ return { ok: false, reason: "invalid" };
558
+ }
559
+ if (!user.email_verified) {
560
+ return { ok: false, reason: "unverified" };
561
+ }
562
+ await getDatabase().prepare(
563
+ `UPDATE users SET last_seen_at = datetime('now') WHERE id = ?`
564
+ ).bind(user.id).run();
565
+ return { ok: true, user: { ...user, email } };
566
+ }
567
+ async function getUserByEmail(email) {
568
+ return await getDatabase().prepare(`SELECT * FROM users WHERE email = ?`).bind(normalizeEmail2(email)).first();
569
+ }
570
+ async function getUserById(id) {
571
+ return await getDatabase().prepare(`SELECT * FROM users WHERE id = ?`).bind(id).first();
572
+ }
573
+ async function listUsers(limit = 100) {
574
+ const { results } = await getDatabase().prepare(
575
+ `SELECT * FROM users ORDER BY created_at DESC LIMIT ?`
576
+ ).bind(limit).all();
577
+ return results || [];
578
+ }
579
+ async function listUsersWithPostCounts(limit = 100) {
580
+ const { results } = await getDatabase().prepare(
581
+ `SELECT u.*,
582
+ (SELECT COUNT(*) FROM posts p WHERE p.owner_email = u.email) AS post_count
583
+ FROM users u
584
+ ORDER BY u.created_at DESC
585
+ LIMIT ?`
586
+ ).bind(limit).all();
587
+ return results || [];
588
+ }
589
+ async function revokeUserSessions(userId) {
590
+ const user = await getUserById(userId);
591
+ if (!user) return false;
592
+ await getDatabase().prepare(
593
+ `UPDATE users SET session_rev = session_rev + 1 WHERE id = ?`
594
+ ).bind(userId).run();
595
+ return true;
596
+ }
597
+ async function setUserRole(userId, role) {
598
+ const user = await getUserById(userId);
599
+ if (!user) return { ok: false, reason: "not_found" };
600
+ if (await isAdminEmail(user.email)) {
601
+ return { ok: false, reason: "is_admin" };
602
+ }
603
+ await getDatabase().prepare(
604
+ `UPDATE users
605
+ SET role = ?,
606
+ last_seen_at = datetime('now')
607
+ WHERE id = ?`
608
+ ).bind(role, userId).run();
609
+ const updated = await getUserById(userId);
610
+ if (!updated) return { ok: false, reason: "not_found" };
611
+ return { ok: true, user: updated };
612
+ }
613
+ async function deleteUserAccount(userId) {
614
+ const user = await getUserById(userId);
615
+ if (!user) return { ok: false, reason: "not_found" };
616
+ if (await isAdminEmail(user.email)) {
617
+ return { ok: false, reason: "is_admin" };
618
+ }
619
+ const settings = await getAppSettings();
620
+ const adminEmail = settings.admin_email;
621
+ const db = getDatabase();
622
+ await db.batch([
623
+ db.prepare(
624
+ `UPDATE posts SET owner_email = ? WHERE owner_email = ?`
625
+ ).bind(adminEmail, user.email),
626
+ db.prepare(`DELETE FROM users WHERE id = ?`).bind(userId)
627
+ ]);
628
+ return { ok: true, email: user.email };
629
+ }
630
+
631
+ // src/auth/user-session.ts
632
+ var SESSION_TTL_SECONDS = 60 * 60 * 24 * 7;
633
+ var USER_COOKIE_NAME = "vinext_user_session";
634
+ var ADMIN_COOKIE_NAME = "vinext_admin_session";
635
+ var USER_COOKIE = USER_COOKIE_NAME;
636
+ var ADMIN_COOKIE = ADMIN_COOKIE_NAME;
637
+ function getAdminPassword() {
638
+ let fromWorker;
639
+ try {
640
+ const mod = __require("cloudflare:workers");
641
+ fromWorker = mod.env?.ADMIN_PASSWORD;
642
+ } catch {
643
+ fromWorker = void 0;
644
+ }
645
+ if (fromWorker) return fromWorker;
646
+ return process.env.ADMIN_PASSWORD ?? "vinext-admin-2026";
647
+ }
648
+ async function hmac(secret, message) {
649
+ const enc = new TextEncoder();
650
+ const key = await crypto.subtle.importKey(
651
+ "raw",
652
+ enc.encode(secret),
653
+ { name: "HMAC", hash: "SHA-256" },
654
+ false,
655
+ ["sign"]
656
+ );
657
+ const sig = await crypto.subtle.sign("HMAC", key, enc.encode(message));
658
+ return [...new Uint8Array(sig)].map((b) => b.toString(16).padStart(2, "0")).join("");
659
+ }
660
+ async function constantTimeEqual2(a, b) {
661
+ const aBytes = new TextEncoder().encode(a);
662
+ const bBytes = new TextEncoder().encode(b);
663
+ if (aBytes.length !== bBytes.length) return false;
664
+ let diff = 0;
665
+ for (let i = 0; i < aBytes.length; i++) {
666
+ diff |= aBytes[i] ^ bBytes[i];
667
+ }
668
+ return diff === 0;
669
+ }
670
+ async function signPayload(payload) {
671
+ return hmac(getAdminPassword(), payload);
672
+ }
673
+ function utf8ToBase64(s) {
674
+ const bytes = new TextEncoder().encode(s);
675
+ let bin = "";
676
+ for (const b of bytes) bin += String.fromCharCode(b);
677
+ return btoa(bin);
678
+ }
679
+ function base64ToUtf8(b64) {
680
+ const bin = atob(b64);
681
+ const bytes = new Uint8Array(bin.length);
682
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
683
+ return new TextDecoder().decode(bytes);
684
+ }
685
+ async function signUserToken(user) {
686
+ const exp = Math.floor(Date.now() / 1e3) + SESSION_TTL_SECONDS;
687
+ const payload = { ...user, exp };
688
+ const json = JSON.stringify(payload);
689
+ const b64 = utf8ToBase64(json);
690
+ const sig = await signPayload(b64);
691
+ return `${b64}.${sig}`;
692
+ }
693
+ async function setUserSessionCookie(user) {
694
+ const token = await signUserToken(user);
695
+ const jar = await cookies();
696
+ jar.set(USER_COOKIE_NAME, token, {
697
+ httpOnly: true,
698
+ secure: process.env.NODE_ENV === "production",
699
+ sameSite: "lax",
700
+ path: "/",
701
+ maxAge: SESSION_TTL_SECONDS
702
+ });
703
+ }
704
+ async function verifyUserToken(token) {
705
+ if (!token) return null;
706
+ const parts = token.split(".");
707
+ if (parts.length !== 2) return null;
708
+ const [b64, sig] = parts;
709
+ const expected = await signPayload(b64);
710
+ if (!await constantTimeEqual2(sig, expected)) return null;
711
+ try {
712
+ const json = base64ToUtf8(b64);
713
+ const payload = JSON.parse(json);
714
+ if (payload.exp < Math.floor(Date.now() / 1e3)) return null;
715
+ const dbUser = await getUserById(payload.uid);
716
+ if (!dbUser) return null;
717
+ if (dbUser.email !== payload.email) return null;
718
+ const tokenRev = payload.rev ?? 0;
719
+ const dbRev = dbUser.session_rev ?? 0;
720
+ if (tokenRev !== dbRev) return null;
721
+ return {
722
+ uid: payload.uid,
723
+ email: payload.email,
724
+ name: payload.name,
725
+ picture: payload.picture,
726
+ rev: dbRev
727
+ };
728
+ } catch {
729
+ return null;
730
+ }
731
+ }
732
+ async function getCurrentUser() {
733
+ const jar = await cookies();
734
+ const token = jar.get(USER_COOKIE_NAME)?.value;
735
+ return verifyUserToken(token);
736
+ }
737
+ async function clearUserSessionCookie() {
738
+ const jar = await cookies();
739
+ jar.set(USER_COOKIE_NAME, "", { path: "/", maxAge: 0 });
740
+ }
741
+ async function signAdminToken() {
742
+ const password = getAdminPassword();
743
+ const exp = Math.floor(Date.now() / 1e3) + SESSION_TTL_SECONDS;
744
+ const payload = `ok.${exp}`;
745
+ const sig = await hmac(password, payload);
746
+ return `${payload}.${sig}`;
747
+ }
748
+ async function verifyAdminToken(token) {
749
+ if (!token) return false;
750
+ const parts = token.split(".");
751
+ if (parts.length !== 3) return false;
752
+ const [flag, expStr, sig] = parts;
753
+ if (flag !== "ok") return false;
754
+ const exp = Number(expStr);
755
+ if (!Number.isFinite(exp) || exp < Math.floor(Date.now() / 1e3)) return false;
756
+ const password = getAdminPassword();
757
+ const expected = await hmac(password, `${flag}.${exp}`);
758
+ return constantTimeEqual2(sig, expected);
759
+ }
760
+ async function checkPassword(input) {
761
+ const expected = getAdminPassword();
762
+ return constantTimeEqual2(input, expected);
763
+ }
764
+ async function setSessionCookie() {
765
+ const token = await signAdminToken();
766
+ const jar = await cookies();
767
+ jar.set(ADMIN_COOKIE_NAME, token, {
768
+ httpOnly: true,
769
+ secure: process.env.NODE_ENV === "production",
770
+ sameSite: "lax",
771
+ path: "/",
772
+ maxAge: SESSION_TTL_SECONDS
773
+ });
774
+ }
775
+ async function clearSessionCookie() {
776
+ const jar = await cookies();
777
+ jar.set(ADMIN_COOKIE_NAME, "", { path: "/", maxAge: 0 });
778
+ }
779
+ function getAdminEmailFromEnv() {
780
+ let fromWorker;
781
+ try {
782
+ const mod = __require("cloudflare:workers");
783
+ fromWorker = mod.env?.ADMIN_EMAIL;
784
+ } catch {
785
+ fromWorker = void 0;
786
+ }
787
+ return (fromWorker || "zhaofilms@gmail.com").toLowerCase();
788
+ }
789
+ async function getAuthViewer() {
790
+ const jar = await cookies();
791
+ if (await verifyAdminToken(jar.get(ADMIN_COOKIE_NAME)?.value)) {
792
+ return {
793
+ email: getAdminEmailFromEnv(),
794
+ user: null,
795
+ role: "admin",
796
+ isAdmin: true,
797
+ isVip: true,
798
+ canViewVipContent: true
799
+ };
800
+ }
801
+ const user = await verifyUserToken(jar.get(USER_COOKIE_NAME)?.value);
802
+ if (!user) return null;
803
+ const dbUser = await getUserById(user.uid);
804
+ if (!dbUser) return null;
805
+ const role = dbUser.role === "admin" || dbUser.role === "vip" ? dbUser.role : "user";
806
+ const isAdmin = role === "admin";
807
+ const isVip = role === "vip" || isAdmin;
808
+ return {
809
+ email: user.email,
810
+ user,
811
+ role,
812
+ isAdmin,
813
+ isVip,
814
+ canViewVipContent: isVip
815
+ };
816
+ }
817
+ async function isAuthenticated() {
818
+ const jar = await cookies();
819
+ if (await verifyAdminToken(jar.get(ADMIN_COOKIE_NAME)?.value)) return true;
820
+ if (await verifyUserToken(jar.get(USER_COOKIE_NAME)?.value)) return true;
821
+ return false;
822
+ }
823
+
824
+ // src/auth/rate-limit.ts
825
+ var WINDOW_MS = 15 * 60 * 1e3;
826
+ var MAX_EMAIL_ATTEMPTS = 5;
827
+ var MAX_IP_ATTEMPTS = 30;
828
+ function scopeKey(kind, bucket, identifier) {
829
+ const normalized = bucket === "email" ? identifier.trim().toLowerCase() : identifier.trim();
830
+ return `${kind}:${bucket}:${normalized}`;
831
+ }
832
+ async function readScope(scope) {
833
+ return getDatabase().prepare(
834
+ `SELECT attempts, window_start FROM auth_rate_limits WHERE scope = ?`
835
+ ).bind(scope).first();
836
+ }
837
+ async function checkScoped(kind, bucket, identifier, maxAttempts) {
838
+ const scope = scopeKey(kind, bucket, identifier);
839
+ const row = await readScope(scope);
840
+ const now = Date.now();
841
+ if (!row) return { ok: true };
842
+ if (now - row.window_start >= WINDOW_MS) return { ok: true };
843
+ if (row.attempts >= maxAttempts) {
844
+ const retryAfterSec = Math.ceil(
845
+ (row.window_start + WINDOW_MS - now) / 1e3
846
+ );
847
+ return {
848
+ ok: false,
849
+ retryAfterSec: Math.max(retryAfterSec, 1),
850
+ scope: bucket
851
+ };
852
+ }
853
+ return { ok: true };
854
+ }
855
+ async function checkAuthRateLimit(kind, email) {
856
+ return checkScoped(kind, "email", email, MAX_EMAIL_ATTEMPTS);
857
+ }
858
+ async function enforceAuthRateLimits(kind, ctx) {
859
+ if (ctx.email) {
860
+ const emailLimit = await checkScoped(
861
+ kind,
862
+ "email",
863
+ ctx.email,
864
+ MAX_EMAIL_ATTEMPTS
865
+ );
866
+ if (!emailLimit.ok) return emailLimit;
867
+ }
868
+ if (ctx.ip) {
869
+ const ipLimit = await checkScoped(kind, "ip", ctx.ip, MAX_IP_ATTEMPTS);
870
+ if (!ipLimit.ok) return ipLimit;
871
+ }
872
+ return { ok: true };
873
+ }
874
+ async function recordScoped(kind, bucket, identifier) {
875
+ const scope = scopeKey(kind, bucket, identifier);
876
+ const now = Date.now();
877
+ const row = await readScope(scope);
878
+ if (!row || now - row.window_start >= WINDOW_MS) {
879
+ await getDatabase().prepare(
880
+ `INSERT INTO auth_rate_limits (scope, attempts, window_start)
881
+ VALUES (?, 1, ?)
882
+ ON CONFLICT(scope) DO UPDATE SET attempts = 1, window_start = excluded.window_start`
883
+ ).bind(scope, now).run();
884
+ return;
885
+ }
886
+ await getDatabase().prepare(
887
+ `UPDATE auth_rate_limits SET attempts = attempts + 1 WHERE scope = ?`
888
+ ).bind(scope).run();
889
+ }
890
+ async function recordAuthFailures(kind, ctx) {
891
+ if (ctx.email) await recordScoped(kind, "email", ctx.email);
892
+ if (ctx.ip) await recordScoped(kind, "ip", ctx.ip);
893
+ }
894
+ async function recordAuthFailure(kind, email) {
895
+ await recordScoped(kind, "email", email);
896
+ }
897
+ async function clearScoped(kind, bucket, identifier) {
898
+ await getDatabase().prepare(`DELETE FROM auth_rate_limits WHERE scope = ?`).bind(scopeKey(kind, bucket, identifier)).run();
899
+ }
900
+ async function clearAuthRateLimits(kind, ctx) {
901
+ if (ctx.email) await clearScoped(kind, "email", ctx.email);
902
+ if (ctx.ip) await clearScoped(kind, "ip", ctx.ip);
903
+ }
904
+ async function clearAuthRateLimit(kind, email) {
905
+ await clearScoped(kind, "email", email);
906
+ }
907
+
908
+ // src/auth/turnstile.ts
909
+ var SITEVERIFY_URL = "https://challenges.cloudflare.com/turnstile/v0/siteverify";
910
+ async function getTurnstileRuntimeConfig() {
911
+ const pub = await getTurnstilePublicConfig();
912
+ const secretKey = workerEnv.TURNSTILE_SECRET_KEY?.trim();
913
+ if (!pub.enabled || !pub.siteKey || !secretKey) return null;
914
+ return { enabled: true, siteKey: pub.siteKey, secretKey };
915
+ }
916
+ async function verifyTurnstileToken(token, remoteIp) {
917
+ const config = await getTurnstileRuntimeConfig();
918
+ if (!config) {
919
+ console.error("[DEBUG-TURNSTILE] runtime config missing", {
920
+ tokenLength: token.length,
921
+ remoteIpPresent: Boolean(remoteIp)
922
+ });
923
+ return { ok: true };
924
+ }
925
+ if (!token) {
926
+ console.error("[DEBUG-TURNSTILE] missing token", {
927
+ siteKeySuffix: config.siteKey.slice(-6),
928
+ remoteIpPresent: Boolean(remoteIp)
929
+ });
930
+ return { ok: false, codes: ["missing-input-response"] };
931
+ }
932
+ const body = new URLSearchParams();
933
+ body.set("secret", config.secretKey);
934
+ body.set("response", token);
935
+ if (remoteIp) body.set("remoteip", remoteIp);
936
+ const res = await fetch(SITEVERIFY_URL, {
937
+ method: "POST",
938
+ headers: { "content-type": "application/x-www-form-urlencoded" },
939
+ body
940
+ });
941
+ if (!res.ok) {
942
+ console.error("[DEBUG-TURNSTILE] siteverify http error", {
943
+ siteKeySuffix: config.siteKey.slice(-6),
944
+ tokenLength: token.length,
945
+ status: res.status
946
+ });
947
+ return { ok: false, codes: [`http-${res.status}`] };
948
+ }
949
+ const data = await res.json();
950
+ if (data.success) return { ok: true };
951
+ console.error("[DEBUG-TURNSTILE] siteverify failed", {
952
+ siteKeySuffix: config.siteKey.slice(-6),
953
+ tokenLength: token.length,
954
+ codes: data["error-codes"] ?? ["verification-failed"]
955
+ });
956
+ return { ok: false, codes: data["error-codes"] ?? ["verification-failed"] };
957
+ }
958
+ async function verifyTurnstileFromForm(formData, remoteIp) {
959
+ const token = String(formData.get("cf-turnstile-response") ?? "").trim();
960
+ console.error("[DEBUG-TURNSTILE] form token read", {
961
+ tokenLength: token.length,
962
+ remoteIpPresent: Boolean(remoteIp)
963
+ });
964
+ const result = await verifyTurnstileToken(token, remoteIp);
965
+ return result.ok ? { ok: true } : { ok: false };
966
+ }
967
+ export {
968
+ ADMIN_COOKIE,
969
+ USER_COOKIE,
970
+ authenticateEmailUser,
971
+ changeUserPassword,
972
+ checkAuthRateLimit,
973
+ checkPassword,
974
+ clearAuthRateLimit,
975
+ clearAuthRateLimits,
976
+ clearSessionCookie,
977
+ clearUserSessionCookie,
978
+ createAuth,
979
+ createEmailUser,
980
+ deleteUserAccount,
981
+ enforceAuthRateLimits,
982
+ getAuthViewer,
983
+ getCurrentUser,
984
+ getTurnstileRuntimeConfig,
985
+ getUserByEmail,
986
+ getUserById,
987
+ hashPassword,
988
+ isAuthenticated,
989
+ issuePasswordResetToken,
990
+ issueVerificationToken,
991
+ listUsers,
992
+ listUsersWithPostCounts,
993
+ normalizeUserRole,
994
+ recordAuthFailure,
995
+ recordAuthFailures,
996
+ resetPasswordWithToken,
997
+ revokeUserSessions,
998
+ setSessionCookie,
999
+ setUserRole,
1000
+ setUserSessionCookie,
1001
+ signUserToken,
1002
+ upsertGoogleUser,
1003
+ userToSession,
1004
+ validatePasswordStrength,
1005
+ verifyEmailUser,
1006
+ verifyPassword,
1007
+ verifyTurnstileFromForm,
1008
+ verifyTurnstileToken,
1009
+ verifyUserToken
1010
+ };
1011
+ //# sourceMappingURL=index.js.map