@edge-base/server 0.1.1

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 (309) hide show
  1. package/admin-build/.gitkeep +0 -0
  2. package/admin-build/_app/env.js +1 -0
  3. package/admin-build/_app/immutable/assets/0.Bm6cF078.css +1 -0
  4. package/admin-build/_app/immutable/assets/1.BfW3pUNa.css +1 -0
  5. package/admin-build/_app/immutable/assets/11.CVmQOewb.css +1 -0
  6. package/admin-build/_app/immutable/assets/12.B1EhbRZT.css +1 -0
  7. package/admin-build/_app/immutable/assets/13.BvwYeuwE.css +1 -0
  8. package/admin-build/_app/immutable/assets/14.CdVfcO0R.css +1 -0
  9. package/admin-build/_app/immutable/assets/15.2yeZ66b-.css +1 -0
  10. package/admin-build/_app/immutable/assets/17.BVg0JEVu.css +1 -0
  11. package/admin-build/_app/immutable/assets/18.Rwnl3x_i.css +1 -0
  12. package/admin-build/_app/immutable/assets/20.DsPWA9AV.css +1 -0
  13. package/admin-build/_app/immutable/assets/21.Dz2RJ56c.css +1 -0
  14. package/admin-build/_app/immutable/assets/22.DwNLk5Ai.css +1 -0
  15. package/admin-build/_app/immutable/assets/23.CFpu0gOO.css +1 -0
  16. package/admin-build/_app/immutable/assets/24.Cy5LBeoJ.css +1 -0
  17. package/admin-build/_app/immutable/assets/25.pUyLVf-h.css +1 -0
  18. package/admin-build/_app/immutable/assets/26.DBcGrlXa.css +1 -0
  19. package/admin-build/_app/immutable/assets/27.BswYyAJD.css +1 -0
  20. package/admin-build/_app/immutable/assets/28.B4ueB1Kf.css +1 -0
  21. package/admin-build/_app/immutable/assets/29.B-qU6PdF.css +1 -0
  22. package/admin-build/_app/immutable/assets/3.Dg81Pgmd.css +1 -0
  23. package/admin-build/_app/immutable/assets/30.CsdWum94.css +1 -0
  24. package/admin-build/_app/immutable/assets/31.U6OwIp50.css +1 -0
  25. package/admin-build/_app/immutable/assets/4.CyawCCux.css +1 -0
  26. package/admin-build/_app/immutable/assets/5.C0YO2HTk.css +1 -0
  27. package/admin-build/_app/immutable/assets/8.Br5jd6kD.css +1 -0
  28. package/admin-build/_app/immutable/assets/Badge.EMYLHBxE.css +1 -0
  29. package/admin-build/_app/immutable/assets/Button.DpzMRTjK.css +1 -0
  30. package/admin-build/_app/immutable/assets/ConfirmDialog.DAnaWRRk.css +1 -0
  31. package/admin-build/_app/immutable/assets/EmptyState.CwKsu57Y.css +1 -0
  32. package/admin-build/_app/immutable/assets/Input.BDUSenmU.css +1 -0
  33. package/admin-build/_app/immutable/assets/Modal.Dm5B0Xie.css +1 -0
  34. package/admin-build/_app/immutable/assets/PageShell.CmU-Xh-b.css +1 -0
  35. package/admin-build/_app/immutable/assets/SchemaFieldEditor.g4NsCdno.css +1 -0
  36. package/admin-build/_app/immutable/assets/Select.BW4Keufm.css +1 -0
  37. package/admin-build/_app/immutable/assets/Skeleton.KWUulTKJ.css +1 -0
  38. package/admin-build/_app/immutable/assets/Tabs.CniGYb67.css +1 -0
  39. package/admin-build/_app/immutable/assets/TimeChart.BTCDAvmT.css +1 -0
  40. package/admin-build/_app/immutable/assets/Toggle.Cy_K12OM.css +1 -0
  41. package/admin-build/_app/immutable/assets/TopList.ClFzmPlA.css +1 -0
  42. package/admin-build/_app/immutable/chunks/7B47DvSx.js +1 -0
  43. package/admin-build/_app/immutable/chunks/7f08Id8e.js +1 -0
  44. package/admin-build/_app/immutable/chunks/8wJeQ7LN.js +1 -0
  45. package/admin-build/_app/immutable/chunks/B-h2afW5.js +1 -0
  46. package/admin-build/_app/immutable/chunks/B8vJP3wz.js +1 -0
  47. package/admin-build/_app/immutable/chunks/BR_fL5Yv.js +1 -0
  48. package/admin-build/_app/immutable/chunks/BY92tFS2.js +1 -0
  49. package/admin-build/_app/immutable/chunks/BcR-Rdj9.js +1 -0
  50. package/admin-build/_app/immutable/chunks/BdrwyZv8.js +1 -0
  51. package/admin-build/_app/immutable/chunks/Bh56EfQ_.js +1 -0
  52. package/admin-build/_app/immutable/chunks/BkrCkgYp.js +1 -0
  53. package/admin-build/_app/immutable/chunks/BmRjiP5k.js +1 -0
  54. package/admin-build/_app/immutable/chunks/BsokvhWC.js +1 -0
  55. package/admin-build/_app/immutable/chunks/C4D51vTW.js +1 -0
  56. package/admin-build/_app/immutable/chunks/C6puvcoR.js +2 -0
  57. package/admin-build/_app/immutable/chunks/CCKNu7m7.js +1 -0
  58. package/admin-build/_app/immutable/chunks/CWj6FrbW.js +1 -0
  59. package/admin-build/_app/immutable/chunks/Ce-ngf4p.js +5 -0
  60. package/admin-build/_app/immutable/chunks/Cs0GwzJA.js +1 -0
  61. package/admin-build/_app/immutable/chunks/CwROoZK0.js +1 -0
  62. package/admin-build/_app/immutable/chunks/CxCPv_Ut.js +1 -0
  63. package/admin-build/_app/immutable/chunks/CxbRue-5.js +1 -0
  64. package/admin-build/_app/immutable/chunks/CyqB6g-D.js +1 -0
  65. package/admin-build/_app/immutable/chunks/D5h5A1cc.js +2 -0
  66. package/admin-build/_app/immutable/chunks/DnyL7Zq-.js +1 -0
  67. package/admin-build/_app/immutable/chunks/DoPXzH7F.js +1 -0
  68. package/admin-build/_app/immutable/chunks/DrQSgw-f.js +1 -0
  69. package/admin-build/_app/immutable/chunks/DttM2zNO.js +1 -0
  70. package/admin-build/_app/immutable/chunks/DuXuUBWN.js +1 -0
  71. package/admin-build/_app/immutable/chunks/MdeqaOQx.js +10 -0
  72. package/admin-build/_app/immutable/chunks/NuUjtcO2.js +1 -0
  73. package/admin-build/_app/immutable/chunks/Q2nPFxS6.js +1 -0
  74. package/admin-build/_app/immutable/chunks/R6arueIl.js +1 -0
  75. package/admin-build/_app/immutable/chunks/UUazaC_N.js +1 -0
  76. package/admin-build/_app/immutable/chunks/cOYbrQxx.js +1 -0
  77. package/admin-build/_app/immutable/chunks/eFQHTGwA.js +1 -0
  78. package/admin-build/_app/immutable/chunks/ehbppgYb.js +1 -0
  79. package/admin-build/_app/immutable/chunks/glwixJlP.js +1 -0
  80. package/admin-build/_app/immutable/chunks/vApWTCBs.js +1 -0
  81. package/admin-build/_app/immutable/chunks/w89G9Xpi.js +1 -0
  82. package/admin-build/_app/immutable/chunks/wJsUhbfZ.js +1 -0
  83. package/admin-build/_app/immutable/chunks/zfauFM8P.js +1 -0
  84. package/admin-build/_app/immutable/entry/app.CcO-Uos3.js +2 -0
  85. package/admin-build/_app/immutable/entry/start.COebYq3I.js +1 -0
  86. package/admin-build/_app/immutable/nodes/0.CjtHKU-6.js +1 -0
  87. package/admin-build/_app/immutable/nodes/1.DEisjlM0.js +1 -0
  88. package/admin-build/_app/immutable/nodes/10.CvhdyWVB.js +1 -0
  89. package/admin-build/_app/immutable/nodes/11.DjHqcOvy.js +1 -0
  90. package/admin-build/_app/immutable/nodes/12.mQLz4Mj_.js +1 -0
  91. package/admin-build/_app/immutable/nodes/13.CBonZZyP.js +110 -0
  92. package/admin-build/_app/immutable/nodes/14.d-oiZL0j.js +3 -0
  93. package/admin-build/_app/immutable/nodes/15.CKPQsUYF.js +1 -0
  94. package/admin-build/_app/immutable/nodes/16.wPzAPQGx.js +1 -0
  95. package/admin-build/_app/immutable/nodes/17.DayhKyEZ.js +1 -0
  96. package/admin-build/_app/immutable/nodes/18.DKwS0Ir0.js +1 -0
  97. package/admin-build/_app/immutable/nodes/19.wPzAPQGx.js +1 -0
  98. package/admin-build/_app/immutable/nodes/2.BKoKrw1i.js +1 -0
  99. package/admin-build/_app/immutable/nodes/20.BvIkkkrW.js +1 -0
  100. package/admin-build/_app/immutable/nodes/21.DMaFhdHk.js +128 -0
  101. package/admin-build/_app/immutable/nodes/22.3xdgwuK1.js +1 -0
  102. package/admin-build/_app/immutable/nodes/23.8Bvgjbsl.js +112 -0
  103. package/admin-build/_app/immutable/nodes/24.DzSSzRhG.js +2 -0
  104. package/admin-build/_app/immutable/nodes/25.9KKYBnAE.js +2 -0
  105. package/admin-build/_app/immutable/nodes/26.Bhn9dfhY.js +1 -0
  106. package/admin-build/_app/immutable/nodes/27.kRLiC24G.js +1 -0
  107. package/admin-build/_app/immutable/nodes/28.BVIN1-7N.js +1 -0
  108. package/admin-build/_app/immutable/nodes/29.3yabZWj4.js +1 -0
  109. package/admin-build/_app/immutable/nodes/3.BFtSOkX7.js +2 -0
  110. package/admin-build/_app/immutable/nodes/30.CyCQlwaP.js +1 -0
  111. package/admin-build/_app/immutable/nodes/31.C4LDXjES.js +1 -0
  112. package/admin-build/_app/immutable/nodes/4.CvbiMlCa.js +1 -0
  113. package/admin-build/_app/immutable/nodes/5.C6BLv2eM.js +1 -0
  114. package/admin-build/_app/immutable/nodes/6.BcXvfl2P.js +1 -0
  115. package/admin-build/_app/immutable/nodes/7.CIuqhPiK.js +1 -0
  116. package/admin-build/_app/immutable/nodes/8.BQOR_JfO.js +1 -0
  117. package/admin-build/_app/immutable/nodes/9.NZqXQxPy.js +1 -0
  118. package/admin-build/_app/version.json +1 -0
  119. package/admin-build/favicon.svg +26 -0
  120. package/admin-build/index.html +45 -0
  121. package/openapi.json +19543 -0
  122. package/package.json +66 -0
  123. package/src/__tests__/admin-assets.test.ts +55 -0
  124. package/src/__tests__/admin-data-routes.test.ts +488 -0
  125. package/src/__tests__/admin-db-target.test.ts +103 -0
  126. package/src/__tests__/admin-routing.test.ts +31 -0
  127. package/src/__tests__/admin-user-management.test.ts +311 -0
  128. package/src/__tests__/analytics-query.test.ts +75 -0
  129. package/src/__tests__/auth-d1.test.ts +749 -0
  130. package/src/__tests__/auth-db-adapter.test.ts +73 -0
  131. package/src/__tests__/auth-jwt.test.ts +440 -0
  132. package/src/__tests__/auth-oauth.test.ts +389 -0
  133. package/src/__tests__/auth-password.test.ts +367 -0
  134. package/src/__tests__/auth-redirect.test.ts +87 -0
  135. package/src/__tests__/backup-restore.test.ts +711 -0
  136. package/src/__tests__/broadcast.test.ts +128 -0
  137. package/src/__tests__/cli.test.ts +178 -0
  138. package/src/__tests__/cloudflare-realtime.test.ts +113 -0
  139. package/src/__tests__/config.test.ts +469 -0
  140. package/src/__tests__/cors.test.ts +154 -0
  141. package/src/__tests__/cron.test.ts +302 -0
  142. package/src/__tests__/d1-handler.test.ts +402 -0
  143. package/src/__tests__/d1-sql.test.ts +120 -0
  144. package/src/__tests__/database-live-config.test.ts +42 -0
  145. package/src/__tests__/database-live-emitter.test.ts +56 -0
  146. package/src/__tests__/database-live-filters.test.ts +63 -0
  147. package/src/__tests__/database-live-route.test.ts +113 -0
  148. package/src/__tests__/db-sql.test.ts +163 -0
  149. package/src/__tests__/do-lifecycle.test.ts +263 -0
  150. package/src/__tests__/do-router.test.ts +729 -0
  151. package/src/__tests__/email-provider.test.ts +128 -0
  152. package/src/__tests__/email-templates.test.ts +528 -0
  153. package/src/__tests__/error-format.test.ts +250 -0
  154. package/src/__tests__/field-ops.test.ts +242 -0
  155. package/src/__tests__/functions-context.test.ts +334 -0
  156. package/src/__tests__/functions-d1-proxy.test.ts +229 -0
  157. package/src/__tests__/functions-registry-runtime-config.test.ts +17 -0
  158. package/src/__tests__/functions-route.test.ts +139 -0
  159. package/src/__tests__/internal-request.test.ts +77 -0
  160. package/src/__tests__/log-writer.test.ts +44 -0
  161. package/src/__tests__/logger.test.ts +58 -0
  162. package/src/__tests__/meta-admin-proxy.test.ts +48 -0
  163. package/src/__tests__/meta-export-coverage.test.ts +191 -0
  164. package/src/__tests__/meta-route-registration.test.ts +47 -0
  165. package/src/__tests__/namespace-dump.test.ts +28 -0
  166. package/src/__tests__/oauth-providers.test.ts +337 -0
  167. package/src/__tests__/openapi-coverage.test.ts +144 -0
  168. package/src/__tests__/pagination.test.ts +59 -0
  169. package/src/__tests__/password-policy.test.ts +191 -0
  170. package/src/__tests__/plugin-migrations.test.ts +379 -0
  171. package/src/__tests__/postgres-batch-compat.test.ts +133 -0
  172. package/src/__tests__/postgres-dialect.test.ts +328 -0
  173. package/src/__tests__/postgres-executor.test.ts +79 -0
  174. package/src/__tests__/postgres-field-ops-compat.test.ts +222 -0
  175. package/src/__tests__/postgres-schema-init.test.ts +105 -0
  176. package/src/__tests__/postgres-table-utils.test.ts +107 -0
  177. package/src/__tests__/presence.test.ts +199 -0
  178. package/src/__tests__/provider.test.ts +550 -0
  179. package/src/__tests__/public-user-profile.test.ts +339 -0
  180. package/src/__tests__/push-handlers.test.ts +179 -0
  181. package/src/__tests__/push-provider.test.ts +80 -0
  182. package/src/__tests__/push-token.test.ts +418 -0
  183. package/src/__tests__/query.test.ts +771 -0
  184. package/src/__tests__/rate-limit.test.ts +260 -0
  185. package/src/__tests__/room-access-policy.test.ts +101 -0
  186. package/src/__tests__/room-handler-context.test.ts +130 -0
  187. package/src/__tests__/room-monitoring.test.ts +138 -0
  188. package/src/__tests__/room-runtime-routing.test.ts +222 -0
  189. package/src/__tests__/room.test.ts +254 -0
  190. package/src/__tests__/route-parser.test.ts +490 -0
  191. package/src/__tests__/rules.test.ts +234 -0
  192. package/src/__tests__/runtime-surface-accounting.test.ts +120 -0
  193. package/src/__tests__/scheduled.test.ts +80 -0
  194. package/src/__tests__/schema.test.ts +1273 -0
  195. package/src/__tests__/security-hardening.test.ts +312 -0
  196. package/src/__tests__/server.unit.test.ts +333 -0
  197. package/src/__tests__/service-key-db-proxy.test.ts +650 -0
  198. package/src/__tests__/service-key-provider-bypass.test.ts +138 -0
  199. package/src/__tests__/service-key.test.ts +757 -0
  200. package/src/__tests__/smoke-skip-report.test.ts +72 -0
  201. package/src/__tests__/sms-provider.test.ts +39 -0
  202. package/src/__tests__/sql-route.test.ts +218 -0
  203. package/src/__tests__/storage-hook-context.test.ts +115 -0
  204. package/src/__tests__/totp.test.ts +200 -0
  205. package/src/__tests__/uuid.test.ts +144 -0
  206. package/src/__tests__/validation.test.ts +773 -0
  207. package/src/__tests__/websocket-pending.test.ts +163 -0
  208. package/src/_functions-registry.ts +51 -0
  209. package/src/bench-entry.ts +9 -0
  210. package/src/cloudflare-test.d.ts +1 -0
  211. package/src/durable-objects/auth-do.ts +49 -0
  212. package/src/durable-objects/database-do.ts +2240 -0
  213. package/src/durable-objects/database-live-do.ts +949 -0
  214. package/src/durable-objects/logs-do.ts +1200 -0
  215. package/src/durable-objects/room-runtime-base.ts +1604 -0
  216. package/src/durable-objects/rooms-do.ts +2191 -0
  217. package/src/generated-config.ts +6 -0
  218. package/src/index.ts +382 -0
  219. package/src/lib/admin-assets.ts +54 -0
  220. package/src/lib/admin-db-target.ts +301 -0
  221. package/src/lib/admin-routing.ts +35 -0
  222. package/src/lib/admin-user-management.ts +464 -0
  223. package/src/lib/analytics-adapter.ts +103 -0
  224. package/src/lib/analytics-query.ts +579 -0
  225. package/src/lib/auth-d1-service.ts +1193 -0
  226. package/src/lib/auth-d1.ts +1056 -0
  227. package/src/lib/auth-db-adapter.ts +289 -0
  228. package/src/lib/auth-redirect.ts +116 -0
  229. package/src/lib/cidr.ts +115 -0
  230. package/src/lib/client-ip.ts +51 -0
  231. package/src/lib/cloudflare-realtime.ts +251 -0
  232. package/src/lib/control-db.ts +36 -0
  233. package/src/lib/cron.ts +163 -0
  234. package/src/lib/d1-handler.ts +1425 -0
  235. package/src/lib/d1-schema-init.ts +255 -0
  236. package/src/lib/d1-sql.ts +33 -0
  237. package/src/lib/database-live-config.ts +24 -0
  238. package/src/lib/database-live-emitter.ts +111 -0
  239. package/src/lib/db-sql.ts +66 -0
  240. package/src/lib/do-retry.ts +36 -0
  241. package/src/lib/do-router.ts +270 -0
  242. package/src/lib/do-sql.ts +73 -0
  243. package/src/lib/email-provider.ts +379 -0
  244. package/src/lib/email-templates.ts +285 -0
  245. package/src/lib/email-translations.ts +422 -0
  246. package/src/lib/errors.ts +151 -0
  247. package/src/lib/functions.ts +2091 -0
  248. package/src/lib/hono.ts +56 -0
  249. package/src/lib/internal-request.ts +56 -0
  250. package/src/lib/jwt.ts +354 -0
  251. package/src/lib/log-writer.ts +272 -0
  252. package/src/lib/namespace-dump.ts +125 -0
  253. package/src/lib/oauth-providers.ts +1225 -0
  254. package/src/lib/op-parser.ts +99 -0
  255. package/src/lib/openapi.ts +146 -0
  256. package/src/lib/pagination.ts +19 -0
  257. package/src/lib/password-policy.ts +102 -0
  258. package/src/lib/password.ts +145 -0
  259. package/src/lib/plugin-migrations.ts +612 -0
  260. package/src/lib/postgres-executor.ts +203 -0
  261. package/src/lib/postgres-handler.ts +1102 -0
  262. package/src/lib/postgres-schema-init.ts +341 -0
  263. package/src/lib/postgres-table-utils.ts +87 -0
  264. package/src/lib/public-user-profile.ts +187 -0
  265. package/src/lib/push-provider.ts +409 -0
  266. package/src/lib/push-token.ts +294 -0
  267. package/src/lib/query-engine.ts +768 -0
  268. package/src/lib/room-monitoring.ts +97 -0
  269. package/src/lib/room-runtime.ts +14 -0
  270. package/src/lib/route-parser.ts +434 -0
  271. package/src/lib/schema.ts +538 -0
  272. package/src/lib/schemas.ts +152 -0
  273. package/src/lib/service-key.ts +419 -0
  274. package/src/lib/sms-provider.ts +230 -0
  275. package/src/lib/startup-config.ts +99 -0
  276. package/src/lib/totp.ts +242 -0
  277. package/src/lib/uuid.ts +87 -0
  278. package/src/lib/validation.ts +205 -0
  279. package/src/lib/version.ts +2 -0
  280. package/src/lib/websocket-pending.ts +40 -0
  281. package/src/middleware/auth.ts +169 -0
  282. package/src/middleware/captcha-verify.ts +217 -0
  283. package/src/middleware/cors.ts +159 -0
  284. package/src/middleware/error-handler.ts +54 -0
  285. package/src/middleware/internal-guard.ts +26 -0
  286. package/src/middleware/logger.ts +126 -0
  287. package/src/middleware/rate-limit.ts +283 -0
  288. package/src/middleware/rules.ts +475 -0
  289. package/src/routes/admin-auth.ts +447 -0
  290. package/src/routes/admin.ts +3501 -0
  291. package/src/routes/analytics-api.ts +290 -0
  292. package/src/routes/auth.ts +4222 -0
  293. package/src/routes/backup.ts +1466 -0
  294. package/src/routes/config.ts +53 -0
  295. package/src/routes/d1.ts +109 -0
  296. package/src/routes/database-live.ts +281 -0
  297. package/src/routes/functions.ts +155 -0
  298. package/src/routes/health.ts +32 -0
  299. package/src/routes/kv.ts +167 -0
  300. package/src/routes/oauth.ts +1055 -0
  301. package/src/routes/push.ts +1465 -0
  302. package/src/routes/room.ts +639 -0
  303. package/src/routes/schema-endpoint.ts +76 -0
  304. package/src/routes/sql.ts +176 -0
  305. package/src/routes/storage.ts +1674 -0
  306. package/src/routes/tables.ts +699 -0
  307. package/src/routes/users.ts +21 -0
  308. package/src/routes/vectorize.ts +372 -0
  309. package/src/types.ts +99 -0
@@ -0,0 +1,73 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ function mockPgClient() {
4
+ const connect = vi.fn().mockResolvedValue(undefined);
5
+ const query = vi.fn().mockResolvedValue({
6
+ fields: [],
7
+ rows: [],
8
+ rowCount: 0,
9
+ });
10
+ const end = vi.fn().mockResolvedValue(undefined);
11
+ const Client = vi.fn(() => ({
12
+ connect,
13
+ query,
14
+ end,
15
+ }));
16
+
17
+ vi.doMock('pg', () => ({ Client }));
18
+
19
+ return { Client, connect, query, end };
20
+ }
21
+
22
+ describe('auth db adapter', () => {
23
+ afterEach(() => {
24
+ vi.resetModules();
25
+ vi.clearAllMocks();
26
+ vi.doUnmock('pg');
27
+ });
28
+
29
+ it('resolves auth provider and custom connectionString from config by default', async () => {
30
+ const pg = mockPgClient();
31
+ const { resolveAuthDb } = await import('../lib/auth-db-adapter.js');
32
+
33
+ const db = resolveAuthDb({
34
+ EDGEBASE_CONFIG: {
35
+ auth: {
36
+ provider: 'postgres',
37
+ connectionString: 'AUTH_CUSTOM_URL',
38
+ },
39
+ },
40
+ AUTH_CUSTOM_URL: 'postgres://edgebase:test@localhost/auth-custom',
41
+ });
42
+
43
+ expect(db.dialect).toBe('postgres');
44
+ await db.query('SELECT 1');
45
+ expect(pg.Client).toHaveBeenCalledWith({
46
+ connectionString: 'postgres://edgebase:test@localhost/auth-custom',
47
+ });
48
+ });
49
+
50
+ it('explicit args still override config defaults', async () => {
51
+ const pg = mockPgClient();
52
+ const { resolveAuthDb } = await import('../lib/auth-db-adapter.js');
53
+
54
+ const db = resolveAuthDb(
55
+ {
56
+ EDGEBASE_CONFIG: {
57
+ auth: {
58
+ provider: 'd1',
59
+ },
60
+ },
61
+ AUTH_OVERRIDE_URL: 'postgres://edgebase:test@localhost/auth-override',
62
+ },
63
+ 'postgres',
64
+ 'AUTH_OVERRIDE_URL',
65
+ );
66
+
67
+ expect(db.dialect).toBe('postgres');
68
+ await db.query('SELECT 1');
69
+ expect(pg.Client).toHaveBeenCalledWith({
70
+ connectionString: 'postgres://edgebase:test@localhost/auth-override',
71
+ });
72
+ });
73
+ });
@@ -0,0 +1,440 @@
1
+ /**
2
+ * 서버 단위 테스트 — lib/jwt.ts
3
+ * 1-09 auth-jwt.test.ts — 60개
4
+ *
5
+ * 실행: cd packages/server && npx vitest run src/__tests__/auth-jwt.test.ts
6
+ *
7
+ * 테스트 대상:
8
+ * parseDuration / signAccessToken / signRefreshToken / signAdminAccessToken
9
+ * verifyToken / verifyAccessToken / verifyRefreshToken / verifyAdminToken
10
+ * verifyAdminRefreshToken / verifyRefreshTokenWithFallback / verifyAdminTokenWithFallback / verifyAdminRefreshTokenWithFallback
11
+ * decodeTokenUnsafe / TokenExpiredError / TokenInvalidError
12
+ */
13
+
14
+ import { describe, it, expect } from 'vitest';
15
+ import {
16
+ parseDuration,
17
+ signAccessToken,
18
+ signRefreshToken,
19
+ signAdminAccessToken,
20
+ signAdminRefreshToken,
21
+ verifyAccessToken,
22
+ verifyRefreshToken,
23
+ verifyAdminToken,
24
+ verifyAdminRefreshToken,
25
+ verifyRefreshTokenWithFallback,
26
+ verifyAdminRefreshTokenWithFallback,
27
+ decodeTokenUnsafe,
28
+ TokenExpiredError,
29
+ TokenInvalidError,
30
+ } from '../lib/jwt.js';
31
+
32
+ const SECRET = 'test-secret-for-unit-tests-must-be-long-enough';
33
+ const OTHER_SECRET = 'other-secret-for-rotation-must-be-long-enough-too';
34
+
35
+ // ─── A. parseDuration ────────────────────────────────────────────────────────
36
+
37
+ describe('parseDuration', () => {
38
+ it('parses seconds: 30s → 30', () => {
39
+ expect(parseDuration('30s')).toBe(30);
40
+ });
41
+
42
+ it('parses minutes: 15m → 900', () => {
43
+ expect(parseDuration('15m')).toBe(900);
44
+ });
45
+
46
+ it('parses hours: 1h → 3600', () => {
47
+ expect(parseDuration('1h')).toBe(3600);
48
+ });
49
+
50
+ it('parses days: 7d → 604800', () => {
51
+ expect(parseDuration('7d')).toBe(604800);
52
+ });
53
+
54
+ it('parses 28d → 2419200', () => {
55
+ expect(parseDuration('28d')).toBe(2419200);
56
+ });
57
+
58
+ it('throws on invalid format', () => {
59
+ expect(() => parseDuration('abc')).toThrow('Invalid duration');
60
+ });
61
+
62
+ it('throws on empty string', () => {
63
+ expect(() => parseDuration('')).toThrow('Invalid duration');
64
+ });
65
+
66
+ it('throws on no unit', () => {
67
+ expect(() => parseDuration('300')).toThrow('Invalid duration');
68
+ });
69
+
70
+ it('parses 0s → 0', () => {
71
+ expect(parseDuration('0s')).toBe(0);
72
+ });
73
+
74
+ it('parses large value: 365d', () => {
75
+ expect(parseDuration('365d')).toBe(365 * 86400);
76
+ });
77
+ });
78
+
79
+ // ─── B. signAccessToken / verifyAccessToken ───────────────────────────────────
80
+
81
+ describe('signAccessToken + verifyAccessToken', () => {
82
+ it('signs and verifies with correct secret', async () => {
83
+ const token = await signAccessToken({ sub: 'user-1' }, SECRET);
84
+ const payload = await verifyAccessToken(token, SECRET);
85
+ expect(payload.sub).toBe('user-1');
86
+ });
87
+
88
+ it('issuer is edgebase:user', async () => {
89
+ const token = await signAccessToken({ sub: 'user-1' }, SECRET);
90
+ const payload = await verifyAccessToken(token, SECRET);
91
+ expect(payload.iss).toBe('edgebase:user');
92
+ });
93
+
94
+ it('contains exp and iat', async () => {
95
+ const token = await signAccessToken({ sub: 'user-1' }, SECRET);
96
+ const payload = await verifyAccessToken(token, SECRET);
97
+ expect(payload.exp).toBeDefined();
98
+ expect(payload.iat).toBeDefined();
99
+ expect(payload.exp).toBeGreaterThan(payload.iat as number);
100
+ });
101
+
102
+ it('includes email claim', async () => {
103
+ const token = await signAccessToken({ sub: 'u1', email: 'test@example.com' }, SECRET);
104
+ const payload = await verifyAccessToken(token, SECRET);
105
+ expect(payload.email).toBe('test@example.com');
106
+ });
107
+
108
+ it('includes role claim', async () => {
109
+ const token = await signAccessToken({ sub: 'u1', role: 'admin' }, SECRET);
110
+ const payload = await verifyAccessToken(token, SECRET);
111
+ expect(payload.role).toBe('admin');
112
+ });
113
+
114
+ it('includes custom claims', async () => {
115
+ const token = await signAccessToken({ sub: 'u1', custom: { plan: 'pro' } }, SECRET);
116
+ const payload = await verifyAccessToken(token, SECRET);
117
+ expect((payload.custom as any)?.plan).toBe('pro');
118
+ });
119
+
120
+ it('wrong secret → TokenInvalidError', async () => {
121
+ const token = await signAccessToken({ sub: 'u1' }, SECRET);
122
+ await expect(verifyAccessToken(token, OTHER_SECRET)).rejects.toThrow(TokenInvalidError);
123
+ });
124
+
125
+ it('expired token → TokenExpiredError', async () => {
126
+ const token = await signAccessToken({ sub: 'u1' }, SECRET, '1s');
127
+ await new Promise((r) => setTimeout(r, 1100));
128
+ await expect(verifyAccessToken(token, SECRET)).rejects.toThrow(TokenExpiredError);
129
+ });
130
+
131
+ it('malformed token → TokenInvalidError', async () => {
132
+ await expect(verifyAccessToken('not.a.valid.jwt', SECRET)).rejects.toThrow(TokenInvalidError);
133
+ });
134
+
135
+ it('admin token rejected by verifyAccessToken (wrong issuer)', async () => {
136
+ const token = await signAdminAccessToken({ sub: 'admin-1' }, SECRET);
137
+ await expect(verifyAccessToken(token, SECRET)).rejects.toThrow(TokenInvalidError);
138
+ });
139
+ });
140
+
141
+ // ─── C. signRefreshToken / verifyRefreshToken ─────────────────────────────────
142
+
143
+ describe('signRefreshToken + verifyRefreshToken', () => {
144
+ it('signs and verifies refresh token', async () => {
145
+ const token = await signRefreshToken({ sub: 'user-1', type: 'refresh' }, SECRET);
146
+ const payload = await verifyRefreshToken(token, SECRET);
147
+ expect(payload.sub).toBe('user-1');
148
+ expect(payload.type).toBe('refresh');
149
+ });
150
+
151
+ it('type must be refresh', async () => {
152
+ // If token doesn't have type=refresh, verifyRefreshToken throws
153
+ const accessToken = await signAccessToken({ sub: 'u1' }, SECRET);
154
+ await expect(verifyRefreshToken(accessToken, SECRET)).rejects.toThrow(TokenInvalidError);
155
+ });
156
+
157
+ it('includes jti when provided', async () => {
158
+ const token = await signRefreshToken({ sub: 'u1', type: 'refresh', jti: 'session-abc' }, SECRET);
159
+ const payload = await verifyRefreshToken(token, SECRET);
160
+ expect(payload.jti).toBe('session-abc');
161
+ });
162
+
163
+ it('issuer is edgebase:user', async () => {
164
+ const token = await signRefreshToken({ sub: 'u1', type: 'refresh' }, SECRET);
165
+ const payload = await verifyRefreshToken(token, SECRET);
166
+ expect(payload.iss).toBe('edgebase:user');
167
+ });
168
+
169
+ it('wrong secret → TokenInvalidError', async () => {
170
+ const token = await signRefreshToken({ sub: 'u1', type: 'refresh' }, SECRET);
171
+ await expect(verifyRefreshToken(token, OTHER_SECRET)).rejects.toThrow(TokenInvalidError);
172
+ });
173
+
174
+ it('expired token → TokenExpiredError', async () => {
175
+ const token = await signRefreshToken({ sub: 'u1', type: 'refresh' }, SECRET, '1s');
176
+ await new Promise((r) => setTimeout(r, 1100));
177
+ await expect(verifyRefreshToken(token, SECRET)).rejects.toThrow(TokenExpiredError);
178
+ });
179
+ });
180
+
181
+ // ─── D. signAdminAccessToken / verifyAdminToken ─────────────────────────────────────
182
+
183
+ describe('signAdminAccessToken + verifyAdminToken', () => {
184
+ it('signs and verifies admin token', async () => {
185
+ const token = await signAdminAccessToken({ sub: 'admin-1' }, SECRET);
186
+ const payload = await verifyAdminToken(token, SECRET);
187
+ expect(payload.sub).toBe('admin-1');
188
+ });
189
+
190
+ it('issuer is edgebase:admin', async () => {
191
+ const token = await signAdminAccessToken({ sub: 'admin-1' }, SECRET);
192
+ const payload = await verifyAdminToken(token, SECRET);
193
+ expect(payload.iss).toBe('edgebase:admin');
194
+ });
195
+
196
+ it('user token rejected by verifyAdminToken (wrong issuer)', async () => {
197
+ const token = await signAccessToken({ sub: 'u1' }, SECRET);
198
+ await expect(verifyAdminToken(token, SECRET)).rejects.toThrow(TokenInvalidError);
199
+ });
200
+
201
+ it('wrong secret → TokenInvalidError', async () => {
202
+ const token = await signAdminAccessToken({ sub: 'a1' }, SECRET);
203
+ await expect(verifyAdminToken(token, OTHER_SECRET)).rejects.toThrow(TokenInvalidError);
204
+ });
205
+
206
+ it('expired admin token → TokenExpiredError', async () => {
207
+ const token = await signAdminAccessToken({ sub: 'a1' }, SECRET, '1s');
208
+ await new Promise((r) => setTimeout(r, 1100));
209
+ await expect(verifyAdminToken(token, SECRET)).rejects.toThrow(TokenExpiredError);
210
+ });
211
+ });
212
+
213
+ describe('signAdminRefreshToken + verifyAdminRefreshToken', () => {
214
+ it('signs and verifies admin refresh token', async () => {
215
+ const token = await signAdminRefreshToken({ sub: 'admin-1' }, SECRET);
216
+ const payload = await verifyAdminRefreshToken(token, SECRET);
217
+ expect(payload.sub).toBe('admin-1');
218
+ expect(payload.type).toBe('refresh');
219
+ });
220
+
221
+ it('rejects admin access token', async () => {
222
+ const token = await signAdminAccessToken({ sub: 'admin-1' }, SECRET);
223
+ await expect(verifyAdminRefreshToken(token, SECRET)).rejects.toThrow(TokenInvalidError);
224
+ });
225
+
226
+ it('expired admin refresh token → TokenExpiredError', async () => {
227
+ const token = await signAdminRefreshToken({ sub: 'admin-1' }, SECRET, '1s');
228
+ await new Promise((r) => setTimeout(r, 1100));
229
+ await expect(verifyAdminRefreshToken(token, SECRET)).rejects.toThrow(TokenExpiredError);
230
+ });
231
+ });
232
+
233
+ // ─── E. verifyRefreshTokenWithFallback ────────────────────────────────────────
234
+
235
+ describe('verifyRefreshTokenWithFallback', () => {
236
+ it('verifies with current secret normally', async () => {
237
+ const token = await signRefreshToken({ sub: 'u1', type: 'refresh' }, SECRET);
238
+ const payload = await verifyRefreshTokenWithFallback(token, SECRET, OTHER_SECRET, new Date().toISOString());
239
+ expect(payload.sub).toBe('u1');
240
+ });
241
+
242
+ it('falls back to old secret within 28d grace period', async () => {
243
+ // Sign with OTHER_SECRET (simulating old key)
244
+ const token = await signRefreshToken({ sub: 'u1', type: 'refresh' }, OTHER_SECRET);
245
+ const recentOldAt = new Date(Date.now() - 1000).toISOString(); // 1 second ago
246
+ // Verify with current=SECRET, fallback=OTHER_SECRET
247
+ const payload = await verifyRefreshTokenWithFallback(token, SECRET, OTHER_SECRET, recentOldAt);
248
+ expect(payload.sub).toBe('u1');
249
+ });
250
+
251
+ it('rejects old key beyond 28d grace period', async () => {
252
+ const token = await signRefreshToken({ sub: 'u1', type: 'refresh' }, OTHER_SECRET);
253
+ const expiredOldAt = new Date(Date.now() - 29 * 24 * 60 * 60 * 1000).toISOString();
254
+ await expect(
255
+ verifyRefreshTokenWithFallback(token, SECRET, OTHER_SECRET, expiredOldAt),
256
+ ).rejects.toThrowError();
257
+ });
258
+
259
+ it('expired token is not rescued by fallback (expiry takes priority)', async () => {
260
+ const token = await signRefreshToken({ sub: 'u1', type: 'refresh' }, SECRET, '1s');
261
+ await new Promise((r) => setTimeout(r, 1100));
262
+ await expect(verifyRefreshTokenWithFallback(token, SECRET)).rejects.toThrow(TokenExpiredError);
263
+ });
264
+
265
+ it('no old secret provided → throws on wrong key', async () => {
266
+ const token = await signRefreshToken({ sub: 'u1', type: 'refresh' }, OTHER_SECRET);
267
+ await expect(verifyRefreshTokenWithFallback(token, SECRET)).rejects.toThrowError();
268
+ });
269
+
270
+ it('oldAt missing → fallback not triggered', async () => {
271
+ const token = await signRefreshToken({ sub: 'u1', type: 'refresh' }, OTHER_SECRET);
272
+ await expect(verifyRefreshTokenWithFallback(token, SECRET, OTHER_SECRET)).rejects.toThrowError();
273
+ });
274
+ });
275
+
276
+ describe('verifyAdminRefreshTokenWithFallback', () => {
277
+ it('verifies with current secret normally', async () => {
278
+ const token = await signAdminRefreshToken({ sub: 'admin-1' }, SECRET);
279
+ const payload = await verifyAdminRefreshTokenWithFallback(
280
+ token,
281
+ SECRET,
282
+ OTHER_SECRET,
283
+ new Date().toISOString(),
284
+ );
285
+ expect(payload.sub).toBe('admin-1');
286
+ });
287
+
288
+ it('falls back to old secret within 28d grace period', async () => {
289
+ const token = await signAdminRefreshToken({ sub: 'admin-1' }, OTHER_SECRET);
290
+ const recentOldAt = new Date(Date.now() - 1000).toISOString();
291
+ const payload = await verifyAdminRefreshTokenWithFallback(
292
+ token,
293
+ SECRET,
294
+ OTHER_SECRET,
295
+ recentOldAt,
296
+ );
297
+ expect(payload.sub).toBe('admin-1');
298
+ });
299
+
300
+ it('rejects old key beyond 28d grace period', async () => {
301
+ const token = await signAdminRefreshToken({ sub: 'admin-1' }, OTHER_SECRET);
302
+ const expiredOldAt = new Date(Date.now() - 29 * 24 * 60 * 60 * 1000).toISOString();
303
+ await expect(
304
+ verifyAdminRefreshTokenWithFallback(token, SECRET, OTHER_SECRET, expiredOldAt),
305
+ ).rejects.toThrowError();
306
+ });
307
+ });
308
+
309
+ // ─── F. decodeTokenUnsafe ─────────────────────────────────────────────────────
310
+
311
+ describe('decodeTokenUnsafe', () => {
312
+ it('decodes valid JWT payload without verification', async () => {
313
+ const token = await signAccessToken({ sub: 'u1', email: 'x@test.com' }, SECRET);
314
+ const payload = decodeTokenUnsafe(token);
315
+ expect(payload?.sub).toBe('u1');
316
+ expect(payload?.email).toBe('x@test.com');
317
+ });
318
+
319
+ it('returns null for malformed token', () => {
320
+ expect(decodeTokenUnsafe('not.a.token')).toBeNull();
321
+ });
322
+
323
+ it('returns null for empty string', () => {
324
+ expect(decodeTokenUnsafe('')).toBeNull();
325
+ });
326
+
327
+ it('returns null for 2-part token', () => {
328
+ expect(decodeTokenUnsafe('header.payload')).toBeNull();
329
+ });
330
+
331
+ it('does not verify signature (returns payload even for wrong secret token)', async () => {
332
+ const token = await signAccessToken({ sub: 'u-unsafe' }, SECRET);
333
+ // decodeTokenUnsafe doesn't verify — should return payload
334
+ const payload = decodeTokenUnsafe(token);
335
+ expect(payload?.sub).toBe('u-unsafe');
336
+ });
337
+ });
338
+
339
+ // ─── G-1. Token Type Enforcement ──────────────────────────────────────────────
340
+
341
+ describe('token type enforcement', () => {
342
+ it('user refresh token rejected by verifyAccessToken', async () => {
343
+ const token = await signRefreshToken({ sub: 'user-1', type: 'refresh' }, SECRET);
344
+ await expect(verifyAccessToken(token, SECRET)).rejects.toThrow(TokenInvalidError);
345
+ });
346
+
347
+ it('admin refresh token rejected by verifyAdminToken', async () => {
348
+ const token = await signAdminRefreshToken({ sub: 'admin-1' }, SECRET);
349
+ await expect(verifyAdminToken(token, SECRET)).rejects.toThrow(TokenInvalidError);
350
+ });
351
+
352
+ it('admin access token accepted by verifyAdminToken', async () => {
353
+ const token = await signAdminAccessToken({ sub: 'admin-1' }, SECRET);
354
+ const payload = await verifyAdminToken(token, SECRET);
355
+ expect(payload.sub).toBe('admin-1');
356
+ expect(payload.type).toBe('access');
357
+ });
358
+
359
+ it('legacy admin token (no type field) accepted by verifyAdminToken', async () => {
360
+ // Simulate a legacy token without type by using jose directly.
361
+ const { SignJWT } = await import('jose');
362
+ const legacyToken = await new SignJWT({})
363
+ .setProtectedHeader({ alg: 'HS256' })
364
+ .setIssuedAt()
365
+ .setIssuer('edgebase:admin')
366
+ .setSubject('admin-legacy')
367
+ .setExpirationTime('1h')
368
+ .sign(new TextEncoder().encode(SECRET));
369
+
370
+ const payload = await verifyAdminToken(legacyToken, SECRET);
371
+ expect(payload.sub).toBe('admin-legacy');
372
+ expect(payload.type).toBeUndefined();
373
+ });
374
+
375
+ it('signAdminAccessToken includes type=access', async () => {
376
+ const token = await signAdminAccessToken({ sub: 'a1' }, SECRET);
377
+ const payload = decodeTokenUnsafe(token);
378
+ expect(payload?.type).toBe('access');
379
+ });
380
+
381
+ it('signAdminRefreshToken includes type=refresh', async () => {
382
+ const token = await signAdminRefreshToken({ sub: 'a1' }, SECRET);
383
+ const payload = decodeTokenUnsafe(token);
384
+ expect(payload?.type).toBe('refresh');
385
+ });
386
+
387
+ it('legacy admin token with 1h TTL (access-like) accepted', async () => {
388
+ const { SignJWT } = await import('jose');
389
+ const token = await new SignJWT({})
390
+ .setProtectedHeader({ alg: 'HS256' })
391
+ .setIssuedAt()
392
+ .setIssuer('edgebase:admin')
393
+ .setSubject('admin-legacy-access')
394
+ .setExpirationTime('1h')
395
+ .sign(new TextEncoder().encode(SECRET));
396
+
397
+ const payload = await verifyAdminToken(token, SECRET);
398
+ expect(payload.sub).toBe('admin-legacy-access');
399
+ });
400
+
401
+ it('legacy admin token with 28d TTL (refresh-like) rejected', async () => {
402
+ const { SignJWT } = await import('jose');
403
+ const token = await new SignJWT({})
404
+ .setProtectedHeader({ alg: 'HS256' })
405
+ .setIssuedAt()
406
+ .setIssuer('edgebase:admin')
407
+ .setSubject('admin-legacy-refresh')
408
+ .setExpirationTime('28d')
409
+ .sign(new TextEncoder().encode(SECRET));
410
+
411
+ await expect(verifyAdminToken(token, SECRET)).rejects.toThrow(TokenInvalidError);
412
+ });
413
+ });
414
+
415
+ // ─── G. TokenExpiredError / TokenInvalidError ─────────────────────────────────
416
+
417
+ describe('token error classes', () => {
418
+ it('TokenExpiredError extends Error', () => {
419
+ const err = new TokenExpiredError('expired');
420
+ expect(err).toBeInstanceOf(Error);
421
+ expect(err).toBeInstanceOf(TokenExpiredError);
422
+ expect(err.name).toBe('TokenExpiredError');
423
+ expect(err.message).toBe('expired');
424
+ });
425
+
426
+ it('TokenInvalidError extends Error', () => {
427
+ const err = new TokenInvalidError('invalid');
428
+ expect(err).toBeInstanceOf(Error);
429
+ expect(err).toBeInstanceOf(TokenInvalidError);
430
+ expect(err.name).toBe('TokenInvalidError');
431
+ expect(err.message).toBe('invalid');
432
+ });
433
+
434
+ it('TokenExpiredError and TokenInvalidError are distinct', () => {
435
+ const expired = new TokenExpiredError('e');
436
+ const invalid = new TokenInvalidError('i');
437
+ expect(expired).not.toBeInstanceOf(TokenInvalidError);
438
+ expect(invalid).not.toBeInstanceOf(TokenExpiredError);
439
+ });
440
+ });