[PR #4318] [CLOSED] Add lastLogin field to the user schema #30910

Closed
opened 2026-04-17 21:53:59 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/better-auth/better-auth/pull/4318
Author: @0xEbrahim
Created: 8/30/2025
Status: Closed

Base: canaryHead: feat/lastLogin


📝 Commits (8)

📊 Changes

29 files changed (+163 additions, -49 deletions)

View changed files

📝 e2e/smoke/test/fixtures/cloudflare/src/auth-schema.ts (+1 -0)
📝 packages/better-auth/src/__snapshots__/init.test.ts.snap (+6 -0)
📝 packages/better-auth/src/adapters/drizzle-adapter/test/schema.mysql.ts (+1 -0)
📝 packages/better-auth/src/adapters/drizzle-adapter/test/schema.ts (+1 -0)
📝 packages/better-auth/src/adapters/prisma-adapter/test/normal-tests/schema.prisma (+7 -6)
📝 packages/better-auth/src/adapters/prisma-adapter/test/number-id-tests/schema.prisma (+7 -6)
📝 packages/better-auth/src/client/client.test.ts (+5 -0)
📝 packages/better-auth/src/db/get-tables.ts (+6 -0)
📝 packages/better-auth/src/db/internal-adapter.ts (+1 -0)
packages/better-auth/src/db/last-login-at.test.ts (+35 -0)
📝 packages/better-auth/src/db/schema.ts (+1 -0)
📝 packages/better-auth/src/plugins/api-key/index.ts (+1 -0)
📝 packages/better-auth/src/plugins/oidc-provider/index.ts (+1 -0)
📝 packages/better-auth/src/types/types.test.ts (+2 -0)
packages/cli/getConfig_test-Et0z2l/apps/web/tsconfig.json (+8 -0)
packages/cli/getConfig_test-VqMO7h/test/server/auth/auth.ts (+12 -0)
packages/cli/getConfig_test-rd7rUy/packages/shared/tsconfig.json (+8 -0)
📝 packages/cli/test/__snapshots__/auth-schema-mysql-number-id.txt (+3 -0)
📝 packages/cli/test/__snapshots__/auth-schema-mysql.txt (+3 -0)
📝 packages/cli/test/__snapshots__/auth-schema-number-id.txt (+3 -0)

...and 9 more files

📄 Description

Add lastLoginAt field to users

Resolves

fixes #4301

Description

This PR implements a lastLoginAt field for users to track when they last logged in. This is a common requirement for admin dashboards to identify active vs. inactive users.

Problem

As described in the issue, it's quite common for apps (especially for admins) to want to know the last time a user logged in to see which accounts are fresh or stale.

Solution

Added a default lastLoginAt field that automatically updates whenever a user creates a new session (logs in).

Changes Made

1. Database Schema Updates

File: packages/better-auth/src/db/schema.ts

  • Added lastLoginAt: z.date().nullish() to the userSchema
  • Field is nullable/optional to support existing users

File: packages/better-auth/src/db/get-tables.ts

  • Added field definition in the user table configuration:
lastLoginAt: {
    type: "date",
    required: false,
    fieldName: options.user?.fields?.lastLoginAt || "lastLoginAt",
},

2. Session Creation Logic

File: packages/better-auth/src/db/internal-adapter.ts

  • Added updateWithHooks call in createSession function to update user's lastLoginAt timestamp
  • Updates both lastLoginAt and updatedAt fields when sessions are created
  • Works with both database and secondary storage configurations

3. API Response Updates

File: packages/better-auth/src/api/routes/sign-in.ts

  • Added lastLoginAt field to the user object in sign-in response

File: packages/better-auth/src/api/routes/sign-up.ts

  • Added lastLoginAt field to the user object in sign-up response (both auto-sign-in and non-auto-sign-in cases)

4. Tests

File: packages/better-auth/src/db/last-login-at.test.ts

  • Comprehensive test suite covering:
    • Initial state (null for new users)
    • Session creation updates
    • Multiple login scenarios
    • Database persistence
    • Custom field configurations

File: packages/better-auth/src/db/internal-adapter.test.ts

  • Added test to verify the field exists in schema

How It Works

  1. New users: lastLoginAt = null initially
  2. User signs in: createSession() is called → updateWithHooks() updates lastLoginAt to current timestamp
  3. Database: Field is automatically persisted
  4. API response: lastLoginAt is included in user object returned to client
  5. Subsequent logins: lastLoginAt gets updated to new timestamp

Features

  • Automatic updates - No manual intervention needed
  • Database persistence - Changes are committed to database
  • Hook integration - Works with existing database hooks
  • Configurable - Supports custom field names and model names
  • Type safe - Properly typed with TypeScript
  • Backward compatible - Existing users will have null values

Testing

All tests pass:

  • Initial state test: New users have lastLoginAt = null
  • Session creation test: lastLoginAt gets updated when user signs in
  • Multiple logins test: lastLoginAt gets updated on subsequent logins
  • Database persistence test: Field is properly stored in database

Migration Notes

For existing databases, the lastLoginAt field will be added as nullable, so existing users will have null values until their next login.

Alternative Implementation

As mentioned in the original issue, this could have been implemented using additionalFields and hooks, but providing it as a default field offers better developer experience and consistency across applications.

Usage Example

// After user signs in
const signInResponse = await client.signIn.email({
  email: "user@example.com",
  password: "password123",
});

// lastLoginAt will be automatically set
console.log(signInResponse.data?.user.lastLoginAt); // Date object

// For admin queries
const users = await db.findMany({
  model: "user",
  sortBy: { field: "lastLoginAt", direction: "desc" }
});

Benefits

  • Admin dashboards: Easily identify active vs. inactive users
  • User analytics: Track user engagement patterns
  • Account management: Identify stale accounts for cleanup
  • Security: Monitor suspicious login patterns
  • Zero configuration: Works out of the box with sensible defaults

Summary by cubic

Add lastLoginAt to the user model. It auto-updates on session creation and is returned by sign-in/sign-up APIs to track recent logins.

  • New Features

    • user schema: nullable date field lastLoginAt
    • createSession updates lastLoginAt (and updatedAt) via updateWithHooks
    • sign-in and sign-up responses include lastLoginAt
    • Tests cover initial null, updates on login, and DB persistence
  • Migration

    • No action needed; field is nullable and defaults to null for existing users
    • Supports custom field name via options.user.fields.lastLoginAt

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/better-auth/better-auth/pull/4318 **Author:** [@0xEbrahim](https://github.com/0xEbrahim) **Created:** 8/30/2025 **Status:** ❌ Closed **Base:** `canary` ← **Head:** `feat/lastLogin` --- ### 📝 Commits (8) - [`1c57f1e`](https://github.com/better-auth/better-auth/commit/1c57f1e522f21002de7dfabedd62c1bee55f72a1) add the lastLoginAt to session schema - [`5614b1c`](https://github.com/better-auth/better-auth/commit/5614b1c3d9a743e436361318335c1b2320ee39d3) fix linting - [`97ac93f`](https://github.com/better-auth/better-auth/commit/97ac93fca03fe910a1dc510d966006c5e99f5feb) Merge branch 'canary' into feat/lastLogin - [`9513718`](https://github.com/better-auth/better-auth/commit/9513718b5923f4b0c0f633dc1d1e699538caddb4) fixed - [`9508dba`](https://github.com/better-auth/better-auth/commit/9508dbad19b31e497f7667054d41184ed16f4dc4) Merge branch 'feat/lastLogin' of https://github.com/0xEbrahim/better-auth into feat/lastLogin - [`60a90a2`](https://github.com/better-auth/better-auth/commit/60a90a2d57cba89c5044b387fc8b7e8566bc2040) fix snapshot - [`3b9b7f7`](https://github.com/better-auth/better-auth/commit/3b9b7f72cbe3bea6b1ad47d12f150ef139ba1f52) fix - [`e9bb18e`](https://github.com/better-auth/better-auth/commit/e9bb18e25b869cba3cf457350d9be5dd2e3c603e) linting ### 📊 Changes **29 files changed** (+163 additions, -49 deletions) <details> <summary>View changed files</summary> 📝 `e2e/smoke/test/fixtures/cloudflare/src/auth-schema.ts` (+1 -0) 📝 `packages/better-auth/src/__snapshots__/init.test.ts.snap` (+6 -0) 📝 `packages/better-auth/src/adapters/drizzle-adapter/test/schema.mysql.ts` (+1 -0) 📝 `packages/better-auth/src/adapters/drizzle-adapter/test/schema.ts` (+1 -0) 📝 `packages/better-auth/src/adapters/prisma-adapter/test/normal-tests/schema.prisma` (+7 -6) 📝 `packages/better-auth/src/adapters/prisma-adapter/test/number-id-tests/schema.prisma` (+7 -6) 📝 `packages/better-auth/src/client/client.test.ts` (+5 -0) 📝 `packages/better-auth/src/db/get-tables.ts` (+6 -0) 📝 `packages/better-auth/src/db/internal-adapter.ts` (+1 -0) ➕ `packages/better-auth/src/db/last-login-at.test.ts` (+35 -0) 📝 `packages/better-auth/src/db/schema.ts` (+1 -0) 📝 `packages/better-auth/src/plugins/api-key/index.ts` (+1 -0) 📝 `packages/better-auth/src/plugins/oidc-provider/index.ts` (+1 -0) 📝 `packages/better-auth/src/types/types.test.ts` (+2 -0) ➕ `packages/cli/getConfig_test-Et0z2l/apps/web/tsconfig.json` (+8 -0) ➕ `packages/cli/getConfig_test-VqMO7h/test/server/auth/auth.ts` (+12 -0) ➕ `packages/cli/getConfig_test-rd7rUy/packages/shared/tsconfig.json` (+8 -0) 📝 `packages/cli/test/__snapshots__/auth-schema-mysql-number-id.txt` (+3 -0) 📝 `packages/cli/test/__snapshots__/auth-schema-mysql.txt` (+3 -0) 📝 `packages/cli/test/__snapshots__/auth-schema-number-id.txt` (+3 -0) _...and 9 more files_ </details> ### 📄 Description # Add `lastLoginAt` field to users ## Resolves fixes [#4301](https://github.com/better-auth/better-auth/issues/4301) ## Description This PR implements a `lastLoginAt` field for users to track when they last logged in. This is a common requirement for admin dashboards to identify active vs. inactive users. ### Problem As described in the issue, it's quite common for apps (especially for admins) to want to know the last time a user logged in to see which accounts are fresh or stale. ### Solution Added a default `lastLoginAt` field that automatically updates whenever a user creates a new session (logs in). ## Changes Made ### 1. Database Schema Updates **File: `packages/better-auth/src/db/schema.ts`** - Added `lastLoginAt: z.date().nullish()` to the `userSchema` - Field is nullable/optional to support existing users **File: `packages/better-auth/src/db/get-tables.ts`** - Added field definition in the user table configuration: ```typescript lastLoginAt: { type: "date", required: false, fieldName: options.user?.fields?.lastLoginAt || "lastLoginAt", }, ``` ### 2. Session Creation Logic **File: `packages/better-auth/src/db/internal-adapter.ts`** - Added `updateWithHooks` call in `createSession` function to update user's `lastLoginAt` timestamp - Updates both `lastLoginAt` and `updatedAt` fields when sessions are created - Works with both database and secondary storage configurations ### 3. API Response Updates **File: `packages/better-auth/src/api/routes/sign-in.ts`** - Added `lastLoginAt` field to the user object in sign-in response **File: `packages/better-auth/src/api/routes/sign-up.ts`** - Added `lastLoginAt` field to the user object in sign-up response (both auto-sign-in and non-auto-sign-in cases) ### 4. Tests **File: `packages/better-auth/src/db/last-login-at.test.ts`** - Comprehensive test suite covering: - Initial state (null for new users) - Session creation updates - Multiple login scenarios - Database persistence - Custom field configurations **File: `packages/better-auth/src/db/internal-adapter.test.ts`** - Added test to verify the field exists in schema ## How It Works 1. **New users**: `lastLoginAt = null` initially 2. **User signs in**: `createSession()` is called → `updateWithHooks()` updates `lastLoginAt` to current timestamp 3. **Database**: Field is automatically persisted 4. **API response**: `lastLoginAt` is included in user object returned to client 5. **Subsequent logins**: `lastLoginAt` gets updated to new timestamp ## Features - **Automatic updates** - No manual intervention needed - **Database persistence** - Changes are committed to database - **Hook integration** - Works with existing database hooks - **Configurable** - Supports custom field names and model names - **Type safe** - Properly typed with TypeScript - **Backward compatible** - Existing users will have `null` values ## Testing All tests pass: - ✅ Initial state test: New users have `lastLoginAt = null` - ✅ Session creation test: `lastLoginAt` gets updated when user signs in - ✅ Multiple logins test: `lastLoginAt` gets updated on subsequent logins - ✅ Database persistence test: Field is properly stored in database ## Migration Notes For existing databases, the `lastLoginAt` field will be added as nullable, so existing users will have `null` values until their next login. ## Alternative Implementation As mentioned in the original issue, this could have been implemented using `additionalFields` and hooks, but providing it as a default field offers better developer experience and consistency across applications. ## Usage Example ```typescript // After user signs in const signInResponse = await client.signIn.email({ email: "user@example.com", password: "password123", }); // lastLoginAt will be automatically set console.log(signInResponse.data?.user.lastLoginAt); // Date object // For admin queries const users = await db.findMany({ model: "user", sortBy: { field: "lastLoginAt", direction: "desc" } }); ``` ## Benefits - **Admin dashboards**: Easily identify active vs. inactive users - **User analytics**: Track user engagement patterns - **Account management**: Identify stale accounts for cleanup - **Security**: Monitor suspicious login patterns - **Zero configuration**: Works out of the box with sensible defaults <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Add lastLoginAt to the user model. It auto-updates on session creation and is returned by sign-in/sign-up APIs to track recent logins. - **New Features** - user schema: nullable date field lastLoginAt - createSession updates lastLoginAt (and updatedAt) via updateWithHooks - sign-in and sign-up responses include lastLoginAt - Tests cover initial null, updates on login, and DB persistence - **Migration** - No action needed; field is nullable and defaults to null for existing users - Supports custom field name via options.user.fields.lastLoginAt <!-- End of auto-generated description by cubic. --> --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-17 21:53:59 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#30910