Skip to content

Feat: add email login auth#832

Merged
graycyrus merged 10 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/add-email-auth
Apr 23, 2026
Merged

Feat: add email login auth#832
graycyrus merged 10 commits into
tinyhumansai:mainfrom
YellowSnnowmann:feat/add-email-auth

Conversation

@YellowSnnowmann
Copy link
Copy Markdown
Contributor

@YellowSnnowmann YellowSnnowmann commented Apr 23, 2026

Summary

  • Add email magic-link login to the Welcome screen alongside the existing OAuth buttons.
  • Detect desktop vs web at submit time and send the appropriate frontendRedirectUri (openhuman:// for Tauri, current origin for web).
  • Add a dedicated sendEmailMagicLink(...) frontend API helper that calls the backend /auth/email/send-link endpoint.
  • Show inline loading, error, and "check your email" success states so the new login path feels native instead of bolted on.
  • Reuse the existing deep-link auth flow rather than inventing a second frontend callback path.

Problem

  • The app already supported OAuth-based sign-in, but users could not start login with email from the main welcome screen.
  • The backend email-magic-link work needs a matching frontend entry point, otherwise the feature is effectively undiscoverable.
  • Desktop and web need different post-verification redirect targets, so the UI has to choose the correct redirect URI before requesting a magic link.

Solution

  • Update app/src/pages/Welcome.tsx to add:
    • email input state
    • submit handling
    • pending/error/success UI states
    • desktop/web redirect URI selection
  • Add app/src/services/api/authApi.ts helper sendEmailMagicLink(email, frontendRedirectUri) to POST to the backend auth endpoint.
  • Keep the frontend intentionally thin:
    • backend owns email sending, redirect validation, and verification
    • frontend only initiates the flow and explains the result to the user
  • For desktop, send openhuman:// so the backend can redirect back into the existing deep-link handler.
  • For web, send window.location.origin so the SPA can continue handling auth in-browser.

Submission Checklist

  • E2E / integration — Where behavior is user-visible or crosses UI → Tauri → sidecar → JSON-RPC; use existing harnesses (app/test/e2e, mock backend, tests/json_rpc_e2e.rs as appropriate)
  • Manual test — Manually tested the dev-backend and the openhuman tauri app , and performed the whole login flow
  • N/A — Backend-driven auth flow; this PR is a focused UI/API integration and does not add dedicated frontend tests in this branch
  • Doc comments — Added JSDoc for sendEmailMagicLink(...) and brief inline note for the desktop deep-link constant
  • Inline comments — Added where the desktop/web redirect split is non-obvious

(Any feature related checklist can go in here)

Impact

  • Runtime/platform impact: frontend welcome/login flow on desktop and web.
  • Security: no new auth policy is implemented in this PR; it relies on backend validation of redirect URIs and token issuance.
  • Compatibility: additive change to login options, with no change to the existing OAuth path.
  • UX: users can start email login directly from the first screen and get immediate success/error feedback.

Related

Summary by CodeRabbit

  • New Features

    • Added email magic-link sign-in with inline validation, disabled/spinner submit state, and a “Check your email” confirmation; OAuth sign-in remains available.
    • Backend email-link request support for the new flow.
  • Tests

    • Added tests covering the email magic-link UI flow and backend request behavior (including timeout handling).
    • Enabled/updated whitespace-collapsing test cases.

@YellowSnnowmann YellowSnnowmann requested a review from a team April 23, 2026 12:07
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

📝 Walkthrough

Walkthrough

Adds an email magic-link sign-in flow: Welcome page UI/state changes to send and confirm magic links, a new sendEmailMagicLink API client with timeout/abort handling, tests for both the UI and API, and a small Rust test import fix.

Changes

Cohort / File(s) Summary
Welcome page (UI + tests)
app/src/pages/Welcome.tsx, app/src/pages/__tests__/Welcome.test.tsx
Reintroduces and enables email magic-link form and handler: email state, sending/success/error states, Tauri vs web redirect URI, disabled/spinning submit UI; tests cover web/Tauri redirect values, lifecycle UI states, and error handling.
Auth API (client + tests)
app/src/services/api/authApi.ts, app/src/services/api/__tests__/authApi.test.ts
Adds export async function sendEmailMagicLink(email, frontendRedirectUri, timeoutMs?) which POSTs to /auth/email/send-link using backend base URL, supports AbortSignal timeout and maps non-OK responses to thrown errors; tests verify request shape and timeout behavior.
Rust tests import
src/openhuman/threads/ops.rs
Imports collapse_whitespace into the test module so related tests compile and run; no runtime behavior changes.

Sequence Diagram

sequenceDiagram
    actor User
    participant Welcome as Welcome Component
    participant API as sendEmailMagicLink
    participant Backend as Backend Service

    User->>Welcome: Enter email & submit
    activate Welcome
    Welcome->>Welcome: set sending/loading state
    Welcome->>API: call sendEmailMagicLink(email, redirectUri)
    deactivate Welcome

    activate API
    API->>API: resolve backend URL, start timeout/AbortSignal
    API->>Backend: POST /auth/email/send-link {email, frontendRedirectUri}
    deactivate API

    activate Backend
    Backend-->>API: 200 OK or error payload
    deactivate Backend

    activate API
    API->>API: parse response or throw (handle AbortError -> timeout message)
    API-->>Welcome: resolve or reject
    deactivate API

    activate Welcome
    Welcome->>Welcome: show "Check your email" on success or inline error on failure
    deactivate Welcome
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • M3gA-Mind
  • senamakel

Poem

🐰
I hop a tiny message bright,
A magic link to guide your night.
No keys, no fuss — just inbox cheer,
Click once and welcome — you are here! ✨

🚥 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 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat: add email login auth' clearly summarizes the main feature addition in the changeset—email magic-link authentication on the Welcome page.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
app/src/services/api/authApi.ts (1)

11-25: Please add redacted debug logging around this network hop.

This is the only backend call in the new flow, but there’s no dev-only trace for request start, resolved target, or failure path. Add namespaced logs here and redact the email rather than logging full PII.

As per coding guidelines, "Use namespaced debug + dev-only detail for logging in frontend code. Never log secrets or full PII — redact." and "Add substantial development-oriented logging at entry/exit points, branch decisions, external calls, retries/timeouts, state transitions, and error paths."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/services/api/authApi.ts` around lines 11 - 25, Add dev-only,
namespaced debug logging around sendEmailMagicLink: import and use a namespaced
debug logger (e.g., debug('auth:sendEmailMagicLink')) at entry, before the
fetch, on success and on error; redact the email when logging (do not log full
PII—mask local part leaving maybe first char and domain or replace with
*****@domain) and log the resolved backend target/endpoint, HTTP method,
response.status and any returned error message (but never log tokens/PII).
Ensure logs are only emitted in non-production/dev using the debug mechanism or
an explicit NODE_ENV check so production does not print these details.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/src/pages/Welcome.tsx`:
- Around line 77-107: The success and pending UI states (the isSent confirmation
block and the sending spinner) and the inline error need accessible live region
attributes so screen readers announce changes: add role="status" and
aria-live="polite" to the pending spinner container and to the isSent
confirmation container that renders the "Check your email" message (the JSX that
uses isSent and references email), and add role="alert" (or
aria-live="assertive") to the inline error block that displays submission
errors; update the same attributes in the other similar branch (lines referenced
in the review: the block around the spinner/error and the block at 131-153) so
isSent, setIsSent, setEmail, and the error rendering path provide proper
announcements.
- Around line 136-144: The email input in Welcome.tsx (the input bound to
value={email} and onChange={e => setEmail(e.target.value)}, disabled by
isSending) lacks an accessible name; add an associated <label htmlFor="..."> and
give the input a matching id (e.g., id="email") or, if you prefer not to add a
visible label, add a clear aria-label (e.g., aria-label="Email address") on the
same input element so screen readers can identify the field.
- Around line 21-42: The new handleEmailSubmit flow (function handleEmailSubmit)
introduces platform branching (isTauri vs window.location.origin), async state
changes (setIsSending, setIsSent, setEmailError) and UI paths (success view and
"Use a different email" reset) but lacks unit tests; add a co-located Vitest
React test file (e.g., Welcome.test.tsx) under app/src that mocks
sendEmailMagicLink and toggles isTauri, and assert: the correct redirect URI is
passed (DESKTOP_FRONTEND_URI when isTauri returns true, window.location.origin
otherwise), the pending state shows while the promise is unresolved and
setIsSending is toggled, backend errors surface to the UI when mocked
sendEmailMagicLink rejects (and setEmailError content is rendered), success
rendering occurs when it resolves (setIsSent true), and clicking the "Use a
different email" control resets state; use Vitest/react-testing-library patterns
and the existing app/test/vitest.config.ts for setup.

In `@app/src/services/api/authApi.ts`:
- Around line 15-23: The POST to `${backendUrl}/auth/email/send-link` can hang;
wrap the fetch in an AbortController with a reasonable timeout (e.g., 8–15s) and
call controller.abort() on timeout, then catch AbortError around the fetch in
the same function (the block using getBackendUrl and the fetch to
/auth/email/send-link) and rethrow a clear, user-facing Error like "Request
timed out, please retry" instead of leaving the promise unresolved; ensure the
controller.signal is passed into fetch and cleanup of the timeout timer after
fetch completes.

---

Nitpick comments:
In `@app/src/services/api/authApi.ts`:
- Around line 11-25: Add dev-only, namespaced debug logging around
sendEmailMagicLink: import and use a namespaced debug logger (e.g.,
debug('auth:sendEmailMagicLink')) at entry, before the fetch, on success and on
error; redact the email when logging (do not log full PII—mask local part
leaving maybe first char and domain or replace with *****@domain) and log the
resolved backend target/endpoint, HTTP method, response.status and any returned
error message (but never log tokens/PII). Ensure logs are only emitted in
non-production/dev using the debug mechanism or an explicit NODE_ENV check so
production does not print these details.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 75cae279-1450-4206-b56a-95c8c75d3307

📥 Commits

Reviewing files that changed from the base of the PR and between c8ecfd8 and 63eed85.

📒 Files selected for processing (2)
  • app/src/pages/Welcome.tsx
  • app/src/services/api/authApi.ts

Comment thread app/src/pages/Welcome.tsx
Comment thread app/src/pages/Welcome.tsx
Comment thread app/src/pages/Welcome.tsx
Comment thread app/src/services/api/authApi.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/src/services/api/__tests__/authApi.test.ts (1)

5-44: Good behavior-driven test coverage for the happy path and timeout.

The tests properly verify the request shape and timeout behavior without real network calls or time-dependent flakes.

Consider adding a test case for non-OK response handling to verify the error message extraction logic:

📝 Suggested additional test
it('throws the backend error message on non-OK responses', async () => {
  vi.spyOn(globalThis, 'fetch').mockResolvedValue(
    new Response(JSON.stringify({ error: 'Invalid email format' }), { status: 400 })
  );

  await expect(sendEmailMagicLink('bad-email', 'openhuman://')).rejects.toThrow(
    'Invalid email format'
  );
});

it('throws a status-based fallback when error body is missing', async () => {
  vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response('{}', { status: 500 }));

  await expect(sendEmailMagicLink('user@example.com', 'openhuman://')).rejects.toThrow(
    'Failed to send magic link (500)'
  );
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/services/api/__tests__/authApi.test.ts` around lines 5 - 44, Add
tests that exercise sendEmailMagicLink's non-OK response handling: mock
globalThis.fetch to resolve to a Response with status 400 and body JSON { error:
'Invalid email format' } and assert sendEmailMagicLink('bad-email',
'openhuman://') rejects with 'Invalid email format'; also add a test where fetch
resolves to a Response with status 500 and an empty or non-error body (e.g.
'{}') and assert the call rejects with the fallback message 'Failed to send
magic link (500)'. Ensure the tests use the same pattern as existing tests
(spyOn globalThis.fetch, mockResolvedValue with Response, and
expect(...).rejects.toThrow(...)) and name them clearly (e.g., "throws the
backend error message on non-OK responses" and "throws a status-based fallback
when error body is missing") so they align with the current test suite around
sendEmailMagicLink.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@app/src/services/api/__tests__/authApi.test.ts`:
- Around line 5-44: Add tests that exercise sendEmailMagicLink's non-OK response
handling: mock globalThis.fetch to resolve to a Response with status 400 and
body JSON { error: 'Invalid email format' } and assert
sendEmailMagicLink('bad-email', 'openhuman://') rejects with 'Invalid email
format'; also add a test where fetch resolves to a Response with status 500 and
an empty or non-error body (e.g. '{}') and assert the call rejects with the
fallback message 'Failed to send magic link (500)'. Ensure the tests use the
same pattern as existing tests (spyOn globalThis.fetch, mockResolvedValue with
Response, and expect(...).rejects.toThrow(...)) and name them clearly (e.g.,
"throws the backend error message on non-OK responses" and "throws a
status-based fallback when error body is missing") so they align with the
current test suite around sendEmailMagicLink.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dd976874-4101-49ef-a9b7-1d5784025bdf

📥 Commits

Reviewing files that changed from the base of the PR and between f5479c7 and 444525e.

📒 Files selected for processing (4)
  • app/src/pages/Welcome.tsx
  • app/src/pages/__tests__/Welcome.test.tsx
  • app/src/services/api/__tests__/authApi.test.ts
  • app/src/services/api/authApi.ts

@Al629176 Al629176 requested a review from graycyrus April 23, 2026 15:23
@graycyrus graycyrus merged commit 29a3962 into tinyhumansai:main Apr 23, 2026
7 checks passed
AusAgentSmith pushed a commit to AusAgentSmith/openhuman that referenced this pull request May 23, 2026
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.

2 participants