Skip to content

feat(auth): Google SSO via OIDC PKCE - #10

Open
psdjungpulzze wants to merge 15 commits into
mainfrom
feat/google-sso
Open

feat(auth): Google SSO via OIDC PKCE#10
psdjungpulzze wants to merge 15 commits into
mainfrom
feat/google-sso

Conversation

@psdjungpulzze

Copy link
Copy Markdown
Contributor

Summary

  • Adds a Continue with Google button to the login screen using OIDC PKCE against https://auth.interactor.com
  • New src/lib/oidc.ts: useOidcRequest hook + exchangeCodeForToken via expo-auth-session (PKCE S256)
  • New signInWithJwt() in auth store; signIn() (password flow) now delegates to it — both paths end with the same pm_mobile_* session exchange

How it works

  1. User taps "Continue with Google" → promptAsync() opens in-app browser to https://auth.interactor.com/oauth/authorize
  2. Account-server handles the Google IdP flow and returns an auth code
  3. App exchanges code + PKCE verifier for an id_token via expo-auth-session
  4. id_token is exchanged for a long-lived pm_mobile_* token via POST /api/v1/me/mobile-sessions

Required action before testing

Register these redirect URIs with account-server's OAuth client (EXPO_PUBLIC_INTERACTOR_CLIENT_ID):

  • buildapp://auth/callback — standalone app
  • exp://*/--/auth/callback — Expo Go / simulator (wildcard for LAN IPs)

Test plan

  • "Continue with Google" button visible on login screen
  • Tapping it opens account-server OAuth in browser
  • Completing Google auth redirects back and signs in (Me tab shows email)
  • Password login still works unchanged
  • npm run typecheck passes (no TS errors)

- Add src/lib/oidc.ts: useOidcRequest hook + exchangeCodeForToken using
  expo-auth-session with PKCE (S256) against https://auth.interactor.com
- Add signInWithJwt() to auth store; refactor signIn() to reuse it so
  both password and OIDC paths share the same pm_mobile_* exchange logic
- Update login screen with "Continue with Google" button above email/password
- Fix pre-existing TypeScript error in _layout.tsx (router.replace type)
- Install expo-auth-session, expo-crypto, expo-web-browser; add EXPO_PUBLIC_INTERACTOR_CLIENT_ID to config

Redirect URIs to register with account-server:
  buildapp://auth/callback          (standalone app)
  exp://*/--/auth/callback          (Expo Go / simulator)
- Fix env var names (EXPO_PUBLIC_INTERACTOR_CLIENT_ID/SECRET) to match config.ts
- Disable PKCE (account-server does not support code_challenge)
- Pass request.redirectUri to token exchange to guarantee URI consistency
- Bypass mobile-session exchange with user JWT directly (TODO: restore once
  POST /api/v1/me/mobile-sessions is deployed on build.interactor.com)
- Add babel.config.js with babel-preset-expo + reanimated plugin
- Add react-native-worklets peer dep for reanimated 4.x
- Update npm start to --dev-client for native build compatibility
- Add Ionicons to tab bar (home/checkbox/thumbs-up/notifications/person)
- Fix SafeAreaView import on home screen (react-native-safe-area-context)
- Remove Tasks and Inbox tabs; add Goal and Feedback tabs
- Overview: stats grid (todos, approvals, tasks, unread) + recent todo list with pull-to-refresh
- Goal: filter pills (All/Open/In Progress/Accepted) with GoalCard showing type badge, display number, status, priority dot, progress bar
- Approvals: approve/reject actions with reject comment modal (bottom sheet)
- Feedback: project picker scroll pills → feedback list with source, votes, status, rating
- Me: fix SafeAreaView import (react-native → react-native-safe-area-context), add settings section rows
- queries.ts: add EngineGoal, FeedbackEntry types; useGoals, useFeedback hooks; fix TodoProject typing
…+ cookie session

The mobile app is the frontend for the product-manager backend which uses
NextAuth cookie-based auth. iOS's NSURLSession cookie jar automatically stores
and sends the authjs.session-token cookie, so no manual token forwarding needed.

- Login: GET /api/auth/csrf → POST /api/auth/callback/credentials → cookie jar
  stores the session token → GET /api/auth/session confirms the session
- API client: remove Authorization Bearer header (cookie jar handles auth)
- SSE: remove Bearer header (cookie jar handles auth)
- Remove: expo-auth-session, expo-crypto, expo-web-browser, src/lib/oidc.ts
- Remove: pm_mobile_* token storage, signInWithJwt, ACCOUNT_SERVER_URL config
- Login screen: clean email/password form, no OIDC button
…e_* Bearer auth

All sign-in methods (email/password and Interactor SSO) now end with a
long-lived pm_mobile_* Bearer token stored in SecureStore. Every API call
and SSE connection uses Authorization: Bearer pm_mobile_<token>.

Email/password flow: credentials POST → session cookie → exchange via
POST /api/v1/me/mobile-sessions → store pm_mobile_*.

Interactor SSO flow: openAuthSessionAsync opens the product-manager OIDC
sign-in page → backend mobile-callback route mints pm_mobile_* and redirects
to buildapp://auth/callback?token=...&email=...&userId=... → app captures it.

The backend GET /api/auth/mobile-callback route is tracked in T#105 and
requires a separate PR before the SSO button is fully functional.
Full notification inbox with All/Unread filter pills, per-notification
mark-as-read, mark-all-as-read, type-based icons, and page navigation.
Replaces the Feedback tab with Inbox (more action-critical on mobile).
…k tabs, global header, web design tokens

- Tab bar now matches product-manager's mobile-bottom-nav: Overview, Goal,
  center AI action (Ask the Senior PM bottom sheet), Approval, Feedback
- Notifications + Me move to a global top bar (bell with unread badge, avatar)
- src/lib/theme.ts: design tokens copied from the web palette
- src/lib/responsive.ts: md/lg breakpoints + useBreakpoint for tablet grids
- app/(tabs)/ai.tsx: AI screen stub that hands off to web /ai chat
- app.json: enable all orientations for iPad/tablet
- useProjects hook (GET /api/v1/me/projects) + persisted selected-project
  store (SecureStore), null = All projects / global scope
- Header brand becomes the switcher trigger showing the pinned project,
  opening an org-grouped bottom sheet (web project-switcher parity)
- Goals filter by the pinned project; Feedback uses it and hides its
  local pills while a project is pinned
…shapes to match backend

Verified against product-manager origin/main route handlers:
- /me/todos returns { data: { items, facets } } — fix TodosResponse and all
  consumers (Overview, Approvals, Feedback)
- /me/engine/goals REQUIRES projectId and returns bare EngineGoal rows
  (no completion/ownerName) — Goals tab now scopes to the pinned project
  (falling back to the user's first project) and renders real fields
  (PR state, release, paused)
- New goal detail screen (app/goal/[goalId]) on GET /me/engine/goals/[id]:
  status/priority/type pills, description, completion progress, deploy gate
  (mergeable/CI/must-fix), PR link, release/target date, attachments
- Approvals: Requests | Admissions tabs — admissions list from
  GET /me/engine/approvals with the AI admission brief (verdict + summary),
  auto-approved provenance hides decide buttons
- Overview: phase breakdown section for the pinned project
  (GET /projects/[id]/phases)
…av, goal lanes, push deep links

Syncs with the completed web mobile/tablet UI (product-manager #1966-#1969):
- Tab bar now matches the final web bottom nav: Attention · Goals · AI hero
  (56px ring button) · Feedback · Home
- Fleet sidebar (src/components/fleet-sidebar.tsx): right-side panel with
  machine health dots + working/total counts, needs-attention machine cards,
  'Goals being worked on' queue in exact claim order (status pills: Fleet/
  Interactive/Blocked/Waiting on you/Recovering/Queued/Waiting on deps), and
  'Awaiting acceptance'. Opens via left-swipe from the right edge or the
  header fleet button; swipe right / tap scrim to close. Data from
  /me/engine/fleet-queue + /me/machines/fleet, polled 60s while open
- Goals: segmented Blocked | Ready | Queue lane switcher with counts
  (web phase-3 parity), replacing status filter pills
- Push: notification-tap deep links (engine_attention → goal detail or
  Attention tab)
…eet, GNB switcher, tab badges, lane reorder

- Bottom nav: Attention · Goals · AI hero · Feedback · Menu (Overview moves
  into the Menu sheet, matching web's Menu-tab layout)
- Red count badges on Attention (approvals + admissions) and Goals
  (running/recovering fleet goals), #FF3B30 like web
- Menu sheet (src/components/menu-sheet.tsx): profile group (Profile,
  Notifications, Sign out), native Overview, and web deep-links for
  Release/Sprint/Meeting/Comment/Decision/History/Setting on the pinned
  project
- Header = web GNB below lg: centered two-line org/project switcher, fleet
  button with live status dot (red alert / green working / gray idle),
  bell; avatar removed (profile lives in the Menu sheet)
- Fleet drawer: web-matched gestures (edge zone 32px, 50px thresholds,
  vertical-swipe rejection), width 85% max 360, plus an always-visible
  right-edge handle with status dot and working/total count
- Goals lanes reordered to Queue | Ready | Blocked (default Blocked)
- push.ts: alert-permission helpers (get/request with iOS re-prompt
  handling), local test alert, app-icon badge sync
- Root layout: app icon badge mirrors the unread notification count live
  (SSE-invalidated), cleared on sign-out
- Me screen: new 'Device alerts' section — OS permission state with
  Enable / Open Settings action, 'Goal attention alerts' toggle wired to
  the backend engine_attention_push preference, send-test-alert row, and
  the account's registered devices with remove
- queries.ts: useDevices/useDeleteDevice/usePreferences/useUpdatePreferences
  (needs product-manager PR #1974 for Bearer auth on /me/preferences)
Compared side-by-side against live web at 390px (Playwright captures with
an authenticated session):

- Real brand assets from product-manager/public: AI hero now renders
  icon_green.png in the ringed circle (label clipping fixed), Goals tab
  uses the target/disc icon
- Header = web GNB: chat + service-launcher icons left, centered two-line
  org/project switcher, fleet button (red alert dot live), GREEN bell badge
- Dashboard rebuilt as the web's My Todos card: FOCUS NOW hero band,
  UP NEXT numbered list, web todo priority palette (High orange, Medium
  yellow), priority sort to match web ordering
- PageHeader component (32pt title + gray subtitle) applied to Approvals /
  Goals / Feedback with the exact page-meta subtitles
- Goals: round green create-FAB + new-goal sheet (POST /me/engine/goals)
- Auth: transient iOS network errors (NSURLErrorDomain -1005/-1017)
  retried with backoff; step-tagged auth errors; dev-only QA auto-login
  via gitignored .env.local; debug probes removed
- useCounts() now accepts orgSlug + projectSlug to fetch project-scoped
  counts including untriagedFeedbackCount from /api/v1/me/counts
- Tab layout reads selected project and drives a red Feedback badge from
  untriagedFeedbackCount, matching web useNavCounts (PR #1975 parity)
- Feedback page now has Internal / Portal queue / Idea source tabs
  (client-side filter on the source field), mirroring the web's three-tab
  layout from feedback-client.tsx; badge counts shown inline per tab
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.

1 participant