[object Object]

← back to Norma

Fix tier_credentials schema drift in auth + impersonate routes

86dc7d4cbd182cbb338101e4983ff9d0f677a150 · 2026-07-11 08:55:30 -0700 · Steve Abrams

login: remove dead is_active block + swap row.full_name -> display_name.
session: drop full_name/email from SELECT (email inferred from username).
impersonate: drop is_active from SELECT + remove disabled-account guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 86dc7d4cbd182cbb338101e4983ff9d0f677a150
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Jul 11 08:55:30 2026 -0700

    Fix tier_credentials schema drift in auth + impersonate routes
    
    login: remove dead is_active block + swap row.full_name -> display_name.
    session: drop full_name/email from SELECT (email inferred from username).
    impersonate: drop is_active from SELECT + remove disabled-account guard.
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 app/api/admin/impersonate/route.ts |  7 ++-----
 app/api/auth/login/route.ts        | 15 ++-------------
 app/api/auth/session/route.ts      | 13 +++++--------
 3 files changed, 9 insertions(+), 26 deletions(-)

diff --git a/app/api/admin/impersonate/route.ts b/app/api/admin/impersonate/route.ts
index 2362dab..58bbe6e 100644
--- a/app/api/admin/impersonate/route.ts
+++ b/app/api/admin/impersonate/route.ts
@@ -74,17 +74,14 @@ export async function POST(request: NextRequest) {
       return NextResponse.json({ error: 'Already that user' }, { status: 400 });
     }
 
-    const result = await query<{ username: string; role: string; org_id: string | null; is_active: boolean }>(
-      'SELECT username, role, org_id, is_active FROM tier_credentials WHERE username = $1',
+    const result = await query<{ username: string; role: string; org_id: string | null }>(
+      'SELECT username, role, org_id FROM tier_credentials WHERE username = $1',
       [targetUsername],
     );
     if (result.rows.length === 0) {
       return NextResponse.json({ error: 'User not found' }, { status: 404 });
     }
     const target = result.rows[0];
-    if (target.is_active === false) {
-      return NextResponse.json({ error: 'Cannot impersonate a disabled account' }, { status: 403 });
-    }
 
     // Preserve admin's existing session in the imp-by cookie
     const cookies = parseCookies(request);
diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts
index b461ec6..88ce4cb 100644
--- a/app/api/auth/login/route.ts
+++ b/app/api/auth/login/route.ts
@@ -73,17 +73,6 @@ export async function POST(request: NextRequest) {
       return invalidCredentials(ip);
     }
 
-    // Block deactivated accounts AFTER password check so we don't leak whether
-    // a given username exists + is disabled vs simply invalid.
-    if (row.is_active === false) {
-      return NextResponse.json(
-        { error: 'Account disabled. Contact an admin.' },
-        { status: 403 },
-      );
-    }
-
-    // (last_login_at bump removed — column not present in this schema)
-
     // Auto-upgrade SHA-256 hashes to bcrypt on successful login
     if (needsRehash) {
       const bcryptHash = await hashPassword(password);
@@ -111,8 +100,8 @@ export async function POST(request: NextRequest) {
       success: true,
       role: row.role,
       orgId: row.org_id,
-      displayName: row.full_name || row.display_name,
-      fullName: row.full_name,
+      displayName: row.display_name,
+      fullName: row.display_name,
       clientType: clientType || row.client_type || null,
       isInteriorDesigner: clientType === 'trade',
     });
diff --git a/app/api/auth/session/route.ts b/app/api/auth/session/route.ts
index d302985..c0c6fb2 100644
--- a/app/api/auth/session/route.ts
+++ b/app/api/auth/session/route.ts
@@ -10,26 +10,23 @@ export async function GET(request: NextRequest) {
     // who's logged in (header chrome, Gmail CRM, audit log, etc.)
     let displayName: string | null = null;
     let clientType:  string | null = null;
-    let fullName:    string | null = null;
     let email:       string | null = null;
     try {
       const res = await query(
-        'SELECT display_name, client_type, full_name, email FROM tier_credentials WHERE username = $1',
+        'SELECT display_name, client_type FROM tier_credentials WHERE username = $1',
         [session.username],
       );
       if (res.rows.length > 0) {
         displayName = res.rows[0].display_name;
         clientType  = res.rows[0].client_type;
-        fullName    = res.rows[0].full_name;
-        email       = res.rows[0].email;
       }
     } catch {
       // Non-fatal — proceed without display name
     }
 
-    // Fallback: if `email` column wasn't populated, infer from the username
-    // when it's already an email-shaped string (e.g. 'info@studentdebtcrisis.org').
-    if (!email && session.username.includes('@')) {
+    // Infer email from the username when it's already an email-shaped string
+    // (e.g. 'info@studentdebtcrisis.org') — tier_credentials has no email column.
+    if (session.username.includes('@')) {
       email = session.username;
     }
 
@@ -39,7 +36,7 @@ export async function GET(request: NextRequest) {
       role: session.role,
       orgId: session.orgId,
       displayName,
-      fullName,
+      fullName: displayName,
       email,
       clientType,
       isInteriorDesigner: clientType === 'trade',

← 9ba27ad Fix tier_credentials schema drift in settings CRUD routes  ·  back to Norma  ·  Fix tier_credentials schema drift in users + gmail routes b184c10 →