NAC_Blockchain/services/nac-admin/server/nacAuth.ts

138 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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;
}