feat(auth): anonymous sessions#851
Conversation
… health check (THU-383)
The 20-LOC information_schema columns query at the top of createAuth forced the function async, cascading awaits through 11 files (elysia plugin, index.ts, swagger.test.ts, and 8 auth/api test files). The schema-drift test in CI plus Postgres' "column does not exist" error on first anonymous sign-in already cover the deployment hazard the health check was guarding against (M3 spec external-4). Removing it lets createAuth stay sync, drops 11 mechanical await/Awaited<> edits, and lets swagger.test.ts go back to being DB-less.
…load Better Auth's M3 registration adds isAnonymous to additionalFields, so the session.user object already carries it. The Path 1 + PUT /upload re-fetches via getUserById were left over from when M4 ran in parallel against an M3 that hadn't yet exposed the field. Read user.isAnonymous directly off the session — eliminates two redundant DB round-trips per token refresh / upload. Path 2 (Bearer-only refresh, no session derive) still needs getUserById.
- Replace per-file mock.module of @/contexts with createMockAuthClient + createTestProvider so tests exercise real provider wiring. - Mock only single-export leaf modals (SignInModal, SyncSetupModal) and useIsMobile, per docs/development/testing.md. - Add isAnonymous to MockAuthClientOptions session shape.
- remove mock.module() calls for app hooks, contexts, and modals - rely on real implementations via createTestProvider + providers - aligns with docs/development/testing.md (no mocking shared modules)
- Collapse phase state machine into a single hasAttempted flag - Compute loading directly from observable state to avoid a render gap between mount and the post-commit effect firing - Add effect cleanup to ignore stale completions on unmount
raivieiraadriano92
left a comment
There was a problem hiding this comment.
left a few question to confirm the correct behavior
…Account - Drop the anonymous DAL (migration, row-cap, transient-error retry) - Reduce onLinkAccount to a single delete of the anonymous user row - Trim the auth integration test to cover the delete + fixation guard
Anonymous sessions have no real account to delete, so showing the Delete My Account action is misleading and the underlying flow would fail. Gate the section on !isAnonymous alongside isAuthenticated.
…tatus - replace hide-entirely branch with reusing the logged-out popover path - anonymous users now see the same sync affordance and Sign-In CTA as fully logged-out users, keeping the two states visually consistent
- relocate storage-event handler from use-powersync-credentials-invalid- listener.ts to AuthProvider so auth-token concerns live next to the rest of the auth lifecycle - add coverage for both branches (token rotated → reload; token cleared → dispatch powersync_credentials_invalid) - use the shared powersyncCredentialsInvalid event-name constant instead of repeating the string literal
- Sign In button already surfaces this affordance elsewhere - update tests to assert on the button instead of hint text
…add-anonymous-session-support-with-turnstile-bot-protection # Conflicts: # src/lib/auth-token.test.ts # src/lib/auth-token.ts
…ion tests - inject trackEvent/alias deps into createAnonymousPromotionAnalytics so unit tests pass fakes instead of spyOn-ing the posthog ESM namespace - dispatch real oauthRetryEvent via window in integration-completion tests instead of overwriting window.addEventListener (which leaked to sibling tests under --randomize --rerun-each)
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8c921b6. Configure here.
| if (user) { | ||
| if (user.isAnonymous) { | ||
| throw new ForbiddenError('Anonymous users cannot sync') | ||
| } |
There was a problem hiding this comment.
Anonymous user 403 causes PowerSync credential retry loop
Medium Severity
When the backend throws ForbiddenError for anonymous users hitting PowerSync endpoints, the safeErrorHandler returns { success: false, data: null, error: 'Forbidden' } with status 403 but no code field. The client-side getCredentialsInvalidReason in connector.ts only recognizes 403 with code: 'DEVICE_DISCONNECTED', so this 403 is treated as an unrecognized transient error — the connector returns null and PowerSync retries indefinitely. If a user previously had sync enabled and later gets an anonymous session (e.g., after signing out in bypass-waitlist mode), the connector will poll the token endpoint in a retry loop, generating repeated console errors and unnecessary network traffic.
Reviewed by Cursor Bugbot for commit 8c921b6. Configure here.


Note
Medium Risk
Touches authentication/session creation and PowerSync access control, plus a DB schema migration; mistakes could block logins or incorrectly allow/deny sync for some users. Changes are covered by new backend and frontend tests but still span critical identity flows.
Overview
Introduces anonymous user support end-to-end by adding
user.is_anonymous(migration + Drizzle schema), enabling Better Auth’sanonymousplugin, and exposingisAnonymousvia Better AuthadditionalFields.Adds guards and UX behavior around anonymous sessions: a new
AnonymousSessionGuardauto-creates an anonymous session when entering the app without one, while UI elements (e.g.SidebarFooter,PowerSyncStatus, Preferences sync toggle/account deletion) treat anonymous users as effectively logged out and prompt sign-in.Tightens sync security/capabilities by rejecting PowerSync
GET /powersync/token(both session and bearer refresh paths) andPUT /powersync/uploadfor anonymous users, and updates DALgetUserByIdto returnisAnonymousfor these checks.Adds promotion analytics + SSO bridge to alias anonymous IDs to real user IDs on successful OTP/SSO sign-in, plus cross-tab token-change handling via
onAuthTokenChangedInOtherTab; includes extensive new tests for anonymous auth wiring, PowerSync rejection, guard behavior, analytics, and improved event-listener testing reliability.Reviewed by Cursor Bugbot for commit 8c921b6. Bugbot is set up for automated code reviews on this repo. Configure here.