@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.
- package/.changeset/config.json +11 -0
- package/CHANGELOG.md +30 -0
- package/LICENSE +21 -0
- package/README.md +833 -0
- package/package.json +90 -0
- package/packages/ai/LICENSE +21 -0
- package/packages/ai/README.md +106 -0
- package/packages/ai/dist/index.cjs +229 -0
- package/packages/ai/dist/index.d.cts +139 -0
- package/packages/ai/dist/index.d.ts +139 -0
- package/packages/ai/dist/index.js +226 -0
- package/packages/ai/node_modules/.bin/esbuild +19 -0
- package/packages/ai/node_modules/.bin/tsc +22 -0
- package/packages/ai/node_modules/.bin/tsserver +22 -0
- package/packages/ai/node_modules/.bin/tsup +22 -0
- package/packages/ai/node_modules/.bin/tsup-node +22 -0
- package/packages/ai/node_modules/.bin/vite +22 -0
- package/packages/ai/node_modules/.bin/vitest +22 -0
- package/packages/ai/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/ai/package.json +61 -0
- package/packages/ai/src/AiService.ts +226 -0
- package/packages/ai/src/index.ts +18 -0
- package/packages/ai/src/sse.ts +141 -0
- package/packages/ai/src/types.ts +30 -0
- package/packages/ai/tests/chat-stream.test.ts +151 -0
- package/packages/ai/tests/rag-namespaces.test.ts +54 -0
- package/packages/ai/tests/sse.test.ts +69 -0
- package/packages/ai/tsconfig.json +5 -0
- package/packages/ai/tsup.config.ts +11 -0
- package/packages/aurabase/LICENSE +21 -0
- package/packages/aurabase/README.md +64 -0
- package/packages/aurabase/dist/AuraClient-DAtDbfPB.d.cts +77 -0
- package/packages/aurabase/dist/AuraClient-DAtDbfPB.d.ts +77 -0
- package/packages/aurabase/dist/index.cjs +237 -0
- package/packages/aurabase/dist/index.d.cts +46 -0
- package/packages/aurabase/dist/index.d.ts +46 -0
- package/packages/aurabase/dist/index.js +177 -0
- package/packages/aurabase/dist/ssr.cjs +224 -0
- package/packages/aurabase/dist/ssr.d.cts +65 -0
- package/packages/aurabase/dist/ssr.d.ts +65 -0
- package/packages/aurabase/dist/ssr.js +214 -0
- package/packages/aurabase/node_modules/.bin/esbuild +19 -0
- package/packages/aurabase/node_modules/.bin/tsc +22 -0
- package/packages/aurabase/node_modules/.bin/tsserver +22 -0
- package/packages/aurabase/node_modules/.bin/tsup +22 -0
- package/packages/aurabase/node_modules/.bin/tsup-node +22 -0
- package/packages/aurabase/node_modules/.bin/vite +22 -0
- package/packages/aurabase/node_modules/.bin/vitest +22 -0
- package/packages/aurabase/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/aurabase/package.json +80 -0
- package/packages/aurabase/src/AuraClient.ts +237 -0
- package/packages/aurabase/src/index.ts +152 -0
- package/packages/aurabase/src/ssr.ts +156 -0
- package/packages/aurabase/tests/ai-streaming-rag-e2e.test.ts +82 -0
- package/packages/aurabase/tests/ai.test.ts +111 -0
- package/packages/aurabase/tests/auth-admin.test.ts +515 -0
- package/packages/aurabase/tests/auth-e2e.test.ts +135 -0
- package/packages/aurabase/tests/auth-hardening.test.ts +206 -0
- package/packages/aurabase/tests/auth.test.ts +947 -0
- package/packages/aurabase/tests/database.test.ts +230 -0
- package/packages/aurabase/tests/db-types-e2e.test.ts +217 -0
- package/packages/aurabase/tests/dispose.test.ts +23 -0
- package/packages/aurabase/tests/foreign-token.e2e.test.ts +129 -0
- package/packages/aurabase/tests/functions.test.ts +107 -0
- package/packages/aurabase/tests/integration.test.ts +413 -0
- package/packages/aurabase/tests/live-production-validation.js +241 -0
- package/packages/aurabase/tests/live-sdk-production-validation.js +293 -0
- package/packages/aurabase/tests/mocks.ts +139 -0
- package/packages/aurabase/tests/notifications.test.ts +314 -0
- package/packages/aurabase/tests/observability-backup.test.ts +186 -0
- package/packages/aurabase/tests/observability-hook.test.ts +130 -0
- package/packages/aurabase/tests/production-robustness.test.ts +268 -0
- package/packages/aurabase/tests/public-api-surface.test.ts +53 -0
- package/packages/aurabase/tests/query-builder.test.ts +329 -0
- package/packages/aurabase/tests/readme-smoke-e2e.test.ts +231 -0
- package/packages/aurabase/tests/realtime-filter-e2e.test.ts +344 -0
- package/packages/aurabase/tests/refresh-locks.test.ts +210 -0
- package/packages/aurabase/tests/retry-option.test.ts +17 -0
- package/packages/aurabase/tests/rpc-e2e.test.ts +254 -0
- package/packages/aurabase/tests/rs256-local-e2e.test.ts +114 -0
- package/packages/aurabase/tests/rt-alias-public-e2e.test.ts +235 -0
- package/packages/aurabase/tests/rt-http-broadcast-e2e.test.ts +124 -0
- package/packages/aurabase/tests/service-role-guard.test.ts +99 -0
- package/packages/aurabase/tests/ssr.test.ts +91 -0
- package/packages/aurabase/tests/storage-backend-e2e.test.ts +481 -0
- package/packages/aurabase/tests/storage-sdk-parity-e2e.test.ts +104 -0
- package/packages/aurabase/tests/storage-transform-e2e.test.ts +684 -0
- package/packages/aurabase/tests/storage.test.ts +55 -0
- package/packages/aurabase/tests/types/client-generic.test-d.ts +79 -0
- package/packages/aurabase/tests/types/docs-snippets.test-d.ts +571 -0
- package/packages/aurabase/tests/types/error-code.test-d.ts +53 -0
- package/packages/aurabase/tsconfig.json +5 -0
- package/packages/aurabase/tsconfig.test-d.json +5 -0
- package/packages/aurabase/tsup.config.ts +11 -0
- package/packages/aurabase/vitest.config.ts +16 -0
- package/packages/aurabase/vitest.e2e.config.ts +9 -0
- package/packages/auth/LICENSE +21 -0
- package/packages/auth/README.md +215 -0
- package/packages/auth/dist/index.cjs +1175 -0
- package/packages/auth/dist/index.d.cts +397 -0
- package/packages/auth/dist/index.d.ts +397 -0
- package/packages/auth/dist/index.js +1170 -0
- package/packages/auth/node_modules/.bin/esbuild +19 -0
- package/packages/auth/node_modules/.bin/tsc +22 -0
- package/packages/auth/node_modules/.bin/tsserver +22 -0
- package/packages/auth/node_modules/.bin/tsup +22 -0
- package/packages/auth/node_modules/.bin/tsup-node +22 -0
- package/packages/auth/node_modules/.bin/vite +22 -0
- package/packages/auth/node_modules/.bin/vitest +22 -0
- package/packages/auth/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/auth/package.json +61 -0
- package/packages/auth/src/AuthAdminService.ts +149 -0
- package/packages/auth/src/AuthService.ts +1166 -0
- package/packages/auth/src/SessionManager.ts +92 -0
- package/packages/auth/src/index.ts +17 -0
- package/packages/auth/src/jwks.ts +337 -0
- package/packages/auth/src/pkce.ts +27 -0
- package/packages/auth/src/types.ts +89 -0
- package/packages/auth/tests/auth-lifecycle.test.ts +149 -0
- package/packages/auth/tests/detect-session.test.ts +361 -0
- package/packages/auth/tests/getclaims.test.ts +780 -0
- package/packages/auth/tests/is-mfa-challenge.test.ts +27 -0
- package/packages/auth/tests/password-recovery.test.ts +124 -0
- package/packages/auth/tests/pkce.test.ts +192 -0
- package/packages/auth/tests/session-manager.test.ts +103 -0
- package/packages/auth/tests/set-session.test.ts +139 -0
- package/packages/auth/tests/verify-otp.test.ts +153 -0
- package/packages/auth/tsconfig.json +5 -0
- package/packages/auth/tsup.config.ts +11 -0
- package/packages/core/LICENSE +21 -0
- package/packages/core/README.md +132 -0
- package/packages/core/dist/index.cjs +695 -0
- package/packages/core/dist/index.d.cts +1137 -0
- package/packages/core/dist/index.d.ts +1137 -0
- package/packages/core/dist/index.js +673 -0
- package/packages/core/node_modules/.bin/esbuild +22 -0
- package/packages/core/node_modules/.bin/tsc +22 -0
- package/packages/core/node_modules/.bin/tsserver +22 -0
- package/packages/core/node_modules/.bin/tsup +22 -0
- package/packages/core/node_modules/.bin/tsup-node +22 -0
- package/packages/core/node_modules/.bin/vite +22 -0
- package/packages/core/node_modules/.bin/vitest +22 -0
- package/packages/core/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/core/package.json +57 -0
- package/packages/core/src/AuraError.ts +89 -0
- package/packages/core/src/BaseService.ts +44 -0
- package/packages/core/src/client.ts +34 -0
- package/packages/core/src/cookie-utils.ts +42 -0
- package/packages/core/src/fetch.ts +435 -0
- package/packages/core/src/index.ts +22 -0
- package/packages/core/src/retry.ts +63 -0
- package/packages/core/src/serialize.ts +6 -0
- package/packages/core/src/stores/BaseAuthStore.ts +164 -0
- package/packages/core/src/stores/LocalAuthStore.ts +199 -0
- package/packages/core/src/types.ts +1042 -0
- package/packages/core/tests/auth-store.test.ts +300 -0
- package/packages/core/tests/error-code.test.ts +61 -0
- package/packages/core/tests/error.test.ts +24 -0
- package/packages/core/tests/fetch.test.ts +286 -0
- package/packages/core/tests/mocks.ts +139 -0
- package/packages/core/tests/observability.test.ts +86 -0
- package/packages/core/tests/refresh-hardening.test.ts +164 -0
- package/packages/core/tests/refresh-observability.test.ts +111 -0
- package/packages/core/tests/retry.test.ts +106 -0
- package/packages/core/tests/serialize.test.ts +12 -0
- package/packages/core/tests/timeout.test.ts +79 -0
- package/packages/core/tsconfig.json +5 -0
- package/packages/core/tsup.config.ts +11 -0
- package/packages/db/LICENSE +21 -0
- package/packages/db/README.md +152 -0
- package/packages/db/VENDORED.md +93 -0
- package/packages/db/dist/index.cjs +544 -0
- package/packages/db/dist/index.d.cts +1111 -0
- package/packages/db/dist/index.d.ts +1111 -0
- package/packages/db/dist/index.js +541 -0
- package/packages/db/node_modules/.bin/esbuild +19 -0
- package/packages/db/node_modules/.bin/tsc +22 -0
- package/packages/db/node_modules/.bin/tsserver +22 -0
- package/packages/db/node_modules/.bin/tsup +22 -0
- package/packages/db/node_modules/.bin/tsup-node +22 -0
- package/packages/db/node_modules/.bin/vite +22 -0
- package/packages/db/node_modules/.bin/vitest +22 -0
- package/packages/db/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/db/package.json +62 -0
- package/packages/db/src/DatabaseService.ts +170 -0
- package/packages/db/src/QueryBuilder.ts +830 -0
- package/packages/db/src/index.ts +2 -0
- package/packages/db/src/pgrst-types/common/common.ts +86 -0
- package/packages/db/src/pgrst-types/common/rpc.ts +151 -0
- package/packages/db/src/pgrst-types/feature-flags.ts +20 -0
- package/packages/db/src/pgrst-types/types.ts +134 -0
- package/packages/db/src/select-query-parser/parser.ts +479 -0
- package/packages/db/src/select-query-parser/result.ts +553 -0
- package/packages/db/src/select-query-parser/types.ts +132 -0
- package/packages/db/src/select-query-parser/utils.ts +707 -0
- package/packages/db/tests/filters.test.ts +81 -0
- package/packages/db/tests/mutations-filtered.e2e.test.ts +132 -0
- package/packages/db/tests/mutations-filtered.test.ts +188 -0
- package/packages/db/tests/order.test.ts +45 -0
- package/packages/db/tests/rpc.test.ts +41 -0
- package/packages/db/tests/scale/build-large-schema.ts +94 -0
- package/packages/db/tests/scale/scale-compile.test.ts +167 -0
- package/packages/db/tests/schema.test.ts +77 -0
- package/packages/db/tests/single-accept.e2e.test.ts +123 -0
- package/packages/db/tests/single.test.ts +211 -0
- package/packages/db/tests/types/filters.test-d.ts +52 -0
- package/packages/db/tests/types/fixtures.ts +40 -0
- package/packages/db/tests/types/from-views.test-d.ts +18 -0
- package/packages/db/tests/types/mutations.test-d.ts +23 -0
- package/packages/db/tests/types/rpc.test-d.ts +60 -0
- package/packages/db/tests/types/schema-shape.test-d.ts +8 -0
- package/packages/db/tests/types/select-inference.test-d.ts +38 -0
- package/packages/db/tests/vendored/fixtures.ts +83 -0
- package/packages/db/tests/vendored/get-result.test-d.ts +75 -0
- package/packages/db/tsconfig.json +5 -0
- package/packages/db/tsconfig.test-d.json +5 -0
- package/packages/db/tsup.config.ts +11 -0
- package/packages/db/vitest.config.ts +8 -0
- package/packages/functions/LICENSE +21 -0
- package/packages/functions/README.md +96 -0
- package/packages/functions/dist/index.cjs +173 -0
- package/packages/functions/dist/index.d.cts +81 -0
- package/packages/functions/dist/index.d.ts +81 -0
- package/packages/functions/dist/index.js +168 -0
- package/packages/functions/node_modules/.bin/esbuild +19 -0
- package/packages/functions/node_modules/.bin/tsc +22 -0
- package/packages/functions/node_modules/.bin/tsserver +22 -0
- package/packages/functions/node_modules/.bin/tsup +22 -0
- package/packages/functions/node_modules/.bin/tsup-node +22 -0
- package/packages/functions/node_modules/.bin/vite +22 -0
- package/packages/functions/node_modules/.bin/vitest +22 -0
- package/packages/functions/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/functions/package.json +60 -0
- package/packages/functions/src/FunctionsService.ts +188 -0
- package/packages/functions/src/errors.ts +70 -0
- package/packages/functions/src/index.ts +6 -0
- package/packages/functions/tests/invoke.test.ts +97 -0
- package/packages/functions/tests/response-type-errors.test.ts +135 -0
- package/packages/functions/tsconfig.json +5 -0
- package/packages/functions/tsup.config.ts +11 -0
- package/packages/gen/LICENSE +21 -0
- package/packages/gen/README.md +84 -0
- package/packages/gen/dist/cli.cjs +334 -0
- package/packages/gen/dist/cli.d.cts +1 -0
- package/packages/gen/dist/cli.d.ts +1 -0
- package/packages/gen/dist/cli.js +332 -0
- package/packages/gen/dist/index.cjs +257 -0
- package/packages/gen/dist/index.d.cts +12 -0
- package/packages/gen/dist/index.d.ts +12 -0
- package/packages/gen/dist/index.js +255 -0
- package/packages/gen/node_modules/.bin/esbuild +19 -0
- package/packages/gen/node_modules/.bin/tsc +22 -0
- package/packages/gen/node_modules/.bin/tsserver +22 -0
- package/packages/gen/node_modules/.bin/tsup +22 -0
- package/packages/gen/node_modules/.bin/tsup-node +22 -0
- package/packages/gen/node_modules/.bin/vite +22 -0
- package/packages/gen/node_modules/.bin/vitest +22 -0
- package/packages/gen/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/gen/package.json +66 -0
- package/packages/gen/src/cli.ts +67 -0
- package/packages/gen/src/generate.ts +0 -0
- package/packages/gen/src/index.ts +2 -0
- package/packages/gen/tests/compile.test.ts +213 -0
- package/packages/gen/tests/e2e.test.ts +130 -0
- package/packages/gen/tests/generate.test.ts +396 -0
- package/packages/gen/tsconfig.json +5 -0
- package/packages/gen/tsup.config.ts +11 -0
- package/packages/notifications/LICENSE +21 -0
- package/packages/notifications/README.md +134 -0
- package/packages/notifications/dist/index.cjs +128 -0
- package/packages/notifications/dist/index.d.cts +81 -0
- package/packages/notifications/dist/index.d.ts +81 -0
- package/packages/notifications/dist/index.js +125 -0
- package/packages/notifications/node_modules/.bin/esbuild +19 -0
- package/packages/notifications/node_modules/.bin/tsc +22 -0
- package/packages/notifications/node_modules/.bin/tsserver +22 -0
- package/packages/notifications/node_modules/.bin/tsup +22 -0
- package/packages/notifications/node_modules/.bin/tsup-node +22 -0
- package/packages/notifications/node_modules/.bin/vite +22 -0
- package/packages/notifications/node_modules/.bin/vitest +22 -0
- package/packages/notifications/package.json +60 -0
- package/packages/notifications/src/NotificationsService.ts +230 -0
- package/packages/notifications/src/index.ts +19 -0
- package/packages/notifications/tsconfig.json +5 -0
- package/packages/notifications/tsup.config.ts +11 -0
- package/packages/realtime/LICENSE +21 -0
- package/packages/realtime/README.md +183 -0
- package/packages/realtime/dist/index.cjs +977 -0
- package/packages/realtime/dist/index.d.cts +259 -0
- package/packages/realtime/dist/index.d.ts +259 -0
- package/packages/realtime/dist/index.js +973 -0
- package/packages/realtime/node_modules/.bin/esbuild +19 -0
- package/packages/realtime/node_modules/.bin/tsc +22 -0
- package/packages/realtime/node_modules/.bin/tsserver +22 -0
- package/packages/realtime/node_modules/.bin/tsup +22 -0
- package/packages/realtime/node_modules/.bin/tsup-node +22 -0
- package/packages/realtime/node_modules/.bin/vite +22 -0
- package/packages/realtime/node_modules/.bin/vitest +22 -0
- package/packages/realtime/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/realtime/package.json +60 -0
- package/packages/realtime/src/AuraChannel.ts +485 -0
- package/packages/realtime/src/HeartbeatManager.ts +73 -0
- package/packages/realtime/src/RealtimeService.ts +505 -0
- package/packages/realtime/src/columnFilter.ts +301 -0
- package/packages/realtime/src/index.ts +4 -0
- package/packages/realtime/tests/ack-routing.test.ts +81 -0
- package/packages/realtime/tests/column-filter.test.ts +144 -0
- package/packages/realtime/tests/dispatch.test.ts +191 -0
- package/packages/realtime/tests/heartbeat.test.ts +67 -0
- package/packages/realtime/tests/http-broadcast.test.ts +68 -0
- package/packages/realtime/tests/realtime-token.test.ts +43 -0
- package/packages/realtime/tests/reconnect-race.test.ts +149 -0
- package/packages/realtime/tests/ref-id.test.ts +34 -0
- package/packages/realtime/tests/set-auth.test.ts +70 -0
- package/packages/realtime/tsconfig.json +5 -0
- package/packages/realtime/tsup.config.ts +11 -0
- package/packages/storage/LICENSE +21 -0
- package/packages/storage/README.md +141 -0
- package/packages/storage/dist/index.cjs +333 -0
- package/packages/storage/dist/index.d.cts +185 -0
- package/packages/storage/dist/index.d.ts +185 -0
- package/packages/storage/dist/index.js +329 -0
- package/packages/storage/node_modules/.bin/esbuild +19 -0
- package/packages/storage/node_modules/.bin/tsc +22 -0
- package/packages/storage/node_modules/.bin/tsserver +22 -0
- package/packages/storage/node_modules/.bin/tsup +22 -0
- package/packages/storage/node_modules/.bin/tsup-node +22 -0
- package/packages/storage/node_modules/.bin/vite +22 -0
- package/packages/storage/node_modules/.bin/vitest +22 -0
- package/packages/storage/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -0
- package/packages/storage/package.json +60 -0
- package/packages/storage/src/DownloadBuilder.ts +60 -0
- package/packages/storage/src/StorageService.ts +323 -0
- package/packages/storage/src/index.ts +2 -0
- package/packages/storage/src/path.ts +35 -0
- package/packages/storage/src/transform.ts +15 -0
- package/packages/storage/tests/download.test.ts +64 -0
- package/packages/storage/tests/parity.test.ts +154 -0
- package/packages/storage/tests/path.test.ts +87 -0
- package/packages/storage/tests/transform.test.ts +79 -0
- package/packages/storage/tsconfig.json +5 -0
- package/packages/storage/tsup.config.ts +11 -0
- package/pnpm-workspace.yaml +24 -0
- 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
|
+
})
|