138 lines
3.8 KiB
TypeScript
138 lines
3.8 KiB
TypeScript
/**
|
||
* NAC原生认证模块
|
||
* 直连 nac_id MySQL数据库进行用户认证
|
||
*
|
||
* 安全说明:所有密钥通过 secrets.ts 模块读取,绝不硬编码
|
||
*/
|
||
import mysql from "mysql2/promise";
|
||
import bcrypt from "bcryptjs";
|
||
import jwt from "jsonwebtoken";
|
||
import { getNacMysqlUrl, getNacJwtSecret } from "./secrets";
|
||
|
||
let pool: mysql.Pool | null = null;
|
||
|
||
function getNacMysqlPool(): mysql.Pool {
|
||
if (pool) return pool;
|
||
const url = new URL(getNacMysqlUrl());
|
||
pool = mysql.createPool({
|
||
host: url.hostname,
|
||
port: parseInt(url.port || "3306"),
|
||
user: url.username,
|
||
password: url.password,
|
||
database: url.pathname.slice(1),
|
||
waitForConnections: true,
|
||
connectionLimit: 5,
|
||
connectTimeout: 10000,
|
||
});
|
||
return pool;
|
||
}
|
||
|
||
export interface NacUser {
|
||
id: number;
|
||
name: string;
|
||
email: string;
|
||
kyc_level: number;
|
||
node_status: string;
|
||
is_active: number;
|
||
role: string; // admin | reviewer | legal
|
||
}
|
||
|
||
/**
|
||
* 根据用户的KYC等级和节点状态确定管理后台角色
|
||
* - constitutional节点 + kyc_level>=2 → admin
|
||
* - kyc_level>=1 → legal
|
||
* - 其他 → reviewer
|
||
*/
|
||
function resolveRole(kyc_level: number, node_status: string): string {
|
||
if (kyc_level >= 2 && node_status === "constitutional") return "admin";
|
||
if (kyc_level >= 1) return "legal";
|
||
return "reviewer";
|
||
}
|
||
|
||
/**
|
||
* 使用NAC账户(email+password)登录
|
||
* 返回用户信息和JWT令牌
|
||
*/
|
||
export async function loginWithNacCredentials(
|
||
email: string,
|
||
password: string
|
||
): Promise<{ user: NacUser; token: string } | null> {
|
||
const pool = getNacMysqlPool();
|
||
|
||
const [rows] = await pool.execute<mysql.RowDataPacket[]>(
|
||
"SELECT id, name, email, password, kyc_level, node_status, is_active FROM users WHERE email = ? AND is_active = 1",
|
||
[email]
|
||
);
|
||
|
||
if (rows.length === 0) return null;
|
||
const dbUser = rows[0];
|
||
|
||
const valid = await bcrypt.compare(password, dbUser.password);
|
||
if (!valid) return null;
|
||
|
||
const role = resolveRole(dbUser.kyc_level, dbUser.node_status);
|
||
const user: NacUser = {
|
||
id: dbUser.id,
|
||
name: dbUser.name,
|
||
email: dbUser.email,
|
||
kyc_level: dbUser.kyc_level,
|
||
node_status: dbUser.node_status,
|
||
is_active: dbUser.is_active,
|
||
role,
|
||
};
|
||
|
||
const token = jwt.sign(
|
||
{ id: user.id, email: user.email, role: user.role },
|
||
getNacJwtSecret(),
|
||
{ expiresIn: "24h" }
|
||
);
|
||
|
||
return { user, token };
|
||
}
|
||
|
||
/**
|
||
* 验证JWT令牌有效性
|
||
*/
|
||
export function verifyNacToken(token: string): { id: number; email: string; role: string } | null {
|
||
try {
|
||
return jwt.verify(token, getNacJwtSecret()) as { id: number; email: string; role: string };
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 通过ID查询NAC用户
|
||
*/
|
||
export async function getNacUserById(id: number): Promise<NacUser | null> {
|
||
const pool = getNacMysqlPool();
|
||
const [rows] = await pool.execute<mysql.RowDataPacket[]>(
|
||
"SELECT id, name, email, kyc_level, node_status, is_active FROM users WHERE id = ? AND is_active = 1",
|
||
[id]
|
||
);
|
||
if (rows.length === 0) return null;
|
||
const r = rows[0];
|
||
return { ...r, role: resolveRole(r.kyc_level, r.node_status) } as NacUser;
|
||
}
|
||
|
||
/**
|
||
* 获取NAC用户列表(管理员功能)
|
||
*/
|
||
export async function listNacUsers(limit = 50, offset = 0): Promise<NacUser[]> {
|
||
const pool = getNacMysqlPool();
|
||
const [rows] = await pool.execute<mysql.RowDataPacket[]>(
|
||
"SELECT id, name, email, kyc_level, node_status, is_active, created_at, last_login_at FROM users ORDER BY id DESC LIMIT ? OFFSET ?",
|
||
[limit, offset]
|
||
);
|
||
return rows.map((r) => ({ ...r, role: resolveRole(r.kyc_level, r.node_status) } as NacUser));
|
||
}
|
||
|
||
/**
|
||
* 获取NAC用户总数
|
||
*/
|
||
export async function getNacUserCount(): Promise<number> {
|
||
const pool = getNacMysqlPool();
|
||
const [rows] = await pool.execute<mysql.RowDataPacket[]>("SELECT COUNT(*) as cnt FROM users");
|
||
return (rows[0] as mysql.RowDataPacket)?.cnt || 0;
|
||
}
|