Login build step 1 — phone OTP + one session

Branch: universe-login. Plan: docs/LOGIN-PLAN.md. Status: built and tested, switched OFF.

What it does

  • /login page: mobile number → OTP box inside the same card (no popup) → navy + gold account card showing the student's own name, Cetking ID and real Lifetime CK Coins. Shared-computer tick box (session ends when the browser closes).
  • Signs in existing Cetking students only, using their existing permanent Person ID, Arena student ID and coin ledger. No new student, wallet or coins are ever created. (New-student registration is build step 2.)
  • One secure session per device: __Host-ck_session cookie (HttpOnly, Secure, SameSite=Lax), 30 days max / 7 days idle on personal devices, 12 hours on shared computers. Up to 5 devices; the 6th is told to log out on one (never silently dropped).
  • GET /api/auth/me (own profile only), POST /api/auth/logout (this device).
  • Home headline shows "'s Learning Universe" once login is switched on.

Safety built in

  • The browser's "OTP verified" is never trusted: the server re-checks every verification with MSG91 and requires the verified number to match.
  • Each MSG91 verification can open only one session (replay blocked).
  • Rate limits per IP and per number; if the limiter is down, nobody gets in (fails closed).
  • Pilot list: only the numbers in CK_IDENTITY_PILOT_PHONES can sign in. Checked only after the OTP is correct, so it never reveals anything about a number.
  • Cross-site requests blocked (same-origin check + custom header).
  • Database: new separate area ck_identity in Cetking Learn. Only the server can use it (no browser access; RLS on, no policies). It reads people/students/coin ledger and never writes them. The live WordPress login (ck_one_private) is not touched.
  • No OTPs, tokens, phone numbers or IPs are logged; the database stores only hashes.
  • "Logged in" is shown only after name and coins load for that same session (plan rule 6). A database problem shows "temporarily unavailable", never "0 coins" or "signed out".
  • While switched off, every page behaves exactly as before (home stays static).

Tests

  • npm test (in cetking-one/): 64 tests — phone formats, MSG91 confirmation, rate limits, pilot list, replay, device cap, cookie flags, cross-site blocking, logout, no false zeros.
  • supabase/tests/ck_identity_step1_test.sql: database functions on a throwaway local Postgres with fake data (login, replay, broken links, device cap, expiry, permissions). Never run it against a real project.
  • next build passes; / stays static while login is off.

How to switch it on for the pilot (preview only, live site untouched)

  1. Database (Claude, after Ravneet approves): apply supabase/migrations/20261004090000_ck_identity_step1.sql to Cetking Learn (suqcijtpfeaystltekfn). Additive only; rollback steps are at the bottom of the file.
  2. Vercel environment variables (ChatGPT, scoped to Preview for branch universe-login only — not Production):
    • CK_IDENTITY_ENABLED = true
    • CK_IDENTITY_SUPABASE_URL = https://suqcijtpfeaystltekfn.supabase.co
    • CK_IDENTITY_SUPABASE_SERVICE_KEY = Cetking Learn service-role key
    • CK_IDENTITY_SECRET = a new random value, 64 characters (generate fresh; never reuse)
    • MSG91_AUTH_KEY = the same MSG91 auth key the WordPress login uses
    • NEXT_PUBLIC_MSG91_WIDGET_ID and NEXT_PUBLIC_MSG91_TOKEN_AUTH = the same MSG91 OTP widget ID and public token the WordPress login uses
    • CK_IDENTITY_PILOT_PHONES = Ravneet's numbers, comma-separated
  3. MSG91: if the OTP widget restricts allowed domains, add the Vercel preview domain.
  4. Redeploy the universe-login preview, open /login on a phone, sign in with a pilot number.

Pilot checks (Ravneet, on a real phone)

  • OTP arrives; wrong code shows a clear message; Resend works after 60s; Change number works.
  • Card shows your real name and coins (same as Arena).
  • Home page shows "'s Learning Universe".
  • Log out works; signing in on a second device keeps the first signed in.
  • A non-pilot number gets the "private test" message after OTP.

Source: GitHub cetking-one/docs/LOGIN-STEP1.md. Edit the file there; this page updates on the next release.