feat: UserPayload 增加 permissions 字段,getCurrentUser 补全权限列表

- UserPayload 新增 permissions: string[] 字段
- getCurrentUser() 三个返回路径(共享JWT查DB/SSO自动创建/本地JWT)
  均通过 getUserPermissions() 从 roles 表实时查询补全权限
- 新增 getUserPermissions(role) 工具函数,admin 返回 ['*'],
  其他角色从 roles 表解析 permissions JSON
- login() 函数和 login API 路由同步补全 permissions
- verifyToken() 返回对象补全 permissions 空值防止类型错误
This commit is contained in:
gitadmin 2026-05-14 16:47:35 +08:00
parent 4b6bee1868
commit 1ae84294bb
4 changed files with 25 additions and 5 deletions

View File

@ -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',

View File

@ -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<UserPayload | null> {
const cookieStore = await cookies()
@ -29,6 +30,7 @@ export async function getCurrentUser(): Promise<UserPayload | null> {
).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<UserPayload | null> {
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<UserPayload | null> {
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 }
}

View File

@ -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<UserPayload | null> {
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
}

View File

@ -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 []
}
}