feat(auth): Google SSO via OIDC PKCE - #10
Open
psdjungpulzze wants to merge 15 commits into
Open
Conversation
- 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
https://auth.interactor.comsrc/lib/oidc.ts:useOidcRequesthook +exchangeCodeForTokenviaexpo-auth-session(PKCE S256)signInWithJwt()in auth store;signIn()(password flow) now delegates to it — both paths end with the samepm_mobile_*session exchangeHow it works
promptAsync()opens in-app browser tohttps://auth.interactor.com/oauth/authorizeid_tokenviaexpo-auth-sessionid_tokenis exchanged for a long-livedpm_mobile_*token viaPOST /api/v1/me/mobile-sessionsRequired action before testing
Register these redirect URIs with account-server's OAuth client (
EXPO_PUBLIC_INTERACTOR_CLIENT_ID):buildapp://auth/callback— standalone appexp://*/--/auth/callback— Expo Go / simulator (wildcard for LAN IPs)Test plan
npm run typecheckpasses (no TS errors)