Skip to content

fix: activate pending users on login#210

Merged
Systemsaholic merged 3 commits intomainfrom
fix/pending-user-login
Apr 18, 2026
Merged

fix: activate pending users on login#210
Systemsaholic merged 3 commits intomainfrom
fix/pending-user-login

Conversation

@Systemsaholic
Copy link
Copy Markdown
Owner

@Systemsaholic Systemsaholic commented Apr 15, 2026

Summary

  • Pending users who log in via the login form (not invite link) were staying in pending status despite being authenticated
  • recordLogin() now sets status='active' for pending users — if they can authenticate, they've completed onboarding
  • Locked users are excluded from the update (stay locked)

Test plan

  • Invite a new user → verify they become active after first login
  • Verify locked users are NOT activated by logging in

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Clarified and fixed login tracking so last-seen/login timestamps are handled correctly and account activation is deferred until after password setup/login.
  • Authentication Flow
    • Invite/signup users are now sent directly to the set-password page and remain pending until password setup; pending users are blocked from other routes and redirected to set-password.

If a pending user can authenticate (via login form or invite link),
they should be activated. Previously only the invite callback path
called /me/activate, so users who logged in directly stayed pending.

Now recordLogin() sets status='active' for pending users. Locked
users are excluded from the update.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 15, 2026

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

Project Deployment Actions Updated (UTC)
tailfire-client Ready Ready Preview, Comment Apr 18, 2026 3:32pm
tailfire-ota Ready Ready Preview, Comment Apr 18, 2026 3:32pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8e882b4b-8569-492b-ad8a-5fb918d38492

📥 Commits

Reviewing files that changed from the base of the PR and between fc91daa and 09645b3.

📒 Files selected for processing (5)
  • apps/admin/src/app/auth/callback/route.ts
  • apps/admin/src/app/auth/set-password/page.tsx
  • apps/admin/src/lib/supabase/middleware.ts
  • apps/admin/src/middleware.ts
  • apps/api/src/user-profiles/user-profiles.service.ts

📝 Walkthrough

Walkthrough

recordLogin was clarified to only update lastLoginAt and lastSeenAt (no activation). Admin auth callback no longer attempts server-side activation and redirects invite/signup users to set-password. The set-password page now calls the activation API with the session token and refreshes the Supabase session. Middleware exposes userStatus and enforces pending-user redirects to /auth/set-password.

Changes

Cohort / File(s) Summary
API — login recording
apps/api/src/user-profiles/user-profiles.service.ts
Comment added: recordLogin updates only lastLoginAt/lastSeenAt and does not perform account activation. No behavior change to update logic.
Admin — auth callback
apps/admin/src/app/auth/callback/route.ts
Removed server-side activation attempt after sign-in; client-side hash callback no longer calls activation and now redirects invite/signup flows to /auth/set-password. Updated comments to reflect pending status until password set.
Admin — set-password flow
apps/admin/src/app/auth/set-password/page.tsx
After password set, fetches Supabase session, conditionally POSTs to /user-profiles/me/activate using access token, refreshes Supabase session, then redirects to /profile?setup=true.
Admin — supabase middleware utils
apps/admin/src/lib/supabase/middleware.ts
updateSession now retrieves current session, extracts user_status from JWT access token (base64-decode middle segment), and returns `userStatus: string
Admin — middleware enforcement
apps/admin/src/middleware.ts
Added pendingAllowedRoutes whitelist and new check: if userStatus === 'pending' and route not allowed, redirect to /auth/set-password. updateSession destructuring updated to include userStatus.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant AdminApp as Admin App (set-password)
    participant Supabase
    participant API as /user-profiles/me/activate

    Browser->>AdminApp: Submit new password
    AdminApp->>Supabase: setSession / update password
    AdminApp->>Supabase: auth.getSession()
    Supabase-->>AdminApp: session (access_token)
    AdminApp->>API: POST /user-profiles/me/activate (Authorization: Bearer access_token)
    API-->>AdminApp: 200 OK (activation enqueued/done)
    AdminApp->>Supabase: auth.refreshSession() / setSession with new JWT
    Supabase-->>Browser: redirect to /profile?setup=true
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰✨ I hopped through code to nudge a gate,
Now pending friends go set their fate.
Tokens fetched and calls sent true,
Sessions refreshed — the path is new.
Small hops, big burrows—reborn crew! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: activate pending users on login' directly and accurately summarizes the main change: pending users are now activated when they log in, which is the primary objective of this PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/pending-user-login

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms


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 and usage tips.

Copy link
Copy Markdown

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/user-profiles/user-profiles.service.ts`:
- Around line 271-280: The recordLogin update call in user-profiles.service.ts
updates lastLoginAt, lastSeenAt and status but omits updatedAt; modify the
.set(...) in the recordLogin flow to also set updatedAt to the same now value so
status transitions use the same timestamp semantics as other status-update paths
(refer to recordLogin, lastLoginAt, lastSeenAt, status, updatedAt and
this.db.schema.userProfiles in the service).
🪄 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: 1c4e6a90-f756-4a50-bdab-6b11bd8a4aba

📥 Commits

Reviewing files that changed from the base of the PR and between b99b418 and fc91daa.

📒 Files selected for processing (1)
  • apps/api/src/user-profiles/user-profiles.service.ts

Comment on lines +271 to +280
.set({ lastLoginAt: now, lastSeenAt: now, status: 'active' })
.where(
and(
eq(this.db.schema.userProfiles.id, userId),
or(
eq(this.db.schema.userProfiles.status, 'active'),
eq(this.db.schema.userProfiles.status, 'pending'),
),
),
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Include updatedAt when transitioning user status in recordLogin.

This query now changes status, but it does not update updatedAt, which makes status transitions here inconsistent with other status-update paths.

Suggested patch
-      .set({ lastLoginAt: now, lastSeenAt: now, status: 'active' })
+      .set({
+        lastLoginAt: now,
+        lastSeenAt: now,
+        status: 'active',
+        updatedAt: now,
+      })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.set({ lastLoginAt: now, lastSeenAt: now, status: 'active' })
.where(
and(
eq(this.db.schema.userProfiles.id, userId),
or(
eq(this.db.schema.userProfiles.status, 'active'),
eq(this.db.schema.userProfiles.status, 'pending'),
),
),
)
.set({
lastLoginAt: now,
lastSeenAt: now,
status: 'active',
updatedAt: now,
})
.where(
and(
eq(this.db.schema.userProfiles.id, userId),
or(
eq(this.db.schema.userProfiles.status, 'active'),
eq(this.db.schema.userProfiles.status, 'pending'),
),
),
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/user-profiles/user-profiles.service.ts` around lines 271 - 280,
The recordLogin update call in user-profiles.service.ts updates lastLoginAt,
lastSeenAt and status but omits updatedAt; modify the .set(...) in the
recordLogin flow to also set updatedAt to the same now value so status
transitions use the same timestamp semantics as other status-update paths (refer
to recordLogin, lastLoginAt, lastSeenAt, status, updatedAt and
this.db.schema.userProfiles in the service).

- Remove early activation from both auth callback paths (query-param
  and hash-fragment) — users stay pending until password is set
- Revert recordLogin() to only update timestamps, not activate
- Add middleware redirect: pending users are sent to /auth/set-password
  for all routes except the set-password page itself and auth callback
- Activate account only after password is successfully set on the
  set-password page, then refresh session so JWT reflects active status
- Decode user_status from JWT claims in middleware for pending detection

Validated with Codex: uses server-controlled user_profiles.status
(injected into JWT via custom_access_token_hook) instead of
user-editable user_metadata.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts:
#	apps/admin/src/app/auth/callback/route.ts
@Systemsaholic Systemsaholic merged commit 81fc2bd into main Apr 18, 2026
1 of 4 checks passed
@Systemsaholic Systemsaholic deleted the fix/pending-user-login branch April 18, 2026 15:31
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