Skip to content

feat(web,server): add email/password authentication#571

Merged
leoisadev1 merged 11 commits intomainfrom
feat/email-password-login
Feb 3, 2026
Merged

feat(web,server): add email/password authentication#571
leoisadev1 merged 11 commits intomainfrom
feat/email-password-login

Conversation

@leoisadev1
Copy link
Member

Summary

  • Enable email & password login alongside existing GitHub/Vercel OAuth
  • Add sign-in/sign-up form with toggle on the auth page
  • Email auth sessions are established inline (no redirect needed)

Changes

File What
apps/server/convex/auth.ts Enable emailAndPassword: { enabled: true } with 8-128 char password constraints
apps/web/src/lib/auth-client.tsx Add signInWithEmail() and signUpWithEmail() helper functions
apps/web/src/routes/auth/sign-in.tsx Email/password form with sign-in ↔ sign-up toggle, error handling, "or" divider above OAuth buttons

How it works

  1. Users can now sign in or create an account with email + password
  2. Sign-up mode adds a name field; defaults to email prefix if omitted
  3. After successful email auth, session is refreshed inline and user is redirected to /
  4. OAuth buttons (GitHub, Vercel) remain below an "or" divider
  5. All buttons are disabled while any auth operation is in progress

Closes #562

Enable email & password login alongside existing GitHub/Vercel OAuth.
Closes #562
@railway-app railway-app bot temporarily deployed to OpenChat / openchat-pr-571 January 31, 2026 16:25 Destroyed
@railway-app
Copy link

railway-app bot commented Jan 31, 2026

🚅 Deployed to the openchat-pr-571 environment in OpenChat

Service Status Web Updated (UTC)
web 😴 Sleeping (View Logs) Web Feb 3, 2026 at 3:15 am

…vigation

- Normalize auth error messages to prevent email enumeration attacks
- Make refetchSession return boolean, only navigate on success
- Add TODO for email verification before production
@railway-app railway-app bot temporarily deployed to OpenChat / openchat-pr-571 January 31, 2026 16:28 Destroyed
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 31, 2026

Greptile Overview

Greptile Summary

This PR successfully adds email/password authentication alongside the existing GitHub/Vercel OAuth, enabling users to sign up and sign in without requiring a third-party account.

Key Changes

  • Backend: Enabled email/password authentication in Better Auth config with sensible password constraints (8-128 characters)
  • Client helpers: Added signInWithEmail() and signUpWithEmail() wrapper functions in auth-client.tsx
  • UI: Implemented sign-in/sign-up form with toggle, error handling, and seamless inline session refresh (no redirect needed)
  • All auth buttons are properly disabled during any auth operation to prevent race conditions

Integration Notes

  • Email auth flows through the same Better Auth → Convex user sync pipeline as OAuth
  • The UserSyncProvider automatically syncs email-authenticated users to the Convex database via users.ensure mutation
  • Session management uses the existing non-reactive StableAuthProvider to prevent infinite loops

Issues Found

  • Missing analytics tracking (confidence 5/5): Email auth success does not call analytics.signedIn() unlike OAuth flows
  • Minor improvements: Error logging, input validation (max password length), and defensive name fallback suggested

Confidence Score: 4/5

  • Safe to merge with one critical fix needed - add analytics tracking for email auth
  • The implementation is solid and integrates cleanly with the existing Better Auth + Convex architecture. The missing analytics.signedIn() call is the only critical issue that should be addressed before merging to maintain consistency with OAuth flows. The other suggestions are minor improvements around error handling and input validation that don't affect functionality.
  • apps/web/src/routes/auth/sign-in.tsx needs the analytics call added on line 259-260

Important Files Changed

Filename Overview
apps/server/convex/auth.ts Enabled email/password auth with 8-128 character password constraints in Better Auth config
apps/web/src/lib/auth-client.tsx Added signInWithEmail() and signUpWithEmail() helper functions for email authentication
apps/web/src/routes/auth/sign-in.tsx Added email/password form with sign-in/sign-up toggle, error handling, and inline session refresh

Sequence Diagram

sequenceDiagram
    participant U as User
    participant SP as Sign In Page
    participant AC as Auth Client
    participant BA as Better Auth
    participant US as User Sync
    participant DB as Convex DB

    U->>SP: Enter form data
    
    alt Registration
        SP->>AC: signUpWithEmail()
        AC->>BA: POST sign up
        BA-->>AC: Response OK
    else Login
        SP->>AC: signInWithEmail()
        AC->>BA: POST sign in
        BA-->>AC: Response OK
    end
    
    SP->>AC: refetchSession()
    AC->>BA: GET session info
    BA-->>AC: Return data
    
    SP->>SP: Navigate home
    
    US->>DB: Sync user data
    DB-->>US: Confirm sync
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

3 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +259 to +260
const success = await refetchSession();
if (success) {
Copy link
Contributor

Choose a reason for hiding this comment

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

After successful email auth, analytics.signedIn() is not called, but it is called for OAuth flows in StableAuthProvider (line 169 in auth-client.tsx)

Suggested change
const success = await refetchSession();
if (success) {
await refetchSession();
analytics.signedIn();
navigate({ to: "/" });


try {
if (isSignUp) {
const { error } = await signUpWithEmail(email, password, name || email.split("@")[0]);
Copy link
Contributor

Choose a reason for hiding this comment

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

Defaulting the name to email.split("@")[0] could fail if the email doesn't contain @ (though the browser's type="email" validation should prevent this)

Better to be defensive:

Suggested change
const { error } = await signUpWithEmail(email, password, name || email.split("@")[0]);
const { error } = await signUpWithEmail(email, password, name || email.split("@")[0] || "User");

const success = await refetchSession();
if (success) {
navigate({ to: "/" });
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

The catch block swallows the actual error, making debugging harder. Consider logging it:

Suggested change
} else {
} catch (err) {
console.error("Email auth failed:", err);
setEmailError(isSignUp ? "Sign up failed. Please try again." : "Sign in failed. Please try again.");

id="password"
type="password"
placeholder="Min. 8 characters"
value={password}
Copy link
Contributor

Choose a reason for hiding this comment

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

HTML5 minLength provides client-side validation, but Better Auth config specifies maxPasswordLength: 128 (line 92 in auth.ts). Add maxLength for consistency:

Suggested change
value={password}
minLength={8}
maxLength={128}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

- Track analytics.signedIn() for email auth flow
- Add defensive fallback for name derivation from email
- Log errors in catch block for debugging
- Add maxLength=128 to password input matching server config
@railway-app railway-app bot temporarily deployed to OpenChat / openchat-pr-571 January 31, 2026 16:31 Destroyed
@railway-app railway-app bot temporarily deployed to OpenChat / openchat-pr-571 February 1, 2026 02:51 Destroyed
@github-actions
Copy link
Contributor

github-actions bot commented Feb 1, 2026

🚀 Preview Deployment Ready

Environment URL
Frontend https://web-openchat-pr-571.up.railway.app
Convex Dashboard Dashboard

Convex Preview Backend

  • Cloud URL: https://brave-labrador-160.convex.cloud
  • Site URL: https://brave-labrador-160.convex.site

🤖 Deployed automatically by GitHub Actions

@railway-app railway-app bot temporarily deployed to OpenChat / openchat-pr-571 February 1, 2026 04:18 Destroyed
@railway-app railway-app bot temporarily deployed to OpenChat / openchat-pr-571 February 1, 2026 04:31 Destroyed
@railway-app railway-app bot temporarily deployed to OpenChat / openchat-pr-571 February 1, 2026 04:43 Destroyed
@railway-app railway-app bot temporarily deployed to OpenChat / openchat-pr-571 February 1, 2026 04:47 Destroyed
requireEnv('GITHUB_CLIENT_ID') threw during Convex module analysis before
env vars were available, preventing code from deploying to preview environments.

Now uses conditional spread (like Vercel OAuth) so GitHub OAuth is only
configured when credentials exist. Also restructures the preview deploy
workflow to: deploy → set env vars → redeploy with env vars.
@railway-app railway-app bot temporarily deployed to OpenChat / openchat-pr-571 February 2, 2026 22:59 Destroyed
--preview-create generates a new Convex deployment each call with a
different URL. The redeploy step was creating a second deployment without
the env vars set in the previous step. Since createAuth() reads env vars
at runtime, a single deploy + env var set is sufficient.
@leoisadev1 leoisadev1 merged commit e3c9786 into main Feb 3, 2026
6 checks passed
@leoisadev1 leoisadev1 deleted the feat/email-password-login branch February 3, 2026 22:49
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.

offer email/pass login

1 participant