audit: 33-agent comprehensive audit + critical fixes
Full team audit run, all reports verbatim in docs/AUDIT-2026-05-12.md (5900+ lines, 30+ critical findings). Already-fixed this commit: - permission-overrides PUT: self-target block + RolePermissions allow-list + cross-tenant guard - /api/auth/resolve-identifier: rate-limit + synthetic miss-email kill enumeration - admin email-change: rotates account.accountId + revokes sessions - middleware: token-gated email confirm/cancel routes whitelisted - NAV_CATALOG: 10 dead-link sweeps to existing /admin/<x> targets Feature work landing same commit: optional username sign-in (migration 0054), per-user permission overrides (0055) with three-state matrix tabbed inside UserForm, user disable button, role + outcome + stage label normalisation across the platform, admin email-change with auto-notification template. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { and, eq } from 'drizzle-orm';
|
||||
|
||||
import { db } from '@/lib/db';
|
||||
import { user, userProfiles, userPortRoles, roles, ports } from '@/lib/db/schema';
|
||||
import { account, session, user, userProfiles, userPortRoles, roles, ports } from '@/lib/db/schema';
|
||||
import { auth } from '@/lib/auth';
|
||||
import { createAuditLog, type AuditMeta } from '@/lib/audit';
|
||||
import { ConflictError, NotFoundError, ValidationError } from '@/lib/errors';
|
||||
@@ -247,6 +247,25 @@ export async function updateUser(
|
||||
await db.update(user).set(userUpdates).where(eq(user.id, userId));
|
||||
}
|
||||
|
||||
if (wantsEmailChange) {
|
||||
const newEmailLower = data.email!.toLowerCase();
|
||||
// Better Auth's credential provider authenticates by
|
||||
// `account.accountId` (the email captured at sign-up), NOT by
|
||||
// `user.email`. Without this update the user can't sign in with
|
||||
// either address — old fails because user.email no longer matches,
|
||||
// new fails because there's no account.accountId row for it.
|
||||
await db
|
||||
.update(account)
|
||||
.set({ accountId: newEmailLower, updatedAt: new Date() })
|
||||
.where(and(eq(account.userId, userId), eq(account.providerId, 'credential')));
|
||||
|
||||
// Revoke every active session — the admin just changed the identity
|
||||
// the user authenticates with, so existing sessions are effectively
|
||||
// orphaned and a security risk if the account is being rotated due
|
||||
// to compromise. The user re-authenticates with the new address.
|
||||
await db.delete(session).where(eq(session.userId, userId));
|
||||
}
|
||||
|
||||
if (wantsEmailChange && previousEmail) {
|
||||
// Best-effort notification — failure to send doesn't roll back the
|
||||
// change because Better Auth's primary identity has already moved.
|
||||
|
||||
Reference in New Issue
Block a user