MotorSignal-App/docs/qa-2026-10-01/website-auth-followup.md

42 lines
9.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Website authentication follow-up
Date: 2026-10-01. Scope: local website Firebase authentication and Group Ride Hub diagnosis. All MotorSignal API traffic in this follow-up used `127.0.0.1`; no request was sent to `api.motorsignal.app`. The isolated services used demo project `demo-motorsignal-web-qa`, website port 3101, Auth Emulator port 9199, Firestore Emulator port 8188, and token-verifying local fixture API port 8189. No production credentials or accounts were used.
## Source and isolated setup
The website client initializes Firebase in `website/lib/firebase-client.ts`. It now honors `NEXT_PUBLIC_FIREBASE_AUTH_EMULATOR_PORT` and `NEXT_PUBLIC_FIREBASE_FIRESTORE_EMULATOR_PORT`, defaulting to 9099 and 8088 for existing local workflows. The QA run set them to 9199 and 8188. Invalid port values fail before Firebase app initialization. The Auth and Firestore clients used the same `demo-motorsignal-web-qa` project. Firestore emulator rules were loaded from a local copy whose SHA-256 matched `firestore.rules` (`A731D6319B56D2A13960FCBE621004D448AE611DC5C5657FAB25F0EFBE302EC6`).
The local Next server ran on `127.0.0.1:3101` with `MOTORSIGNAL_API_BASE_URL=http://127.0.0.1:8189`; the explicit override prevented the code's default `https://api.motorsignal.app` destination from being used. Browser request capture showed auth/session and Firestore traffic on loopback. Google Identity Toolkit and static Google/Tailwind assets were separately contacted by the browser for the popup and page styling. No MotorSignal production API request occurred in this follow-up.
The local API fixture in `website/test-support/qa-auth-backend.mjs` uses Firebase Admin `verifyIdToken`, then checks token audience and issuer against the demo project before issuing opaque in-memory QA session values. Its session endpoint validates those values. It does not implement MotorSignal account/profile or ride APIs and is not evidence of a real MotorSignal server session. It has no production configuration path: startup requires a `demo-` project and loopback Auth Emulator host.
## Authentication trace and 401s
For Google sign-in, `website/components/auth-form.tsx` calls Firebase `signInWithPopup`, obtains `credential.user.getIdToken()`, then POSTs `{id_token}` to same-origin `/api/auth/firebase-login`. `website/app/api/auth/[action]/route.ts` maps this to `/api/v1/auth/firebase/login` at `MOTORSIGNAL_API_BASE_URL`, forwards the upstream status and body, and, on success, sets `motorsignal_access_token` and refresh cookies with `HttpOnly`, `Secure`, `SameSite=Lax`, `Path=/`, and a 30-day max age. `/api/account/sessions` reads the access cookie and forwards it as a bearer token to the backend sessions endpoint. This local HTTP loopback test successfully returned the Secure, Lax, root-path cookie on protected requests; the browser received 200 for `/api/account/sessions` twice per account. Harness logs recorded `verified:true`, the demo project, `cookieBearerPresent:true`, and `sessionMatched:true`, with only UID prefixes `LZvemU5k` (organizer) and `hB1r4vgD` (member).
Browser evidence for both Google accounts showed Auth Emulator identity-provider and account-lookup responses 200, same-origin Firebase login 200, and protected session reads 200. The fixture logged `/api/v1/auth/sessions` status 200 with a present bearer and matched local session. The account page's separate `/api/v1/me` request returned 404 because that endpoint is intentionally absent from the fixture; therefore the account profile workflow was not verified. Email login returned 401 by design: the local fixture logged `email-auth-not-configured-in-local-fixture` and the UI displayed “Email authentication is not configured in this local QA fixture.”
The earlier 401s against the default website setup cannot be attributed to a specific server rejection from this isolated run. The Next route defaults to `https://api.motorsignal.app` when `MOTORSIGNAL_API_BASE_URL` is unset, and that production endpoint was deliberately not called. A code-level issue was confirmed: Google sign-in did not check `response.ok` before redirecting to `/account`, so a failed session exchange could look like successful navigation.
## Hub discovery and multi-user behavior
The website's `website/components/group-rides/group-rides-management.tsx:245-274` listens to two user-scoped queries: `group_rides` where `organizerUid == currentUser.uid`, and `group_rides` where `accessUids` array-contains the current UID. It applies no public visibility, status, or time filter. `firestore.rules:267-296` defines `canReadRide()` (public visibility is allowed for point reads) and a separate `allow list` requiring organizer UID, access UID, or active club-admin association. Thus the member's zero-result Hub was a successful empty response from these scoped queries, not a query error or authorization failure. The “Explore Community Rides” label does not currently query public rides, so public discovery is not implemented by this listener.
As the organizer (`LZvemU5k` UID prefix), the website created one ride through the actual Firestore client SDK and emulator rules. Firestore WebChannel write and listen requests returned 200. Emulator inspection found one `group_rides` record with ID `ride_1790852960205_LZvemU`, title `Website Followup QA Ride 20261001`, visibility `public`, join policy `requestToJoin`, organizer UID matching the creator, and `accessUids` containing that creator. The create flow also wrote the organizer participant document. The organizer Hub then showed one squad, one “My Pack Run”, one “Explore Community Rides”, one “Organized by Me”, and zero “Joined as Wingman”. Screenshot: `website-followup-owner-hub.png`.
The second authenticated member (`hB1r4vgD` UID prefix) saw zero rides because neither query matched that account. Selecting “Join with Code” and entering `APEX84` produced the success toast “Synchronized telemetry with ‘Bay Area Dawn Patrol’” and a “Telemetry Linked” state, but no Firestore write followed. Source at `website/components/group-rides/join-ride-modal.tsx:48-80` confirms this modal uses a 400 ms timeout and canned code mapping, not a ride lookup or membership mutation. The `handleRequestSlot` handler at `website/components/group-rides/group-rides-management.tsx:481-491` also only calls `setRides` and shows a toast. Emulator inspection afterward showed the created ride still had participant count 1 and only the organizer UID in `accessUids`; no member participant document existed. Screenshot: `website-followup-join-simulated.png`. This is a UI-only false success, not a successful join.
The Hub's request-slot handler only updates local React state and shows a toast; it performs no backend write. A join-requests API route exists but the website UI does not call it, and no web approval flow was found. Therefore multi-user create/join/approve is blocked after the verified organizer create: member discovery, durable join, and approval are not implemented in the exercised website path. No product change was made to expand this scope.
## Validation, evidence, and cleanup
Focused client tests ran with `npm exec vitest run tests/firebase-client-emulator.test.ts --reporter=verbose`: 6 passed, 0 failed. They cover emulator selection, custom isolated ports, and invalid port rejection. The run emitted the repository's existing Vite config-loader warning. No full website suite or production build was run; no product behavior code changed.
Screenshots are `docs/qa-2026-10-01/website-followup-owner-hub.png` and `docs/qa-2026-10-01/website-followup-join-simulated.png`. Browser and local service logs were inspected with tokens and cookie contents excluded. Auth/Firestore emulator data was test-only and was cleared after evidence capture. The local Next server, fixture API, Firebase emulators, and three dedicated browser sessions were stopped. The isolated ports were checked for listeners after shutdown. Existing root-owned QA services on 9099/8088/4400/4500 were not stopped. No test user or ride remains in the isolated emulators.
The website-only source/test changes are `website/lib/firebase-client.ts` and `website/tests/firebase-client-emulator.test.ts`. Temporary support files under `website/test-support/` document the isolated emulator rules, Firebase config, and token-verifying fixture server; they are test-only and do not change production auth. Existing modified files, including `website/next-env.d.ts`, were preserved.
## Post-diagnosis auth error handling fix
`website/components/auth-form.tsx` now requires a successful HTTP response from `/api/auth/firebase-login` before redirecting. A rejected response surfaces server detail (or a useful fallback) in the form. The network-error legacy-link fallback must also return a successful response. New UI regression test `website/tests/auth-form.test.tsx` first failed because no alert appeared after a 401 and the handler attempted navigation; after the fix it passes and verifies one request plus visible rejection feedback. Focused test: **1/1 PASS**. Production auth code is not weakened and no API request to production was made.