@aurabase/aurabase-js 0.3.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 (344) hide show
  1. package/.changeset/config.json +11 -0
  2. package/CHANGELOG.md +30 -0
  3. package/LICENSE +21 -0
  4. package/README.md +833 -0
  5. package/package.json +90 -0
  6. package/packages/ai/LICENSE +21 -0
  7. package/packages/ai/README.md +106 -0
  8. package/packages/ai/dist/index.cjs +229 -0
  9. package/packages/ai/dist/index.d.cts +139 -0
  10. package/packages/ai/dist/index.d.ts +139 -0
  11. package/packages/ai/dist/index.js +226 -0
  12. package/packages/ai/node_modules/.bin/esbuild +19 -0
  13. package/packages/ai/node_modules/.bin/tsc +22 -0
  14. package/packages/ai/node_modules/.bin/tsserver +22 -0
  15. package/packages/ai/node_modules/.bin/tsup +22 -0
  16. package/packages/ai/node_modules/.bin/tsup-node +22 -0
  17. package/packages/ai/node_modules/.bin/vite +22 -0
  18. package/packages/ai/node_modules/.bin/vitest +22 -0
  19. package/packages/ai/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  20. package/packages/ai/package.json +61 -0
  21. package/packages/ai/src/AiService.ts +226 -0
  22. package/packages/ai/src/index.ts +18 -0
  23. package/packages/ai/src/sse.ts +141 -0
  24. package/packages/ai/src/types.ts +30 -0
  25. package/packages/ai/tests/chat-stream.test.ts +151 -0
  26. package/packages/ai/tests/rag-namespaces.test.ts +54 -0
  27. package/packages/ai/tests/sse.test.ts +69 -0
  28. package/packages/ai/tsconfig.json +5 -0
  29. package/packages/ai/tsup.config.ts +11 -0
  30. package/packages/aurabase/LICENSE +21 -0
  31. package/packages/aurabase/README.md +64 -0
  32. package/packages/aurabase/dist/AuraClient-DAtDbfPB.d.cts +77 -0
  33. package/packages/aurabase/dist/AuraClient-DAtDbfPB.d.ts +77 -0
  34. package/packages/aurabase/dist/index.cjs +237 -0
  35. package/packages/aurabase/dist/index.d.cts +46 -0
  36. package/packages/aurabase/dist/index.d.ts +46 -0
  37. package/packages/aurabase/dist/index.js +177 -0
  38. package/packages/aurabase/dist/ssr.cjs +224 -0
  39. package/packages/aurabase/dist/ssr.d.cts +65 -0
  40. package/packages/aurabase/dist/ssr.d.ts +65 -0
  41. package/packages/aurabase/dist/ssr.js +214 -0
  42. package/packages/aurabase/node_modules/.bin/esbuild +19 -0
  43. package/packages/aurabase/node_modules/.bin/tsc +22 -0
  44. package/packages/aurabase/node_modules/.bin/tsserver +22 -0
  45. package/packages/aurabase/node_modules/.bin/tsup +22 -0
  46. package/packages/aurabase/node_modules/.bin/tsup-node +22 -0
  47. package/packages/aurabase/node_modules/.bin/vite +22 -0
  48. package/packages/aurabase/node_modules/.bin/vitest +22 -0
  49. package/packages/aurabase/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  50. package/packages/aurabase/package.json +80 -0
  51. package/packages/aurabase/src/AuraClient.ts +237 -0
  52. package/packages/aurabase/src/index.ts +152 -0
  53. package/packages/aurabase/src/ssr.ts +156 -0
  54. package/packages/aurabase/tests/ai-streaming-rag-e2e.test.ts +82 -0
  55. package/packages/aurabase/tests/ai.test.ts +111 -0
  56. package/packages/aurabase/tests/auth-admin.test.ts +515 -0
  57. package/packages/aurabase/tests/auth-e2e.test.ts +135 -0
  58. package/packages/aurabase/tests/auth-hardening.test.ts +206 -0
  59. package/packages/aurabase/tests/auth.test.ts +947 -0
  60. package/packages/aurabase/tests/database.test.ts +230 -0
  61. package/packages/aurabase/tests/db-types-e2e.test.ts +217 -0
  62. package/packages/aurabase/tests/dispose.test.ts +23 -0
  63. package/packages/aurabase/tests/foreign-token.e2e.test.ts +129 -0
  64. package/packages/aurabase/tests/functions.test.ts +107 -0
  65. package/packages/aurabase/tests/integration.test.ts +413 -0
  66. package/packages/aurabase/tests/live-production-validation.js +241 -0
  67. package/packages/aurabase/tests/live-sdk-production-validation.js +293 -0
  68. package/packages/aurabase/tests/mocks.ts +139 -0
  69. package/packages/aurabase/tests/notifications.test.ts +314 -0
  70. package/packages/aurabase/tests/observability-backup.test.ts +186 -0
  71. package/packages/aurabase/tests/observability-hook.test.ts +130 -0
  72. package/packages/aurabase/tests/production-robustness.test.ts +268 -0
  73. package/packages/aurabase/tests/public-api-surface.test.ts +53 -0
  74. package/packages/aurabase/tests/query-builder.test.ts +329 -0
  75. package/packages/aurabase/tests/readme-smoke-e2e.test.ts +231 -0
  76. package/packages/aurabase/tests/realtime-filter-e2e.test.ts +344 -0
  77. package/packages/aurabase/tests/refresh-locks.test.ts +210 -0
  78. package/packages/aurabase/tests/retry-option.test.ts +17 -0
  79. package/packages/aurabase/tests/rpc-e2e.test.ts +254 -0
  80. package/packages/aurabase/tests/rs256-local-e2e.test.ts +114 -0
  81. package/packages/aurabase/tests/rt-alias-public-e2e.test.ts +235 -0
  82. package/packages/aurabase/tests/rt-http-broadcast-e2e.test.ts +124 -0
  83. package/packages/aurabase/tests/service-role-guard.test.ts +99 -0
  84. package/packages/aurabase/tests/ssr.test.ts +91 -0
  85. package/packages/aurabase/tests/storage-backend-e2e.test.ts +481 -0
  86. package/packages/aurabase/tests/storage-sdk-parity-e2e.test.ts +104 -0
  87. package/packages/aurabase/tests/storage-transform-e2e.test.ts +684 -0
  88. package/packages/aurabase/tests/storage.test.ts +55 -0
  89. package/packages/aurabase/tests/types/client-generic.test-d.ts +79 -0
  90. package/packages/aurabase/tests/types/docs-snippets.test-d.ts +571 -0
  91. package/packages/aurabase/tests/types/error-code.test-d.ts +53 -0
  92. package/packages/aurabase/tsconfig.json +5 -0
  93. package/packages/aurabase/tsconfig.test-d.json +5 -0
  94. package/packages/aurabase/tsup.config.ts +11 -0
  95. package/packages/aurabase/vitest.config.ts +16 -0
  96. package/packages/aurabase/vitest.e2e.config.ts +9 -0
  97. package/packages/auth/LICENSE +21 -0
  98. package/packages/auth/README.md +215 -0
  99. package/packages/auth/dist/index.cjs +1175 -0
  100. package/packages/auth/dist/index.d.cts +397 -0
  101. package/packages/auth/dist/index.d.ts +397 -0
  102. package/packages/auth/dist/index.js +1170 -0
  103. package/packages/auth/node_modules/.bin/esbuild +19 -0
  104. package/packages/auth/node_modules/.bin/tsc +22 -0
  105. package/packages/auth/node_modules/.bin/tsserver +22 -0
  106. package/packages/auth/node_modules/.bin/tsup +22 -0
  107. package/packages/auth/node_modules/.bin/tsup-node +22 -0
  108. package/packages/auth/node_modules/.bin/vite +22 -0
  109. package/packages/auth/node_modules/.bin/vitest +22 -0
  110. package/packages/auth/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  111. package/packages/auth/package.json +61 -0
  112. package/packages/auth/src/AuthAdminService.ts +149 -0
  113. package/packages/auth/src/AuthService.ts +1166 -0
  114. package/packages/auth/src/SessionManager.ts +92 -0
  115. package/packages/auth/src/index.ts +17 -0
  116. package/packages/auth/src/jwks.ts +337 -0
  117. package/packages/auth/src/pkce.ts +27 -0
  118. package/packages/auth/src/types.ts +89 -0
  119. package/packages/auth/tests/auth-lifecycle.test.ts +149 -0
  120. package/packages/auth/tests/detect-session.test.ts +361 -0
  121. package/packages/auth/tests/getclaims.test.ts +780 -0
  122. package/packages/auth/tests/is-mfa-challenge.test.ts +27 -0
  123. package/packages/auth/tests/password-recovery.test.ts +124 -0
  124. package/packages/auth/tests/pkce.test.ts +192 -0
  125. package/packages/auth/tests/session-manager.test.ts +103 -0
  126. package/packages/auth/tests/set-session.test.ts +139 -0
  127. package/packages/auth/tests/verify-otp.test.ts +153 -0
  128. package/packages/auth/tsconfig.json +5 -0
  129. package/packages/auth/tsup.config.ts +11 -0
  130. package/packages/core/LICENSE +21 -0
  131. package/packages/core/README.md +132 -0
  132. package/packages/core/dist/index.cjs +695 -0
  133. package/packages/core/dist/index.d.cts +1137 -0
  134. package/packages/core/dist/index.d.ts +1137 -0
  135. package/packages/core/dist/index.js +673 -0
  136. package/packages/core/node_modules/.bin/esbuild +22 -0
  137. package/packages/core/node_modules/.bin/tsc +22 -0
  138. package/packages/core/node_modules/.bin/tsserver +22 -0
  139. package/packages/core/node_modules/.bin/tsup +22 -0
  140. package/packages/core/node_modules/.bin/tsup-node +22 -0
  141. package/packages/core/node_modules/.bin/vite +22 -0
  142. package/packages/core/node_modules/.bin/vitest +22 -0
  143. package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  144. package/packages/core/package.json +57 -0
  145. package/packages/core/src/AuraError.ts +89 -0
  146. package/packages/core/src/BaseService.ts +44 -0
  147. package/packages/core/src/client.ts +34 -0
  148. package/packages/core/src/cookie-utils.ts +42 -0
  149. package/packages/core/src/fetch.ts +435 -0
  150. package/packages/core/src/index.ts +22 -0
  151. package/packages/core/src/retry.ts +63 -0
  152. package/packages/core/src/serialize.ts +6 -0
  153. package/packages/core/src/stores/BaseAuthStore.ts +164 -0
  154. package/packages/core/src/stores/LocalAuthStore.ts +199 -0
  155. package/packages/core/src/types.ts +1042 -0
  156. package/packages/core/tests/auth-store.test.ts +300 -0
  157. package/packages/core/tests/error-code.test.ts +61 -0
  158. package/packages/core/tests/error.test.ts +24 -0
  159. package/packages/core/tests/fetch.test.ts +286 -0
  160. package/packages/core/tests/mocks.ts +139 -0
  161. package/packages/core/tests/observability.test.ts +86 -0
  162. package/packages/core/tests/refresh-hardening.test.ts +164 -0
  163. package/packages/core/tests/refresh-observability.test.ts +111 -0
  164. package/packages/core/tests/retry.test.ts +106 -0
  165. package/packages/core/tests/serialize.test.ts +12 -0
  166. package/packages/core/tests/timeout.test.ts +79 -0
  167. package/packages/core/tsconfig.json +5 -0
  168. package/packages/core/tsup.config.ts +11 -0
  169. package/packages/db/LICENSE +21 -0
  170. package/packages/db/README.md +152 -0
  171. package/packages/db/VENDORED.md +93 -0
  172. package/packages/db/dist/index.cjs +544 -0
  173. package/packages/db/dist/index.d.cts +1111 -0
  174. package/packages/db/dist/index.d.ts +1111 -0
  175. package/packages/db/dist/index.js +541 -0
  176. package/packages/db/node_modules/.bin/esbuild +19 -0
  177. package/packages/db/node_modules/.bin/tsc +22 -0
  178. package/packages/db/node_modules/.bin/tsserver +22 -0
  179. package/packages/db/node_modules/.bin/tsup +22 -0
  180. package/packages/db/node_modules/.bin/tsup-node +22 -0
  181. package/packages/db/node_modules/.bin/vite +22 -0
  182. package/packages/db/node_modules/.bin/vitest +22 -0
  183. package/packages/db/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  184. package/packages/db/package.json +62 -0
  185. package/packages/db/src/DatabaseService.ts +170 -0
  186. package/packages/db/src/QueryBuilder.ts +830 -0
  187. package/packages/db/src/index.ts +2 -0
  188. package/packages/db/src/pgrst-types/common/common.ts +86 -0
  189. package/packages/db/src/pgrst-types/common/rpc.ts +151 -0
  190. package/packages/db/src/pgrst-types/feature-flags.ts +20 -0
  191. package/packages/db/src/pgrst-types/types.ts +134 -0
  192. package/packages/db/src/select-query-parser/parser.ts +479 -0
  193. package/packages/db/src/select-query-parser/result.ts +553 -0
  194. package/packages/db/src/select-query-parser/types.ts +132 -0
  195. package/packages/db/src/select-query-parser/utils.ts +707 -0
  196. package/packages/db/tests/filters.test.ts +81 -0
  197. package/packages/db/tests/mutations-filtered.e2e.test.ts +132 -0
  198. package/packages/db/tests/mutations-filtered.test.ts +188 -0
  199. package/packages/db/tests/order.test.ts +45 -0
  200. package/packages/db/tests/rpc.test.ts +41 -0
  201. package/packages/db/tests/scale/build-large-schema.ts +94 -0
  202. package/packages/db/tests/scale/scale-compile.test.ts +167 -0
  203. package/packages/db/tests/schema.test.ts +77 -0
  204. package/packages/db/tests/single-accept.e2e.test.ts +123 -0
  205. package/packages/db/tests/single.test.ts +211 -0
  206. package/packages/db/tests/types/filters.test-d.ts +52 -0
  207. package/packages/db/tests/types/fixtures.ts +40 -0
  208. package/packages/db/tests/types/from-views.test-d.ts +18 -0
  209. package/packages/db/tests/types/mutations.test-d.ts +23 -0
  210. package/packages/db/tests/types/rpc.test-d.ts +60 -0
  211. package/packages/db/tests/types/schema-shape.test-d.ts +8 -0
  212. package/packages/db/tests/types/select-inference.test-d.ts +38 -0
  213. package/packages/db/tests/vendored/fixtures.ts +83 -0
  214. package/packages/db/tests/vendored/get-result.test-d.ts +75 -0
  215. package/packages/db/tsconfig.json +5 -0
  216. package/packages/db/tsconfig.test-d.json +5 -0
  217. package/packages/db/tsup.config.ts +11 -0
  218. package/packages/db/vitest.config.ts +8 -0
  219. package/packages/functions/LICENSE +21 -0
  220. package/packages/functions/README.md +96 -0
  221. package/packages/functions/dist/index.cjs +173 -0
  222. package/packages/functions/dist/index.d.cts +81 -0
  223. package/packages/functions/dist/index.d.ts +81 -0
  224. package/packages/functions/dist/index.js +168 -0
  225. package/packages/functions/node_modules/.bin/esbuild +19 -0
  226. package/packages/functions/node_modules/.bin/tsc +22 -0
  227. package/packages/functions/node_modules/.bin/tsserver +22 -0
  228. package/packages/functions/node_modules/.bin/tsup +22 -0
  229. package/packages/functions/node_modules/.bin/tsup-node +22 -0
  230. package/packages/functions/node_modules/.bin/vite +22 -0
  231. package/packages/functions/node_modules/.bin/vitest +22 -0
  232. package/packages/functions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  233. package/packages/functions/package.json +60 -0
  234. package/packages/functions/src/FunctionsService.ts +188 -0
  235. package/packages/functions/src/errors.ts +70 -0
  236. package/packages/functions/src/index.ts +6 -0
  237. package/packages/functions/tests/invoke.test.ts +97 -0
  238. package/packages/functions/tests/response-type-errors.test.ts +135 -0
  239. package/packages/functions/tsconfig.json +5 -0
  240. package/packages/functions/tsup.config.ts +11 -0
  241. package/packages/gen/LICENSE +21 -0
  242. package/packages/gen/README.md +84 -0
  243. package/packages/gen/dist/cli.cjs +334 -0
  244. package/packages/gen/dist/cli.d.cts +1 -0
  245. package/packages/gen/dist/cli.d.ts +1 -0
  246. package/packages/gen/dist/cli.js +332 -0
  247. package/packages/gen/dist/index.cjs +257 -0
  248. package/packages/gen/dist/index.d.cts +12 -0
  249. package/packages/gen/dist/index.d.ts +12 -0
  250. package/packages/gen/dist/index.js +255 -0
  251. package/packages/gen/node_modules/.bin/esbuild +19 -0
  252. package/packages/gen/node_modules/.bin/tsc +22 -0
  253. package/packages/gen/node_modules/.bin/tsserver +22 -0
  254. package/packages/gen/node_modules/.bin/tsup +22 -0
  255. package/packages/gen/node_modules/.bin/tsup-node +22 -0
  256. package/packages/gen/node_modules/.bin/vite +22 -0
  257. package/packages/gen/node_modules/.bin/vitest +22 -0
  258. package/packages/gen/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  259. package/packages/gen/package.json +66 -0
  260. package/packages/gen/src/cli.ts +67 -0
  261. package/packages/gen/src/generate.ts +0 -0
  262. package/packages/gen/src/index.ts +2 -0
  263. package/packages/gen/tests/compile.test.ts +213 -0
  264. package/packages/gen/tests/e2e.test.ts +130 -0
  265. package/packages/gen/tests/generate.test.ts +396 -0
  266. package/packages/gen/tsconfig.json +5 -0
  267. package/packages/gen/tsup.config.ts +11 -0
  268. package/packages/notifications/LICENSE +21 -0
  269. package/packages/notifications/README.md +134 -0
  270. package/packages/notifications/dist/index.cjs +128 -0
  271. package/packages/notifications/dist/index.d.cts +81 -0
  272. package/packages/notifications/dist/index.d.ts +81 -0
  273. package/packages/notifications/dist/index.js +125 -0
  274. package/packages/notifications/node_modules/.bin/esbuild +19 -0
  275. package/packages/notifications/node_modules/.bin/tsc +22 -0
  276. package/packages/notifications/node_modules/.bin/tsserver +22 -0
  277. package/packages/notifications/node_modules/.bin/tsup +22 -0
  278. package/packages/notifications/node_modules/.bin/tsup-node +22 -0
  279. package/packages/notifications/node_modules/.bin/vite +22 -0
  280. package/packages/notifications/node_modules/.bin/vitest +22 -0
  281. package/packages/notifications/package.json +60 -0
  282. package/packages/notifications/src/NotificationsService.ts +230 -0
  283. package/packages/notifications/src/index.ts +19 -0
  284. package/packages/notifications/tsconfig.json +5 -0
  285. package/packages/notifications/tsup.config.ts +11 -0
  286. package/packages/realtime/LICENSE +21 -0
  287. package/packages/realtime/README.md +183 -0
  288. package/packages/realtime/dist/index.cjs +977 -0
  289. package/packages/realtime/dist/index.d.cts +259 -0
  290. package/packages/realtime/dist/index.d.ts +259 -0
  291. package/packages/realtime/dist/index.js +973 -0
  292. package/packages/realtime/node_modules/.bin/esbuild +19 -0
  293. package/packages/realtime/node_modules/.bin/tsc +22 -0
  294. package/packages/realtime/node_modules/.bin/tsserver +22 -0
  295. package/packages/realtime/node_modules/.bin/tsup +22 -0
  296. package/packages/realtime/node_modules/.bin/tsup-node +22 -0
  297. package/packages/realtime/node_modules/.bin/vite +22 -0
  298. package/packages/realtime/node_modules/.bin/vitest +22 -0
  299. package/packages/realtime/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  300. package/packages/realtime/package.json +60 -0
  301. package/packages/realtime/src/AuraChannel.ts +485 -0
  302. package/packages/realtime/src/HeartbeatManager.ts +73 -0
  303. package/packages/realtime/src/RealtimeService.ts +505 -0
  304. package/packages/realtime/src/columnFilter.ts +301 -0
  305. package/packages/realtime/src/index.ts +4 -0
  306. package/packages/realtime/tests/ack-routing.test.ts +81 -0
  307. package/packages/realtime/tests/column-filter.test.ts +144 -0
  308. package/packages/realtime/tests/dispatch.test.ts +191 -0
  309. package/packages/realtime/tests/heartbeat.test.ts +67 -0
  310. package/packages/realtime/tests/http-broadcast.test.ts +68 -0
  311. package/packages/realtime/tests/realtime-token.test.ts +43 -0
  312. package/packages/realtime/tests/reconnect-race.test.ts +149 -0
  313. package/packages/realtime/tests/ref-id.test.ts +34 -0
  314. package/packages/realtime/tests/set-auth.test.ts +70 -0
  315. package/packages/realtime/tsconfig.json +5 -0
  316. package/packages/realtime/tsup.config.ts +11 -0
  317. package/packages/storage/LICENSE +21 -0
  318. package/packages/storage/README.md +141 -0
  319. package/packages/storage/dist/index.cjs +333 -0
  320. package/packages/storage/dist/index.d.cts +185 -0
  321. package/packages/storage/dist/index.d.ts +185 -0
  322. package/packages/storage/dist/index.js +329 -0
  323. package/packages/storage/node_modules/.bin/esbuild +19 -0
  324. package/packages/storage/node_modules/.bin/tsc +22 -0
  325. package/packages/storage/node_modules/.bin/tsserver +22 -0
  326. package/packages/storage/node_modules/.bin/tsup +22 -0
  327. package/packages/storage/node_modules/.bin/tsup-node +22 -0
  328. package/packages/storage/node_modules/.bin/vite +22 -0
  329. package/packages/storage/node_modules/.bin/vitest +22 -0
  330. package/packages/storage/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
  331. package/packages/storage/package.json +60 -0
  332. package/packages/storage/src/DownloadBuilder.ts +60 -0
  333. package/packages/storage/src/StorageService.ts +323 -0
  334. package/packages/storage/src/index.ts +2 -0
  335. package/packages/storage/src/path.ts +35 -0
  336. package/packages/storage/src/transform.ts +15 -0
  337. package/packages/storage/tests/download.test.ts +64 -0
  338. package/packages/storage/tests/parity.test.ts +154 -0
  339. package/packages/storage/tests/path.test.ts +87 -0
  340. package/packages/storage/tests/transform.test.ts +79 -0
  341. package/packages/storage/tsconfig.json +5 -0
  342. package/packages/storage/tsup.config.ts +11 -0
  343. package/pnpm-workspace.yaml +24 -0
  344. package/tsconfig.base.json +15 -0
@@ -0,0 +1,149 @@
1
+ import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest'
2
+ import { AuthService } from '../src/AuthService'
3
+ import { BaseAuthStore } from '@aurabase/core'
4
+ import type { AuraClientLike, User } from '@aurabase/core'
5
+
6
+ // --- helpers inline (évite un mocks.ts dédié au package auth) ----------------
7
+ function b64url(obj: unknown): string {
8
+ return btoa(JSON.stringify(obj)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
9
+ }
10
+ function validJwt(): string {
11
+ const exp = Math.floor(Date.now() / 1000) + 3600
12
+ return `${b64url({ alg: 'HS256', typ: 'JWT' })}.${b64url({ sub: 'u1', exp })}.sig`
13
+ }
14
+ function fakeUser(): User {
15
+ return {
16
+ id: 'u1', email: 't@e.com', role: 'authenticated', email_verified: true,
17
+ is_banned: false, is_anonymous: false, mfa_enabled: false,
18
+ user_metadata: {}, app_metadata: {}, created_at: '2025-01-01T00:00:00Z',
19
+ }
20
+ }
21
+
22
+ // --- mock BroadcastChannel ---------------------------------------------------
23
+ class FakeBC {
24
+ static instances: FakeBC[] = []
25
+ onmessage: ((e: { data: unknown }) => void) | null = null
26
+ posted: unknown[] = []
27
+ closed = false
28
+ constructor(public name: string) { FakeBC.instances.push(this) }
29
+ postMessage(d: unknown) { this.posted.push(d) }
30
+ close() { this.closed = true }
31
+ }
32
+
33
+ beforeEach(() => {
34
+ FakeBC.instances = []
35
+ ;(globalThis as unknown as { BroadcastChannel: unknown }).BroadcastChannel = FakeBC
36
+ })
37
+ afterEach(() => {
38
+ vi.useRealTimers()
39
+ delete (globalThis as unknown as { BroadcastChannel?: unknown }).BroadcastChannel
40
+ })
41
+
42
+ function newAuth(store: BaseAuthStore) {
43
+ const client: AuraClientLike = {
44
+ baseUrl: 'http://x', projectId: 'p', apiKey: 'k',
45
+ fetchFn: (async () => new Response('{}', { status: 200 })) as unknown as typeof globalThis.fetch,
46
+ authStore: store,
47
+ auth: null as unknown as AuraClientLike['auth'],
48
+ }
49
+ const svc = new AuthService(client, { autoRefresh: true, persistSession: true, forceEnabled: true })
50
+ ;(client as { auth: unknown }).auth = svc
51
+ return { svc, client }
52
+ }
53
+
54
+ describe('AuthService — cycle de vie session', () => {
55
+ it('sign-in (save) démarre le ticker et broadcaste', () => {
56
+ vi.useFakeTimers()
57
+ const store = new BaseAuthStore()
58
+ const { svc } = newAuth(store)
59
+ store.save(validJwt(), 'rt', fakeUser())
60
+ expect((svc as unknown as { _sessionManager: { isActive: boolean } })._sessionManager.isActive).toBe(true)
61
+ expect(FakeBC.instances[0].posted.length).toBe(1)
62
+ })
63
+
64
+ it('sign-out (clear) arrête le ticker', () => {
65
+ const store = new BaseAuthStore()
66
+ const { svc } = newAuth(store)
67
+ store.save(validJwt(), 'rt', fakeUser())
68
+ store.clear()
69
+ expect((svc as unknown as { _sessionManager: { isActive: boolean } })._sessionManager.isActive).toBe(false)
70
+ })
71
+
72
+ it('réception d’un logout distant → SIGNED_OUT local + store vidé, sans rebroadcast', () => {
73
+ const store = new BaseAuthStore()
74
+ store.save(validJwt(), 'rt', fakeUser())
75
+ const { svc } = newAuth(store)
76
+ const events: string[] = []
77
+ svc.onAuthStateChange(({ event }) => events.push(event))
78
+ const bc = FakeBC.instances[0]
79
+ const before = bc.posted.length
80
+ bc.onmessage!({ data: null }) // null = signed out
81
+ expect(store.token).toBeNull()
82
+ expect(events).toContain('SIGNED_OUT')
83
+ expect(bc.posted.length).toBe(before) // pas de rebroadcast
84
+ })
85
+
86
+ it('dispose() coupe le ticker, ferme le canal, vide les listeners', async () => {
87
+ const store = new BaseAuthStore()
88
+ const { svc } = newAuth(store)
89
+ store.save(validJwt(), 'rt', fakeUser())
90
+ const cb = vi.fn()
91
+ svc.onAuthStateChange(cb)
92
+ svc.dispose()
93
+ expect((svc as unknown as { _sessionManager: { isActive: boolean } })._sessionManager.isActive).toBe(false)
94
+ expect(FakeBC.instances[0].closed).toBe(true)
95
+ await Promise.resolve() // flush le microtask INITIAL_SESSION
96
+ expect(cb).not.toHaveBeenCalled() // listeners vidés → aucun event
97
+ })
98
+
99
+ // --- Correctifs post-audit ---
100
+
101
+ it('F2 : onAuthStateChange APRÈS dispose() n’émet rien', async () => {
102
+ const store = new BaseAuthStore()
103
+ const { svc } = newAuth(store)
104
+ svc.dispose()
105
+ const cb = vi.fn()
106
+ svc.onAuthStateChange(cb) // abonnement après dispose
107
+ await Promise.resolve()
108
+ expect(cb).not.toHaveBeenCalled()
109
+ })
110
+
111
+ it('F3 : un user placeholder (sans id) n’est PAS broadcasté', () => {
112
+ const store = new BaseAuthStore()
113
+ newAuth(store)
114
+ const bc = FakeBC.instances[0]
115
+ store.save(validJwt(), 'rt', {} as User) // placeholder incomplet (setSession)
116
+ expect(bc.posted.length).toBe(0) // rien diffusé
117
+ store.save(validJwt(), 'rt', fakeUser()) // état complet
118
+ expect(bc.posted.length).toBe(1)
119
+ })
120
+
121
+ it('F4 : un message distant malformé est ignoré (store inchangé, aucun event)', () => {
122
+ const store = new BaseAuthStore()
123
+ store.save(validJwt(), 'rt', fakeUser())
124
+ const { svc } = newAuth(store)
125
+ const events: string[] = []
126
+ svc.onAuthStateChange(({ event }) => events.push(event))
127
+ const before = store.token
128
+ FakeBC.instances[0].onmessage!({ data: { foo: 1 } }) // malformé
129
+ FakeBC.instances[0].onmessage!({ data: { token: 123, refreshToken: 'x', user: {} } }) // token non-string
130
+ expect(store.token).toBe(before) // inchangé
131
+ expect(events.filter((e) => e !== 'INITIAL_SESSION')).toEqual([]) // aucun event applicatif
132
+ })
133
+
134
+ it('F6 : inférence d’événement distant (SIGNED_IN / TOKEN_REFRESHED / USER_UPDATED)', () => {
135
+ // Tokens explicitement distincts : _applyRemoteState compare des strings (pas de décodage JWT).
136
+ const store = new BaseAuthStore()
137
+ const { svc } = newAuth(store)
138
+ const events: string[] = []
139
+ svc.onAuthStateChange(({ event }) => { if (event !== 'INITIAL_SESSION') events.push(event) })
140
+ const bc = FakeBC.instances[0]
141
+ // 1) pas de session → login distant
142
+ bc.onmessage!({ data: { token: 'tokA', refreshToken: 'rt', user: fakeUser() } })
143
+ // 2) même token, user modifié → USER_UPDATED
144
+ bc.onmessage!({ data: { token: 'tokA', refreshToken: 'rt', user: { ...fakeUser(), email: 'new@e.com' } } })
145
+ // 3) token différent → TOKEN_REFRESHED
146
+ bc.onmessage!({ data: { token: 'tokB', refreshToken: 'rt2', user: fakeUser() } })
147
+ expect(events).toEqual(['SIGNED_IN', 'USER_UPDATED', 'TOKEN_REFRESHED'])
148
+ })
149
+ })
@@ -0,0 +1,361 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
2
+ import { AuthService } from '../src/AuthService'
3
+ import { BaseAuthStore, LocalAuthStore } from '@aurabase/core'
4
+ import type { AuraClientLike, User } from '@aurabase/core'
5
+
6
+ // --- helpers -----------------------------------------------------------------
7
+
8
+ function fakeUser(id = 'u1'): User {
9
+ return {
10
+ id,
11
+ email: 't@e.com',
12
+ role: 'authenticated',
13
+ email_verified: true,
14
+ is_banned: false,
15
+ is_anonymous: false,
16
+ mfa_enabled: false,
17
+ user_metadata: {},
18
+ app_metadata: {},
19
+ created_at: '2025-01-01T00:00:00Z',
20
+ }
21
+ }
22
+
23
+ class FakeBC {
24
+ static instances: FakeBC[] = []
25
+ onmessage: ((e: { data: unknown }) => void) | null = null
26
+ posted: unknown[] = []
27
+ closed = false
28
+ constructor(public name: string) {
29
+ FakeBC.instances.push(this)
30
+ }
31
+ postMessage(d: unknown) {
32
+ this.posted.push(d)
33
+ }
34
+ close() {
35
+ this.closed = true
36
+ }
37
+ }
38
+
39
+ function b64url(obj: unknown): string {
40
+ return btoa(JSON.stringify(obj)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
41
+ }
42
+ /** JWT non expiré (signature factice — non vérifiée localement en HS256). */
43
+ function jwt(sub = 'u1', ttlSec = 3600): string {
44
+ const exp = Math.floor(Date.now() / 1000) + ttlSec
45
+ return `${b64url({ alg: 'HS256', typ: 'JWT' })}.${b64url({ sub, exp })}.sig`
46
+ }
47
+
48
+ const VERIFIER_KEY = 'aura-proj-1-pkce-code-verifier'
49
+
50
+ beforeEach(() => {
51
+ FakeBC.instances = []
52
+ ;(globalThis as unknown as { BroadcastChannel: unknown }).BroadcastChannel = FakeBC
53
+ })
54
+ afterEach(() => {
55
+ vi.useRealTimers()
56
+ vi.restoreAllMocks()
57
+ delete (globalThis as unknown as { BroadcastChannel?: unknown }).BroadcastChannel
58
+ delete (globalThis as unknown as { window?: unknown }).window
59
+ delete (globalThis as unknown as { localStorage?: unknown }).localStorage
60
+ })
61
+
62
+ function makeAuth(
63
+ fetchFn: typeof globalThis.fetch = (async () =>
64
+ new Response('{}', { status: 200 })) as unknown as typeof globalThis.fetch,
65
+ store: BaseAuthStore = new BaseAuthStore(),
66
+ ) {
67
+ // Construit HORS navigateur (pas de window) → le constructeur ne déclenche pas
68
+ // _detectSessionInUrl ; on le pilote explicitement dans chaque test.
69
+ const client: AuraClientLike = {
70
+ baseUrl: 'http://detect.local',
71
+ projectId: 'proj-1',
72
+ apiKey: 'k',
73
+ fetchFn,
74
+ authStore: store,
75
+ auth: null as unknown as AuraClientLike['auth'],
76
+ }
77
+ const svc = new AuthService(client, { autoRefresh: false, persistSession: false })
78
+ ;(client as { auth: unknown }).auth = svc
79
+ return { svc, client, store }
80
+ }
81
+
82
+ function setWindow(opts: { hash?: string; search?: string; pathname?: string }) {
83
+ const replaceState = vi.fn()
84
+ ;(globalThis as unknown as { window: unknown }).window = {
85
+ location: {
86
+ hash: opts.hash ?? '',
87
+ search: opts.search ?? '',
88
+ pathname: opts.pathname ?? '/app',
89
+ },
90
+ history: { replaceState },
91
+ }
92
+ return replaceState
93
+ }
94
+
95
+ // --- detectSessionInUrl -------------------------------------------------------
96
+
97
+ const INITIATED_KEY = 'aura-proj-1-oauth-implicit-initiated'
98
+
99
+ describe('AuthService._detectSessionInUrl — callback OAuth au boot', () => {
100
+ it('fragment implicit (#access_token) AVEC marqueur d’initiation → setSession + URL nettoyée', async () => {
101
+ const store = new BaseAuthStore()
102
+ // SEC-F3 : le SDK a initié le flux (marqueur posé par signInWithOAuth).
103
+ store.setItem(INITIATED_KEY, '1')
104
+ const { svc } = makeAuth(undefined, store)
105
+ const setSession = vi
106
+ .spyOn(svc, 'setSession')
107
+ .mockResolvedValue({ data: fakeUser(), error: null })
108
+ const replaceState = setWindow({
109
+ hash: '#access_token=AT123&refresh_token=RT456&expires_in=3600&token_type=bearer',
110
+ search: '',
111
+ pathname: '/callback',
112
+ })
113
+
114
+ await svc._detectSessionInUrl()
115
+
116
+ expect(setSession).toHaveBeenCalledWith('AT123', 'RT456')
117
+ // Marqueur à usage unique : purgé après adoption.
118
+ expect(store.getItem(INITIATED_KEY)).toBeNull()
119
+ // URL nettoyée : replaceState appelé, plus aucun token dans l'URL cible.
120
+ expect(replaceState).toHaveBeenCalledTimes(1)
121
+ const cleaned = String(replaceState.mock.calls[0]![2])
122
+ expect(cleaned).not.toContain('access_token')
123
+ expect(cleaned).toBe('/callback')
124
+ })
125
+
126
+ it('query PKCE (?code) → exchangeCodeForSession(code) + code/state retirés de l’URL', async () => {
127
+ const { svc } = makeAuth()
128
+ const exchange = vi
129
+ .spyOn(svc, 'exchangeCodeForSession')
130
+ .mockResolvedValue({ data: null, error: null })
131
+ const replaceState = setWindow({
132
+ hash: '',
133
+ search: '?code=AUTHCODE&state=xyz&foo=bar',
134
+ pathname: '/auth/callback',
135
+ })
136
+
137
+ await svc._detectSessionInUrl()
138
+
139
+ expect(exchange).toHaveBeenCalledWith('AUTHCODE')
140
+ expect(replaceState).toHaveBeenCalledTimes(1)
141
+ const cleaned = String(replaceState.mock.calls[0]![2])
142
+ expect(cleaned).not.toContain('code=')
143
+ expect(cleaned).not.toContain('state=')
144
+ // Les autres query params sont préservés.
145
+ expect(cleaned).toContain('foo=bar')
146
+ })
147
+
148
+ it('URL sans token ni code → no-op (aucun setSession/exchange, aucun nettoyage)', async () => {
149
+ const { svc } = makeAuth()
150
+ const setSession = vi.spyOn(svc, 'setSession')
151
+ const exchange = vi.spyOn(svc, 'exchangeCodeForSession')
152
+ const replaceState = setWindow({ hash: '', search: '?foo=bar' })
153
+
154
+ await svc._detectSessionInUrl()
155
+
156
+ expect(setSession).not.toHaveBeenCalled()
157
+ expect(exchange).not.toHaveBeenCalled()
158
+ expect(replaceState).not.toHaveBeenCalled()
159
+ })
160
+
161
+ it('SEC-F3 : fragment implicit SANS marqueur d’initiation (injecté par un attaquant) → NON adopté (aucun setSession)', async () => {
162
+ const store = new BaseAuthStore() // pas de marqueur : flux NON initié par ce SDK
163
+ const { svc } = makeAuth(undefined, store)
164
+ const setSession = vi.spyOn(svc, 'setSession')
165
+ const replaceState = setWindow({
166
+ hash: '#access_token=ATTACKER&refresh_token=RTATT&token_type=bearer',
167
+ pathname: '/callback',
168
+ })
169
+
170
+ await svc._detectSessionInUrl()
171
+
172
+ // Session fixation évitée : le fragment forgé n'est jamais consommé.
173
+ expect(setSession).not.toHaveBeenCalled()
174
+ expect(replaceState).not.toHaveBeenCalled()
175
+ expect(store.token).toBeNull()
176
+ })
177
+
178
+ it('SEC-F3 : marqueur à usage unique — un 2e fragment (rejeu) après adoption n’est plus adopté', async () => {
179
+ const store = new BaseAuthStore()
180
+ store.setItem(INITIATED_KEY, '1')
181
+ const { svc } = makeAuth(undefined, store)
182
+ const setSession = vi
183
+ .spyOn(svc, 'setSession')
184
+ .mockResolvedValue({ data: fakeUser(), error: null })
185
+ setWindow({
186
+ hash: '#access_token=AT1&refresh_token=RT1&token_type=bearer',
187
+ pathname: '/cb',
188
+ })
189
+
190
+ await svc._detectSessionInUrl() // 1er passage : marqueur présent → adopté + purgé
191
+ expect(setSession).toHaveBeenCalledTimes(1)
192
+
193
+ await svc._detectSessionInUrl() // 2e passage : marqueur purgé → plus adopté
194
+ expect(setSession).toHaveBeenCalledTimes(1)
195
+ })
196
+
197
+ it('SEC-F3 : signInWithOAuth (flux implicit) POSE le marqueur d’initiation avant le redirect', async () => {
198
+ const store = new LocalAuthStore('aurabase_auth')
199
+ // localStorage minimal (persistant → survit au redirect simulé).
200
+ const mem = new Map<string, string>()
201
+ ;(globalThis as unknown as { localStorage: unknown }).localStorage = {
202
+ get length() {
203
+ return mem.size
204
+ },
205
+ key: (i: number) => Array.from(mem.keys())[i] ?? null,
206
+ getItem: (k: string) => (mem.has(k) ? mem.get(k)! : null),
207
+ setItem: (k: string, v: string) => void mem.set(k, v),
208
+ removeItem: (k: string) => void mem.delete(k),
209
+ clear: () => mem.clear(),
210
+ }
211
+ ;(globalThis as unknown as { window: unknown }).window = { location: { href: '' } }
212
+ const { svc } = makeAuth(undefined, store)
213
+
214
+ await svc.signInWithOAuth('google', { redirectTo: 'http://app/cb' })
215
+
216
+ // Marqueur posé (preuve d'initiation) — le fragment retour sera adoptable.
217
+ expect(store.getItem(INITIATED_KEY)).toBe('1')
218
+ })
219
+
220
+ it('SEC-F3 : flux PKCE (?code) reste adopté SANS marqueur implicit (lié au code_verifier)', async () => {
221
+ const store = new BaseAuthStore() // aucun marqueur implicit
222
+ const { svc } = makeAuth(undefined, store)
223
+ const exchange = vi
224
+ .spyOn(svc, 'exchangeCodeForSession')
225
+ .mockResolvedValue({ data: null, error: null })
226
+ setWindow({ hash: '', search: '?code=PKCECODE', pathname: '/cb' })
227
+
228
+ await svc._detectSessionInUrl()
229
+
230
+ // PKCE est déjà lié cryptographiquement au verifier → inchangé par SEC-F3.
231
+ expect(exchange).toHaveBeenCalledWith('PKCECODE')
232
+ })
233
+
234
+ it('hors navigateur (SSR/Node, pas de window) → no-op, ne throw pas', async () => {
235
+ const { svc } = makeAuth()
236
+ const setSession = vi.spyOn(svc, 'setSession')
237
+ const exchange = vi.spyOn(svc, 'exchangeCodeForSession')
238
+ // pas de window
239
+ await expect(svc._detectSessionInUrl()).resolves.toBeUndefined()
240
+ expect(setSession).not.toHaveBeenCalled()
241
+ expect(exchange).not.toHaveBeenCalled()
242
+ })
243
+
244
+ it('déclenché au boot en navigateur quand détecté (constructeur)', async () => {
245
+ // window AVANT construction → le constructeur appelle _detectSessionInUrl.
246
+ const at = jwt('u1')
247
+ setWindow({
248
+ hash: `#access_token=${at}&refresh_token=RT&token_type=bearer`,
249
+ pathname: '/cb',
250
+ })
251
+ const store = new BaseAuthStore()
252
+ // SEC-F3 : le flux a été initié par ce SDK (marqueur présent avant le retour OAuth).
253
+ store.setItem('aura-proj-1-oauth-implicit-initiated', '1')
254
+ let userCalls = 0
255
+ const fetchFn = (async (url: string) => {
256
+ if (String(url).includes('/user')) {
257
+ userCalls++
258
+ return new Response(JSON.stringify({ data: fakeUser() }), {
259
+ status: 200,
260
+ headers: { 'Content-Type': 'application/json' },
261
+ })
262
+ }
263
+ return new Response('{}', { status: 200 })
264
+ }) as unknown as typeof globalThis.fetch
265
+ const client: AuraClientLike = {
266
+ baseUrl: 'http://detect.local',
267
+ projectId: 'proj-1',
268
+ apiKey: 'k',
269
+ fetchFn,
270
+ authStore: store,
271
+ auth: null as unknown as AuraClientLike['auth'],
272
+ }
273
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
274
+ const svc = new AuthService(client, { autoRefresh: false, persistSession: true })
275
+ ;(client as { auth: unknown }).auth = svc
276
+ // Laisse le microtask/await du detect se dérouler.
277
+ await new Promise((r) => setTimeout(r, 0))
278
+ expect(userCalls).toBeGreaterThanOrEqual(1)
279
+ expect(store.token).toBe(at)
280
+ })
281
+ })
282
+
283
+ // --- Purge PKCE ---------------------------------------------------------------
284
+
285
+ describe('Purge du code_verifier PKCE', () => {
286
+ it('BaseAuthStore.clear() vide aussi le stockage clé/valeur (_kv)', () => {
287
+ const store = new BaseAuthStore()
288
+ store.setItem(VERIFIER_KEY, 'verifier-value')
289
+ store.setItem('other', 'x')
290
+ expect(store.getItem(VERIFIER_KEY)).toBe('verifier-value')
291
+
292
+ store.clear()
293
+
294
+ expect(store.getItem(VERIFIER_KEY)).toBeNull()
295
+ expect(store.getItem('other')).toBeNull()
296
+ })
297
+
298
+ it('signOut purge le code_verifier (via clear)', async () => {
299
+ const store = new BaseAuthStore()
300
+ const { svc } = makeAuth(undefined, store)
301
+ store.setItem(VERIFIER_KEY, 'verifier-value')
302
+
303
+ await svc.signOut()
304
+
305
+ expect(store.getItem(VERIFIER_KEY)).toBeNull()
306
+ })
307
+
308
+ it('exchangeCodeForSession en ÉCHEC purge quand même le verifier (usage unique)', async () => {
309
+ const store = new BaseAuthStore()
310
+ store.setItem(VERIFIER_KEY, 'verifier-value')
311
+ const fetchFn = (async (url: string) => {
312
+ if (String(url).includes('/oauth/pkce')) {
313
+ return new Response(JSON.stringify({ error: { code: 'invalid_grant', message: 'bad code' } }), {
314
+ status: 400,
315
+ headers: { 'Content-Type': 'application/json' },
316
+ })
317
+ }
318
+ return new Response('{}', { status: 200 })
319
+ }) as unknown as typeof globalThis.fetch
320
+ const { svc } = makeAuth(fetchFn, store)
321
+
322
+ const { error } = await svc.exchangeCodeForSession('auth-code')
323
+
324
+ expect(error).toBeTruthy()
325
+ // Purge inconditionnelle même à l'échec : plus de réutilisation possible.
326
+ expect(store.getItem(VERIFIER_KEY)).toBeNull()
327
+ })
328
+
329
+ it('LocalAuthStore.clear() supprime les clés PKCE de localStorage (multi-projets)', () => {
330
+ // localStorage minimal en mémoire.
331
+ const mem = new Map<string, string>()
332
+ ;(globalThis as unknown as { localStorage: unknown }).localStorage = {
333
+ get length() {
334
+ return mem.size
335
+ },
336
+ key: (i: number) => Array.from(mem.keys())[i] ?? null,
337
+ getItem: (k: string) => (mem.has(k) ? mem.get(k)! : null),
338
+ setItem: (k: string, v: string) => {
339
+ mem.set(k, v)
340
+ },
341
+ removeItem: (k: string) => {
342
+ mem.delete(k)
343
+ },
344
+ clear: () => mem.clear(),
345
+ }
346
+
347
+ const store = new LocalAuthStore('aurabase_auth')
348
+ store.setItem('aura-proj-1-pkce-code-verifier', 'v1')
349
+ store.setItem('aura-proj-2-pkce-code-verifier', 'v2')
350
+ store.setItem('unrelated-key', 'keep-me')
351
+ expect(mem.get('aura-proj-1-pkce-code-verifier')).toBe('v1')
352
+
353
+ store.clear()
354
+
355
+ expect(store.getItem('aura-proj-1-pkce-code-verifier')).toBeNull()
356
+ expect(mem.get('aura-proj-1-pkce-code-verifier')).toBeUndefined()
357
+ expect(mem.get('aura-proj-2-pkce-code-verifier')).toBeUndefined()
358
+ // Une clé non-PKCE n'est pas balayée par la purge.
359
+ expect(mem.get('unrelated-key')).toBe('keep-me')
360
+ })
361
+ })