- Add user management with roles and permissions (RBAC) - Implement OAuth2 service provider supporting 4 grant types: authorization_code, password, client_credentials, refresh_token - Add JWT authentication with 7-day expiry - Add admin API for users, roles and OAuth clients management - Add CLI tool for user management (scripts/user-cli.js) - Add collapsible sidebar layout with login dialog - Add user management page and OAuth client management page - Add server middleware for auth token verification - Add seed script for initial data (admin/admin123)
201 lines
6.0 KiB
TypeScript
201 lines
6.0 KiB
TypeScript
import db from '../../db'
|
|
import bcrypt from 'bcryptjs'
|
|
import type { User, Role } from './types'
|
|
|
|
export const SALT_ROUNDS = 10
|
|
|
|
export function getRoles(): Role[] {
|
|
const stmt = db.prepare('SELECT * FROM roles ORDER BY is_system DESC, id ASC')
|
|
const rows = stmt.all() as any[]
|
|
return rows.map(row => ({
|
|
...row,
|
|
permissions: JSON.parse(row.permissions || '[]')
|
|
}))
|
|
}
|
|
|
|
export function getRoleByName(name: string): Role | undefined {
|
|
const stmt = db.prepare('SELECT * FROM roles WHERE name = ?')
|
|
const row = stmt.get(name) as any
|
|
if (!row) return undefined
|
|
return {
|
|
...row,
|
|
permissions: JSON.parse(row.permissions || '[]')
|
|
}
|
|
}
|
|
|
|
export function createRole(name: string, description: string, permissions: string[], isSystem = false): Role {
|
|
const stmt = db.prepare(
|
|
'INSERT INTO roles (name, description, permissions, is_system) VALUES (?, ?, ?, ?)'
|
|
)
|
|
const result = stmt.run(name, description, JSON.stringify(permissions), isSystem ? 1 : 0)
|
|
return {
|
|
id: Number(result.lastInsertRowid),
|
|
name,
|
|
description,
|
|
permissions,
|
|
is_system: isSystem,
|
|
created_at: new Date().toISOString()
|
|
}
|
|
}
|
|
|
|
export function getUsers(options?: { roleId?: number; status?: string }): User[] {
|
|
let sql = 'SELECT u.*, r.name as role_name, r.permissions as role_permissions FROM users u LEFT JOIN roles r ON u.role_id = r.id WHERE 1=1'
|
|
const params: any[] = []
|
|
|
|
if (options?.roleId) {
|
|
sql += ' AND u.role_id = ?'
|
|
params.push(options.roleId)
|
|
}
|
|
|
|
if (options?.status) {
|
|
sql += ' AND u.status = ?'
|
|
params.push(options.status)
|
|
}
|
|
|
|
sql += ' ORDER BY u.created_at DESC'
|
|
|
|
const stmt = db.prepare(sql)
|
|
const rows = stmt.all(...params) as any[]
|
|
return rows.map(row => ({
|
|
...row,
|
|
permissions: JSON.parse(row.role_permissions || '[]')
|
|
}))
|
|
}
|
|
|
|
export function getUserById(id: number): User | undefined {
|
|
const stmt = db.prepare('SELECT u.*, r.name as role_name, r.permissions as role_permissions FROM users u LEFT JOIN roles r ON u.role_id = r.id WHERE u.id = ?')
|
|
const row = stmt.get(id) as any
|
|
if (!row) return undefined
|
|
return {
|
|
...row,
|
|
permissions: JSON.parse(row.role_permissions || '[]')
|
|
}
|
|
}
|
|
|
|
export function getUserByUsername(username: string): User | undefined {
|
|
const stmt = db.prepare('SELECT u.*, r.name as role_name, r.permissions as role_permissions FROM users u LEFT JOIN roles r ON u.role_id = r.id WHERE u.username = ?')
|
|
const row = stmt.get(username) as any
|
|
if (!row) return undefined
|
|
return {
|
|
...row,
|
|
permissions: JSON.parse(row.role_permissions || '[]')
|
|
}
|
|
}
|
|
|
|
export function getUserByEmail(email: string): User | undefined {
|
|
const stmt = db.prepare('SELECT u.*, r.name as role_name, r.permissions as role_permissions FROM users u LEFT JOIN roles r ON u.role_id = r.id WHERE u.email = ?')
|
|
const row = stmt.get(email) as any
|
|
if (!row) return undefined
|
|
return {
|
|
...row,
|
|
permissions: JSON.parse(row.role_permissions || '[]')
|
|
}
|
|
}
|
|
|
|
export async function createUser(
|
|
username: string,
|
|
passwordHash: string,
|
|
email?: string,
|
|
realName?: string,
|
|
roleId = 2
|
|
): Promise<User> {
|
|
const stmt = db.prepare(
|
|
'INSERT INTO users (username, password_hash, email, real_name, role_id) VALUES (?, ?, ?, ?, ?)'
|
|
)
|
|
const result = stmt.run(username, passwordHash, email || null, realName || null, roleId)
|
|
return getUserById(Number(result.lastInsertRowid))!
|
|
}
|
|
|
|
export async function updateUser(
|
|
id: number,
|
|
data: {
|
|
email?: string
|
|
realName?: string
|
|
roleId?: number
|
|
status?: string
|
|
avatar?: string
|
|
}
|
|
): Promise<User | undefined> {
|
|
const updates: string[] = []
|
|
const params: any[] = []
|
|
|
|
if (data.email !== undefined) {
|
|
updates.push('email = ?')
|
|
params.push(data.email)
|
|
}
|
|
if (data.realName !== undefined) {
|
|
updates.push('real_name = ?')
|
|
params.push(data.realName)
|
|
}
|
|
if (data.roleId !== undefined) {
|
|
updates.push('role_id = ?')
|
|
params.push(data.roleId)
|
|
}
|
|
if (data.status !== undefined) {
|
|
updates.push('status = ?')
|
|
params.push(data.status)
|
|
}
|
|
if (data.avatar !== undefined) {
|
|
updates.push('avatar = ?')
|
|
params.push(data.avatar)
|
|
}
|
|
|
|
if (updates.length === 0) return getUserById(id)
|
|
|
|
updates.push('updated_at = CURRENT_TIMESTAMP')
|
|
params.push(id)
|
|
|
|
const stmt = db.prepare(`UPDATE users SET ${updates.join(', ')} WHERE id = ?`)
|
|
stmt.run(...params)
|
|
return getUserById(id)
|
|
}
|
|
|
|
export async function updatePassword(id: number, newPasswordHash: string): Promise<void> {
|
|
const stmt = db.prepare('UPDATE users SET password_hash = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?')
|
|
stmt.run(newPasswordHash, id)
|
|
}
|
|
|
|
export function deleteUser(id: number): boolean {
|
|
const stmt = db.prepare('DELETE FROM users WHERE id = ?')
|
|
const result = stmt.run(id)
|
|
return result.changes > 0
|
|
}
|
|
|
|
export function updateLastLogin(id: number): void {
|
|
const stmt = db.prepare('UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?')
|
|
stmt.run(id)
|
|
}
|
|
|
|
export function incrementLoginAttempts(id: number, lockedUntil?: Date): void {
|
|
if (lockedUntil) {
|
|
const stmt = db.prepare('UPDATE users SET login_attempts = login_attempts + 1, locked_until = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?')
|
|
stmt.run(lockedUntil.toISOString(), id)
|
|
} else {
|
|
const stmt = db.prepare('UPDATE users SET login_attempts = login_attempts + 1, updated_at = CURRENT_TIMESTAMP WHERE id = ?')
|
|
stmt.run(id)
|
|
}
|
|
}
|
|
|
|
export function resetLoginAttempts(id: number): void {
|
|
const stmt = db.prepare('UPDATE users SET login_attempts = 0, locked_until = NULL, updated_at = CURRENT_TIMESTAMP WHERE id = ?')
|
|
stmt.run(id)
|
|
}
|
|
|
|
export function hashPassword(password: string): string {
|
|
return bcrypt.hashSync(password, SALT_ROUNDS)
|
|
}
|
|
|
|
export function verifyPassword(password: string, hash: string): boolean {
|
|
return bcrypt.compareSync(password, hash)
|
|
}
|
|
|
|
export function validatePassword(password: string): { valid: boolean; message?: string } {
|
|
if (password.length < 6) {
|
|
return { valid: false, message: '密码长度至少6位' }
|
|
}
|
|
if (/^\d+$/.test(password)) {
|
|
return { valid: false, message: '密码不能为纯数字' }
|
|
}
|
|
return { valid: true }
|
|
}
|