Skip to content

[feat] Sign in on mobile, and keep the session alive (7/12) - #5686

Draft
ardaerzin wants to merge 4 commits into
feat/mobile-sessions-and-transcriptfrom
feat/mobile-auth
Draft

[feat] Sign in on mobile, and keep the session alive (7/12)#5686
ardaerzin wants to merge 4 commits into
feat/mobile-sessions-and-transcriptfrom
feat/mobile-auth

Conversation

@ardaerzin

Copy link
Copy Markdown
Contributor

Context

The mobile app assumed you were already signed in. If you were not, you got a blank screen: no sign-in route, and no attempt to use the session you may already have.

Changes

/m gets a raw email sign-in page and a refresh attempt before it concludes you are signed out. The refresh matters because the access token has a short life: a phone returning from the lock screen often holds a valid refresh token and an expired access token, and without this it would show a sign-out screen to someone who is signed in.

The interceptor is installed at provider scope rather than per-request, so every caller gets the retry rather than only the ones that remembered to ask.

The desktop's sign-in page learns to route a mobile device to /m/auth (in @agenta/shared, alongside the gate), so a phone that starts at the desktop sign-in ends up in the right app.

Tests / notes

  • The session cookie is shared with desktop: same origin, same SuperTokens session. Signing in on either app signs you into both.
  • Provider sign-in (social, OTP, org SSO) is not here. This lane is email only; parity lands in lane 12.

What to QA

  • Open /m signed out. You get the sign-in page rather than a blank screen.
  • Sign in on desktop, then open /m in the same browser. You are already signed in.
  • Leave /m open past the access-token lifetime, then return to the tab. It refreshes rather than showing you a sign-out screen.

@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview Aug 3, 2026 10:30pm

Request Review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ecef6b7a-ef2c-4739-b702-6e455ebe2e9a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added mobile sign-in with email and password authentication when enabled.
    • Added automatic session initialization and refresh support for authenticated API access.
    • Added a dedicated mobile authentication page and sign-in link for signed-out users.
    • Desktop authentication and mobile authentication routes now redirect appropriately.
    • SSO and social sign-in can be completed through the desktop app.
  • Bug Fixes

    • Project loading now retries once after refreshing an expired session.
    • Added clearer handling for unavailable sign-in methods and authentication errors.

Walkthrough

Mobile authentication now uses SuperTokens for password sign-in and session refresh. The mobile app provides an authentication page, retries project requests after session refresh, and routes desktop authentication paths to mobile authentication.

Changes

Mobile authentication

Layer / File(s) Summary
Authentication foundation
web/mobile/package.json, web/mobile/src/lib/auth.ts, web/mobile/src/features/app/AppProviders.tsx
Added SuperTokens initialization, email sign-in mode resolution, password sign-in handling, session refresh, and fetch interception.
Sign-in page and user interface
web/mobile/src/features/auth/SignInScreen.tsx, web/mobile/src/pages/auth.tsx, web/mobile/src/features/context/states/SignedOutNotice.tsx
Added the mobile password sign-in form, authentication states, success redirect, and /auth navigation.
Session-aware project fetching
web/mobile/src/lib/context.ts
Project requests now refresh the session and retry once after an unauthenticated response.
Authentication route mapping
web/packages/agenta-shared/src/utils/mobileGate/index.ts, web/packages/agenta-shared/tests/unit/mobileGate.test.ts
Desktop /auth routes now map to /m/auth, except for /auth/callback. Tests cover the updated exceptions and mappings.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MobileApp
  participant SignInScreen
  participant SuperTokens
  participant ProjectContext
  participant API
  MobileApp->>SuperTokens: initialize session and email-password recipes
  MobileApp->>SignInScreen: render authentication page
  SignInScreen->>SuperTokens: submit email and password
  SuperTokens-->>SignInScreen: return authentication outcome
  SignInScreen->>ProjectContext: invalidate project queries
  ProjectContext->>API: request projects
  API-->>ProjectContext: return unauthenticated response
  ProjectContext->>SuperTokens: refresh session
  SuperTokens-->>ProjectContext: return refresh result
  ProjectContext->>API: retry project request once
Loading

Possibly related PRs

  • Agenta-AI/agenta#5679: Provides the mobile app scaffold extended here with authentication and session handling.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: mobile sign-in and session persistence.
Description check ✅ Passed The description directly explains mobile authentication, session refresh, routing, scope, limitations, and QA expectations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mobile-auth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
web/mobile/src/features/auth/SignInScreen.tsx (1)

50-67: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add accessible names to the email and password fields.

The inputs have no <label> and rely only on placeholder. Screen readers lose the field name once the user starts typing. Add aria-label (or a visually-hidden <label>) for each input.

♿ Proposed fix
                 <input
                     type="email"
                     autoComplete="email"
                     required
                     placeholder="Email"
+                    aria-label="Email"
                     value={email}
                     onChange={(event) => setEmail(event.target.value)}
                     className="border-border bg-background rounded-md border px-3 py-2 text-sm"
                 />
                 <input
                     type="password"
                     autoComplete="current-password"
                     required
                     placeholder="Password"
+                    aria-label="Password"
                     value={password}
                     onChange={(event) => setPassword(event.target.value)}
                     className="border-border bg-background rounded-md border px-3 py-2 text-sm"
                 />
web/mobile/src/lib/auth.ts (1)

29-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Share the OIDC env-key list between mobile and desktop.

web/mobile/src/lib/auth.ts duplicates the OAuth client-id keys used by web/oss/src/lib/helpers/dynamicEnv.ts::getEffectiveAuthConfig. Keep OIDC_CLIENT_ID_KEYS in a shared module and import it from both apps so added providers update getEmailSignInMode() and desktop auth config together.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fbd43ccb-1630-4578-bd25-65688080d262

📥 Commits

Reviewing files that changed from the base of the PR and between b43b6c7 and fe228cc.

⛔ Files ignored due to path filters (1)
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • web/mobile/package.json
  • web/mobile/src/features/app/AppProviders.tsx
  • web/mobile/src/features/auth/SignInScreen.tsx
  • web/mobile/src/features/context/states/SignedOutNotice.tsx
  • web/mobile/src/lib/auth.ts
  • web/mobile/src/lib/context.ts
  • web/mobile/src/pages/auth.tsx
  • web/packages/agenta-shared/src/utils/mobileGate/index.ts
  • web/packages/agenta-shared/tests/unit/mobileGate.test.ts

Comment on lines 74 to +77
/** Desktop URL → mobile equivalent (design.md "Gate and routing"). */
export function mapDesktopToMobile(pathname: string, search: string): string {
// Mobile sign-in (auth-lite): /auth/callback never reaches here (exception).
if (/^\/auth(\/|$)/.test(pathname)) return "/m/auth"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Password-reset token is lost when routing mobile /auth/reset-password links. mapDesktopToMobile discards the search string for every /auth* path, and the test suite only verifies this with an empty search, so the realistic ?token=... case from SuperTokens' reset-password email link is never exercised and the resulting data loss goes unnoticed.

  • web/packages/agenta-shared/src/utils/mobileGate/index.ts#L74-L77: preserve token/tenantId when mapping /auth/reset-password to a mobile destination, or exclude it from mobile mapping like /auth/callback.
  • web/packages/agenta-shared/tests/unit/mobileGate.test.ts#L90-L93: add a case asserting mapDesktopToMobile("/auth/reset-password", "?token=abc") preserves the token once the implementation is fixed.
📍 Affects 2 files
  • web/packages/agenta-shared/src/utils/mobileGate/index.ts#L74-L77 (this comment)
  • web/packages/agenta-shared/tests/unit/mobileGate.test.ts#L90-L93

@ardaerzin

Copy link
Copy Markdown
Contributor Author

Real, fixed, and worse than described — thank you.

mapDesktopToMobile discards search for every /auth* path, so a SuperTokens password-reset link arriving on a phone lost its ?token=. The part the finding did not reach: mobile has no screen that consumes a token at all (web/mobile/src/pages/auth/ is sign-in and the OAuth callback), and the desktop auth page reads router.query.token for invite acceptance as well, so the same redirect silently broke invites.

Preserving the query would have moved the token to a page that cannot use it. The fix follows the precedent already in this file: an OAuth callback is passed through because bouncing it drops the one-time code. A one-time token is the same class of credential, so /auth links carrying one now complete on desktop.

Two tests added, and I verified they fail without the change: /auth and /auth/reset-password with ?token= pass through, while a plain /auth with no token still redirects to /m/auth.

Worth noting the blast radius was bounded: the gate is off unless a deployment sets AGENTA_MOBILE_GATE=true.

@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ardaerzin
ardaerzin force-pushed the feat/mobile-sessions-and-transcript branch from 7a90b8c to 2a51cb5 Compare August 3, 2026 18:26
@ardaerzin

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ardaerzin

Copy link
Copy Markdown
Contributor Author

Already fixed on this branch. The finding is right about mapDesktopToMobile discarding search, but a token-bearing /auth link no longer reaches the mapping: decideDesktopGate passes it through to the desktop before the redirect, on the same reasoning as the OAuth-callback exception. That is what isTokenBearingAuthLink does, and the fix now sits in the commit that opened the hole rather than further up the stack.

Two tests cover it (/auth and /auth/reset-password with ?token= pass through; a plain /auth still redirects), and I verified they fail without the change. Worth adding to the finding: mobile has no screen that consumes a token, and the desktop auth page reads router.query.token for invite acceptance too, so preserving the query into /m/auth would not have helped either case.

/auth leaves DESKTOP_EXCEPTIONS now that /m/auth exists: mobile devices
navigating desktop /auth land on the mobile sign-in via the deep-link map.
/auth/callback stays a desktop exception — the OAuth/SSO redirect landing
must complete on the desktop app (mobile has no SSO).

The web/mobile middleware carries only the reverse-gate subset (none of the
changed functions), so its verbatim copy needs no mirror; the desktop
middlewares import from @agenta/shared and pick this up without edits.
An expired access token was indistinguishable from signed-out: fetchProjects
returned {kind:"unauthenticated"} on the first 401 even when a valid refresh
token cookie was sitting right there. Add a headless supertokens-web-js
client (same 0.16.x the desktop's supertokens-auth-react wraps, appInfo
mirroring web/oss/src/config/appInfo.ts) and have fetchProjects try one
Session.attemptRefreshingSession() + retry before the verdict stands.
/m/auth: thin page shell + SignInScreen with a raw email/password form
against the shared SuperTokens backend (EmailPassword recipe joins the
headless init). The effective email mode mirrors the desktop's
getEffectiveAuthConfig — otp and disabled deployments get a notice instead
of the form, and SSO/social is signposted to the desktop app. On success
the cached unauthenticated projects verdict is invalidated and the root
context resolver takes over. SignedOutNotice now links to /m/auth.
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