Skip to content

fix(auth): server-set fallback for cross-subdomain cookies#336

Open
rabble wants to merge 1 commit into
mainfrom
fix/auth-server-set-cookie
Open

fix(auth): server-set fallback for cross-subdomain cookies#336
rabble wants to merge 1 commit into
mainfrom
fix/auth-server-set-cookie

Conversation

@rabble
Copy link
Copy Markdown
Member

@rabble rabble commented May 5, 2026

Summary

Logins on divine.video weren't carrying over to username.divine.video subdomains. Root cause: document.cookie writes silently fail in some browsers (Brave, Firefox ETP-Strict, Safari ITP edge cases) — the cookie looks set but the browser drops it.

  • Adds same-origin POST/DELETE /api/auth/persist-cookie to the Fastly Compute worker. Server emits Set-Cookie: …; Domain=.divine.video which browsers honor far more reliably than client-side writes.
  • Decouples useDivineSession.clearSession() from clearJwtCookie(). A JWT expiry on one subdomain no longer wipes the cross-subdomain cookie that other subdomains hydrate from. Only explicit logout (AccountSwitcher) clears it.
  • Adds diagnostic console.info in hydrateLoginFromCookie so the next failure mode is visible at a glance.

Test plan

  • vitest — 44 tests across crossSubdomainAuth.test.ts, authPersistCookie.test.ts, AccountSwitcher.test.tsx pass
  • Deploy to staging, log in on divine.video, confirm session appears on *.divine.video
  • Verify Brave + Firefox ETP-Strict (the failure modes that prompted this)
  • Verify explicit logout still clears cookie everywhere

🤖 Generated with Claude Code

Adds a same-origin POST/DELETE /api/auth/persist-cookie endpoint in the
divine-web Fastly Compute worker that emits Set-Cookie with
Domain=.divine.video. document.cookie writes can silently fail in some
privacy-hardened browsers (Brave, Firefox ETP-Strict, Safari ITP edge
cases); browsers handle response Set-Cookie far more reliably.

Decouples useDivineSession.clearSession() from clearJwtCookie() so that
automatic JWT expiry on one subdomain no longer wipes the cross-subdomain
cookie that backs every other subdomain's hydration. Only explicit user
logout (AccountSwitcher) clears the cookie now.

Adds diagnostic console.info in hydrateLoginFromCookie so the next
report makes the failure mode visible at a glance.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying divine-web with  Cloudflare Pages  Cloudflare Pages

Latest commit: eb67f29
Status: ✅  Deploy successful!
Preview URL: https://eb7609d3.divine-web.pages.dev
Branch Preview URL: https://fix-auth-server-set-cookie.divine-web.pages.dev

View logs

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

🚀 Preview Deployment

Property Value
Preview URL https://c927f17e.divine-web-fm8.pages.dev
Commit eb67f29
Branch fix/auth-server-set-cookie

Copy link
Copy Markdown
Contributor

@DSanich DSanich left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rabble I left a few small comments to improve the quality of the code, but overall everything is excellent and useful!

@@ -1 +1 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
import { describe, it, expect, vi, beforeEach, afterEach, type Mock } from 'vitest';

});
}

let fetchMock: ReturnType<typeof vi.fn>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ReturnType is a generic utility and must be given a type argument (for example ReturnType). As written, this is invalid in strict TypeScript and is easy to break silently if TS settings change. Please use ReturnType or Vitest’s Mock type for fetchMock.

Suggested change
let fetchMock: ReturnType<typeof vi.fn>;
let fetchMock: Mock;

*/
export function hydrateLoginFromCookie(): void {
const STORAGE_KEY = 'nostr:login';
const log = (...args: unknown[]) => console.info('[crossSubdomainAuth]', ...args);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These console.info calls will run for all users on every hydration path and may be noisy in production. Consider gating behind import.meta.env.DEV, a debug flag, or logging only once per session to keep diagnostics without polluting end-user consoles.

Suggested change
const log = (...args: unknown[]) => console.info('[crossSubdomainAuth]', ...args);
const log = (...args: unknown[]) => {
if (import.meta.env.DEV) console.info('[crossSubdomainAuth]', ...args);
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants