feat(auth): add lastSeenAt field to user schema and tests
This commit is contained in:
@@ -152,3 +152,5 @@ docs/ai/context/verifications/
|
||||
# Generated from packages/i18n/glossary/terms.yaml at upload time. Never committed, so it
|
||||
# cannot drift from its source.
|
||||
packages/i18n/glossary/glossary.tbx
|
||||
|
||||
/airi-docs/
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { user } from '@proj-airi/auth-shared'
|
||||
import { getTableColumns } from 'drizzle-orm'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
describe('better Auth schema contract', () => {
|
||||
it('keeps the application-owned last_seen_at user column', () => {
|
||||
const columns = getTableColumns(user)
|
||||
|
||||
expect(columns.lastSeenAt).toBeDefined()
|
||||
expect(columns.lastSeenAt.name).toBe('last_seen_at')
|
||||
expect(columns.lastSeenAt.notNull).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -542,6 +542,17 @@ export function createAuth(
|
||||
},
|
||||
|
||||
user: {
|
||||
// Keep this server-managed activity field in Better Auth's declared
|
||||
// schema so `better-auth generate` preserves it in auth-shared.
|
||||
// Session creation updates it below; clients must never supply it.
|
||||
additionalFields: {
|
||||
lastSeenAt: {
|
||||
type: 'date',
|
||||
required: false,
|
||||
input: false,
|
||||
returned: true,
|
||||
},
|
||||
},
|
||||
changeEmail: {
|
||||
enabled: true,
|
||||
// NOTICE:
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { AuthEnv } from '../env'
|
||||
|
||||
import { generateKeyPairSync } from 'node:crypto'
|
||||
|
||||
import { getAuthTables } from 'better-auth/db'
|
||||
import { decodeJwt, decodeProtectedHeader, importSPKI, jwtVerify } from 'jose'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
|
||||
@@ -59,6 +60,31 @@ describe('createAuth', () => {
|
||||
expect(auth.options.account?.accountLinking?.allowDifferentEmails).toBe(true)
|
||||
})
|
||||
|
||||
it('registers lastSeenAt as a server-managed Better Auth user field', () => {
|
||||
const auth = createAuth({} as unknown as AuthDatabase, {
|
||||
PUBLIC_URL: 'http://localhost:3000',
|
||||
AUTH_GOOGLE_CLIENT_ID: 'google-client',
|
||||
AUTH_GOOGLE_CLIENT_SECRET: 'google-secret',
|
||||
AUTH_GITHUB_CLIENT_ID: 'github-client',
|
||||
AUTH_GITHUB_CLIENT_SECRET: 'github-secret',
|
||||
BETTER_AUTH_SECRET: 'test-secret-test-secret-test-secret',
|
||||
ADDITIONAL_TRUSTED_ORIGINS: [],
|
||||
} as unknown as AuthEnv)
|
||||
|
||||
expect(auth.options.user?.additionalFields?.lastSeenAt).toMatchObject({
|
||||
type: 'date',
|
||||
required: false,
|
||||
input: false,
|
||||
returned: true,
|
||||
})
|
||||
expect(getAuthTables(auth.options).user.fields.lastSeenAt).toMatchObject({
|
||||
type: 'date',
|
||||
required: false,
|
||||
input: false,
|
||||
returned: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('asks social providers to show the account picker during OAuth authorization', () => {
|
||||
const auth = createAuth({} as unknown as AuthDatabase, {
|
||||
PUBLIC_URL: 'http://localhost:3000',
|
||||
|
||||
@@ -13,14 +13,6 @@ export const user = pgTable('user', {
|
||||
banned: boolean('banned').default(false),
|
||||
banReason: text('ban_reason'),
|
||||
banExpires: timestamp('ban_expires'),
|
||||
// NOTICE:
|
||||
// Touched in `databaseHooks.session.create.after` (see auth.ts) which
|
||||
// fires on sign-in AND on every OIDC access-token refresh (~hourly), so
|
||||
// this is effectively "last activity" for any user with a live client.
|
||||
// Better Auth has `session.updatedAt` but that's per-session-row; we
|
||||
// want one stable per-user timestamp for DAU-style queries without
|
||||
// joining/aggregating session rows. Nullable so existing rows backfill
|
||||
// lazily on next login instead of needing a migration-time seed.
|
||||
lastSeenAt: timestamp('last_seen_at'),
|
||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at')
|
||||
|
||||
Reference in New Issue
Block a user