From 1ae84294bb9c33bff13cf797037a7ca1845fabb9 Mon Sep 17 00:00:00 2001 From: gitadmin Date: Thu, 14 May 2026 16:47:35 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20UserPayload=20=E5=A2=9E=E5=8A=A0=20perm?= =?UTF-8?q?issions=20=E5=AD=97=E6=AE=B5=EF=BC=8CgetCurrentUser=20=E8=A1=A5?= =?UTF-8?q?=E5=85=A8=E6=9D=83=E9=99=90=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - UserPayload 新增 permissions: string[] 字段 - getCurrentUser() 三个返回路径(共享JWT查DB/SSO自动创建/本地JWT) 均通过 getUserPermissions() 从 roles 表实时查询补全权限 - 新增 getUserPermissions(role) 工具函数,admin 返回 ['*'], 其他角色从 roles 表解析 permissions JSON - login() 函数和 login API 路由同步补全 permissions - verifyToken() 返回对象补全 permissions 空值防止类型错误 --- src/app/api/auth/login/route.ts | 5 +++-- src/lib/auth.ts | 10 ++++++++-- src/lib/jwt.ts | 3 ++- src/lib/permissions.ts | 12 ++++++++++++ 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts index af84e03..ad4ea06 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -4,6 +4,7 @@ import { initDatabase } from '@/lib/db-schema' import { signSharedJwt, sharedCookieConfig } from '@/lib/jwt-shared' import { ldapAuth } from '@/lib/ldap' import { getDb } from '@/lib/db' +import { getUserPermissions } from '@/lib/permissions' import bcrypt from 'bcryptjs' export async function POST(request: NextRequest) { @@ -76,12 +77,12 @@ export async function POST(request: NextRequest) { db.prepare("UPDATE users SET last_login_at = datetime('now', '+8 hours'), last_active_at = datetime('now', '+8 hours') WHERE id = ?").run(userId) // 4. 签发两个 cookie - const localToken = await createToken({ id: userId, username, display_name: displayName, role }) + const localToken = await createToken({ id: userId, username, display_name: displayName, role, permissions: [] }) const sharedToken = signSharedJwt({ username, displayName }) const sharedCfg = sharedCookieConfig() const response = NextResponse.json({ - user: { id: userId, username, display_name: displayName, role }, + user: { id: userId, username, display_name: displayName, role, permissions: getUserPermissions(role) }, }) response.cookies.set('session_issue', localToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 166821e..e3e1fbd 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -8,6 +8,7 @@ export { createToken, verifyToken, type UserPayload } import { verifySharedJwt } from './jwt-shared' import { ldapUserExists } from './ldap' +import { getUserPermissions } from './permissions' export async function getCurrentUser(): Promise { const cookieStore = await cookies() @@ -29,6 +30,7 @@ export async function getCurrentUser(): Promise { ).get(sharedPayload.username) as UserPayload | undefined if (row) { db.prepare("UPDATE users SET last_active_at = datetime('now', '+8 hours') WHERE id = ?").run(row.id) + row.permissions = getUserPermissions(row.role) return row } // SSO 免登录:LLDAP 验证通过但本地无记录 → 自动创建(viewer 角色) @@ -38,7 +40,10 @@ export async function getCurrentUser(): Promise { const newRow = db.prepare( 'SELECT id, username, display_name, role FROM users WHERE username = ? AND is_active = 1' ).get(sharedPayload.username) as UserPayload | undefined - if (newRow) return newRow + if (newRow) { + newRow.permissions = getUserPermissions(newRow.role) + return newRow + } } } @@ -49,6 +54,7 @@ export async function getCurrentUser(): Promise { if (payload) { const db2 = getDb() db2.prepare("UPDATE users SET last_active_at = datetime('now', '+8 hours') WHERE id = ?").run(payload.id) + payload.permissions = getUserPermissions(payload.role) } return payload } @@ -58,7 +64,7 @@ export async function login(username: string, password: string) { const user = db.prepare('SELECT * FROM users WHERE username = ? AND is_active = 1').get(username) as any if (!user) return null if (!bcrypt.compareSync(password, user.password_hash)) return null - const payload: UserPayload = { id: user.id, username: user.username, display_name: user.display_name, role: user.role } + const payload: UserPayload = { id: user.id, username: user.username, display_name: user.display_name, role: user.role, permissions: getUserPermissions(user.role) } return { token: await createToken(payload), user: payload } } diff --git a/src/lib/jwt.ts b/src/lib/jwt.ts index e19c1db..4c9a13d 100644 --- a/src/lib/jwt.ts +++ b/src/lib/jwt.ts @@ -8,6 +8,7 @@ export interface UserPayload { username: string display_name: string role: string + permissions: string[] // 用户权限列表 } // Encode Uint8Array to base64url @@ -72,7 +73,7 @@ export async function verifyToken(token: string): Promise { const payload = JSON.parse(base64urlToStr(parts[1])) if (payload.exp && payload.exp < Math.floor(Date.now() / 1000)) return null if (payload.id == null) return null - return { id: payload.id, username: payload.username, display_name: payload.display_name, role: payload.role } + return { id: payload.id, username: payload.username, display_name: payload.display_name, role: payload.role, permissions: payload.permissions || [] } } catch { return null } diff --git a/src/lib/permissions.ts b/src/lib/permissions.ts index a6458b7..6513eb7 100644 --- a/src/lib/permissions.ts +++ b/src/lib/permissions.ts @@ -31,3 +31,15 @@ export function requirePermission(user: UserPayload | null, permission: string): if (!user) throw new Error('未登录') if (!hasPermission(user, permission)) throw new Error('权限不足') } + +export function getUserPermissions(role: string): string[] { + if (role === 'admin') return ['*'] + const db = getDb() + const row = db.prepare('SELECT permissions FROM roles WHERE name = ?').get(role) as { permissions: string } | undefined + if (!row) return [] + try { + return JSON.parse(row.permissions) + } catch { + return [] + } +}