Accounts & favorites
- Sign-in: Google Identity Services button (
apps/web/src/components/GoogleSignIn.tsx) → ID token →POST /v1/account/auth/google. The API verifies issuer, audience (GOOGLE_CLIENT_ID), RS256 andemail_verifiedagainst Google's public keys (workers/api/src/modules/account/auth.ts, tested with forged/expired/wrong-audience tokens). - Session: opaque random token in an
HttpOnly; Secure; SameSite=Laxcookieal365_sessionscoped toCOOKIE_DOMAIN(artlove365.com) so the site andapi.share it. Only its SHA-256 is stored (sessions). 90 days. - CSRF: every non-GET
/v1/account/*call must come from anartlove365.comorigin (or localhost in dev). - Favorites: anonymous →
localStorage["artlove365.favorites"]; signed in →favoritestable. At sign-in the browser's list is merged into the account (POST /v1/account/favorites/merge), and the local copy is kept as a mirror for instant hearts. - No Client ID yet?
GOOGLE_CLIENT_IDis empty → sign-in UI is hidden, favorites still work locally. Setup: roadmap OPS-01.
Setting up the Google Client ID (OPS-01)
- Google Cloud Console → APIs & Services → Credentials → Create credentials → OAuth client ID → Web application.
- Authorized JavaScript origins:
https://artlove365.com,http://localhost:5173. - Copy the Client ID (it is public) into
GOOGLE_CLIENT_IDinworkers/api/wrangler.jsonc, push tomain.
Local dev: workers/api/.dev.vars must contain COOKIE_DOMAIN="" (http, host-only cookie).