111
This commit is contained in:
578
dist/services/AuthService.js
vendored
Normal file
578
dist/services/AuthService.js
vendored
Normal file
@@ -0,0 +1,578 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AuthService = void 0;
|
||||
// 确保环境变量已加载(如果还没有加载)
|
||||
const dotenv_1 = __importDefault(require("dotenv"));
|
||||
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
||||
const bcryptjs_1 = __importDefault(require("bcryptjs"));
|
||||
const path_1 = __importDefault(require("path"));
|
||||
// pkg 打包后,需要从可执行文件所在目录读取 .env 文件
|
||||
// @ts-ignore - process.pkg 是 pkg 打包后添加的属性
|
||||
const envPath = process.pkg
|
||||
? path_1.default.join(path_1.default.dirname(process.execPath), '.env')
|
||||
: path_1.default.join(process.cwd(), '.env');
|
||||
dotenv_1.default.config({ path: envPath });
|
||||
const fs_1 = __importDefault(require("fs"));
|
||||
const crypto_1 = __importDefault(require("crypto"));
|
||||
const Logger_1 = __importDefault(require("../utils/Logger"));
|
||||
/**
|
||||
* 认证服务
|
||||
*/
|
||||
class AuthService {
|
||||
constructor() {
|
||||
this.users = new Map();
|
||||
this.logger = new Logger_1.default('AuthService');
|
||||
// 确保环境变量已加载(双重保险)
|
||||
// 注意:顶部的 dotenv.config() 已经加载了,这里不需要重复加载
|
||||
// 从环境变量获取配置,如果没有则使用默认值
|
||||
this.JWT_SECRET = process.env.JWT_SECRET || '838AE2CD136220F0758FFCD40A335E82';
|
||||
this.JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h';
|
||||
this.DEFAULT_USERNAME = process.env.DEFAULT_USERNAME || '';
|
||||
this.DEFAULT_PASSWORD = process.env.DEFAULT_PASSWORD || '';
|
||||
// 超级管理员账号配置(从环境变量获取,如果没有则使用默认值)
|
||||
this.SUPERADMIN_USERNAME = process.env.SUPERADMIN_USERNAME || 'superadmin';
|
||||
this.SUPERADMIN_PASSWORD = process.env.SUPERADMIN_PASSWORD || 'superadmin123456';
|
||||
// 调试日志:显示加载的环境变量(不显示敏感信息)
|
||||
const envLoaded = process.env.SUPERADMIN_USERNAME !== undefined;
|
||||
this.logger.info(`环境变量加载状态:`);
|
||||
this.logger.info(` - SUPERADMIN_USERNAME: ${this.SUPERADMIN_USERNAME} ${envLoaded ? '(从.env加载)' : '(使用默认值)'}`);
|
||||
this.logger.info(` - SUPERADMIN_PASSWORD: ${process.env.SUPERADMIN_PASSWORD ? '已从.env加载' : '未设置(使用默认值)'}`);
|
||||
this.logger.info(` - JWT_SECRET: ${process.env.JWT_SECRET ? '已从.env加载' : '未设置(使用默认值)'}`);
|
||||
// 设置初始化锁文件路径(pkg 打包后,从可执行文件所在目录)
|
||||
// @ts-ignore - process.pkg 是 pkg 打包后添加的属性
|
||||
const basePath = process.pkg
|
||||
? path_1.default.dirname(process.execPath)
|
||||
: process.cwd();
|
||||
this.INIT_LOCK_FILE = path_1.default.join(basePath, '.system_initialized');
|
||||
// 设置用户数据文件路径
|
||||
this.USER_DATA_FILE = path_1.default.join(basePath, '.user_data.json');
|
||||
this.logger.info(`认证服务配置完成,锁文件: ${this.INIT_LOCK_FILE},用户数据: ${this.USER_DATA_FILE}`);
|
||||
// 注意:异步初始化在 initialize() 方法中执行
|
||||
}
|
||||
/**
|
||||
* 初始化认证服务(异步)
|
||||
* 必须在创建 AuthService 实例后调用此方法
|
||||
*/
|
||||
async initialize() {
|
||||
try {
|
||||
this.logger.info('开始初始化认证服务...');
|
||||
// 先初始化或恢复用户数据
|
||||
await this.initializeOrRestoreUsers();
|
||||
// 然后初始化超级管理员
|
||||
await this.initializeSuperAdmin();
|
||||
this.logger.info('认证服务初始化完成');
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('认证服务初始化失败:', error);
|
||||
// 即使初始化失败,也尝试创建超级管理员作为备用
|
||||
try {
|
||||
await this.initializeSuperAdmin();
|
||||
}
|
||||
catch (superAdminError) {
|
||||
this.logger.error('创建超级管理员失败:', superAdminError);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 初始化或恢复用户数据
|
||||
*/
|
||||
async initializeOrRestoreUsers() {
|
||||
try {
|
||||
if (this.isInitialized()) {
|
||||
// 系统已初始化,从文件恢复用户数据
|
||||
await this.loadUsersFromFile();
|
||||
this.logger.info('用户数据已从文件恢复');
|
||||
}
|
||||
else {
|
||||
// 系统未初始化,创建默认用户
|
||||
await this.initializeDefaultUser();
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('初始化或恢复用户数据失败:', error);
|
||||
// 如果恢复失败,尝试创建默认用户作为备用
|
||||
await this.initializeDefaultUser();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 初始化默认管理员用户
|
||||
*/
|
||||
async initializeDefaultUser() {
|
||||
try {
|
||||
const passwordHash = await bcryptjs_1.default.hash(this.DEFAULT_PASSWORD, 10);
|
||||
const defaultUser = {
|
||||
id: 'admin',
|
||||
username: this.DEFAULT_USERNAME,
|
||||
passwordHash,
|
||||
role: 'admin',
|
||||
createdAt: new Date()
|
||||
};
|
||||
this.users.set(this.DEFAULT_USERNAME, defaultUser);
|
||||
this.logger.info(`默认用户已创建: ${this.DEFAULT_USERNAME}`);
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('初始化默认用户失败:', error);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 初始化超级管理员账号
|
||||
*/
|
||||
async initializeSuperAdmin() {
|
||||
try {
|
||||
// 如果超级管理员已存在,检查是否需要更新
|
||||
if (this.users.has(this.SUPERADMIN_USERNAME)) {
|
||||
const existingUser = this.users.get(this.SUPERADMIN_USERNAME);
|
||||
let needsUpdate = false;
|
||||
// 如果现有用户不是超级管理员,更新为超级管理员
|
||||
if (existingUser.role !== 'superadmin') {
|
||||
existingUser.role = 'superadmin';
|
||||
needsUpdate = true;
|
||||
this.logger.info(`用户 ${this.SUPERADMIN_USERNAME} 已更新为超级管理员`);
|
||||
}
|
||||
// 🆕 如果环境变量中设置了密码,始终用环境变量中的密码更新(确保.env配置生效)
|
||||
// 通过验证当前密码哈希与环境变量密码是否匹配来判断是否需要更新
|
||||
if (this.SUPERADMIN_PASSWORD) {
|
||||
const isCurrentPassword = await bcryptjs_1.default.compare(this.SUPERADMIN_PASSWORD, existingUser.passwordHash);
|
||||
if (!isCurrentPassword) {
|
||||
// 环境变量中的密码与当前密码不同,更新密码
|
||||
existingUser.passwordHash = await bcryptjs_1.default.hash(this.SUPERADMIN_PASSWORD, 10);
|
||||
needsUpdate = true;
|
||||
this.logger.info(`超级管理员密码已更新(从.env文件加载新密码)`);
|
||||
}
|
||||
else {
|
||||
this.logger.debug(`超级管理员密码与.env配置一致,无需更新`);
|
||||
}
|
||||
}
|
||||
if (needsUpdate) {
|
||||
this.users.set(this.SUPERADMIN_USERNAME, existingUser);
|
||||
await this.saveUsersToFile();
|
||||
}
|
||||
return;
|
||||
}
|
||||
// 创建超级管理员账号
|
||||
const passwordHash = await bcryptjs_1.default.hash(this.SUPERADMIN_PASSWORD, 10);
|
||||
const superAdminUser = {
|
||||
id: 'superadmin',
|
||||
username: this.SUPERADMIN_USERNAME,
|
||||
passwordHash,
|
||||
role: 'superadmin',
|
||||
createdAt: new Date()
|
||||
};
|
||||
this.users.set(this.SUPERADMIN_USERNAME, superAdminUser);
|
||||
this.logger.info(`超级管理员账号已创建: ${this.SUPERADMIN_USERNAME}`);
|
||||
// 保存用户数据到文件
|
||||
try {
|
||||
await this.saveUsersToFile();
|
||||
}
|
||||
catch (saveError) {
|
||||
this.logger.error('保存超级管理员数据失败:', saveError);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('初始化超级管理员失败:', error);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 保存用户数据到文件
|
||||
*/
|
||||
async saveUsersToFile() {
|
||||
try {
|
||||
const usersData = Array.from(this.users.values());
|
||||
const data = {
|
||||
version: '1.0.0',
|
||||
savedAt: new Date().toISOString(),
|
||||
users: usersData
|
||||
};
|
||||
fs_1.default.writeFileSync(this.USER_DATA_FILE, JSON.stringify(data, null, 2));
|
||||
this.logger.debug('用户数据已保存到文件');
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('保存用户数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 从文件加载用户数据
|
||||
*/
|
||||
async loadUsersFromFile() {
|
||||
try {
|
||||
if (!fs_1.default.existsSync(this.USER_DATA_FILE)) {
|
||||
this.logger.warn('用户数据文件不存在,将创建空用户列表');
|
||||
return;
|
||||
}
|
||||
const fileContent = fs_1.default.readFileSync(this.USER_DATA_FILE, 'utf8');
|
||||
const data = JSON.parse(fileContent);
|
||||
this.users.clear();
|
||||
if (data.users && Array.isArray(data.users)) {
|
||||
for (const userData of data.users) {
|
||||
// 恢复Date对象
|
||||
const user = {
|
||||
...userData,
|
||||
role: userData.role || 'admin', // 兼容旧数据,默认为admin
|
||||
createdAt: new Date(userData.createdAt),
|
||||
lastLoginAt: userData.lastLoginAt ? new Date(userData.lastLoginAt) : undefined
|
||||
};
|
||||
this.users.set(user.username, user);
|
||||
}
|
||||
this.logger.info(`已加载 ${data.users.length} 个用户`);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('加载用户数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
async login(username, password) {
|
||||
try {
|
||||
this.logger.info(`用户登录尝试: ${username}`);
|
||||
// 查找用户
|
||||
const user = this.users.get(username);
|
||||
if (!user) {
|
||||
this.logger.warn(`用户不存在: ${username}`);
|
||||
return {
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
};
|
||||
}
|
||||
// 验证密码
|
||||
const isPasswordValid = await bcryptjs_1.default.compare(password, user.passwordHash);
|
||||
if (!isPasswordValid) {
|
||||
this.logger.warn(`密码错误: ${username}`);
|
||||
return {
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
};
|
||||
}
|
||||
// 更新最后登录时间
|
||||
user.lastLoginAt = new Date();
|
||||
// 保存用户数据到文件(异步但不影响登录流程)
|
||||
this.saveUsersToFile().catch(saveError => {
|
||||
this.logger.error('保存用户数据失败:', saveError);
|
||||
});
|
||||
// 生成JWT token(包含用户角色信息)
|
||||
const token = jsonwebtoken_1.default.sign({
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
role: user.role || 'admin' // 包含用户角色
|
||||
}, this.JWT_SECRET, {
|
||||
expiresIn: this.JWT_EXPIRES_IN,
|
||||
issuer: 'remote-control-server',
|
||||
audience: 'remote-control-client'
|
||||
});
|
||||
this.logger.info(`用户登录成功: ${username}`);
|
||||
return {
|
||||
success: true,
|
||||
message: '登录成功',
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role || 'admin',
|
||||
lastLoginAt: user.lastLoginAt
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('登录过程发生错误:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: '登录失败,请稍后重试'
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 验证JWT token
|
||||
*/
|
||||
verifyToken(token) {
|
||||
try {
|
||||
const decoded = jsonwebtoken_1.default.verify(token, this.JWT_SECRET, {
|
||||
issuer: 'remote-control-server',
|
||||
audience: 'remote-control-client'
|
||||
});
|
||||
const user = this.users.get(decoded.username);
|
||||
if (!user) {
|
||||
return {
|
||||
valid: false,
|
||||
error: '用户不存在'
|
||||
};
|
||||
}
|
||||
return {
|
||||
valid: true,
|
||||
user: {
|
||||
id: decoded.userId,
|
||||
username: decoded.username,
|
||||
role: user.role || 'admin' // 返回用户角色
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.warn('Token验证失败:', error.message);
|
||||
if (error.name === 'TokenExpiredError') {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Token已过期'
|
||||
};
|
||||
}
|
||||
else if (error.name === 'JsonWebTokenError') {
|
||||
return {
|
||||
valid: false,
|
||||
error: 'Token无效'
|
||||
};
|
||||
}
|
||||
else {
|
||||
return {
|
||||
valid: false,
|
||||
error: '验证失败'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取用户信息
|
||||
*/
|
||||
getUserByUsername(username) {
|
||||
return this.users.get(username);
|
||||
}
|
||||
/**
|
||||
* 创建新用户(用于扩展功能)
|
||||
*/
|
||||
async createUser(username, password) {
|
||||
try {
|
||||
if (this.users.has(username)) {
|
||||
this.logger.warn(`用户已存在: ${username}`);
|
||||
return false;
|
||||
}
|
||||
const passwordHash = await bcryptjs_1.default.hash(password, 10);
|
||||
const user = {
|
||||
id: `user_${Date.now()}`,
|
||||
username,
|
||||
passwordHash,
|
||||
role: 'admin', // 新创建的用户默认为普通管理员
|
||||
createdAt: new Date()
|
||||
};
|
||||
this.users.set(username, user);
|
||||
// 保存用户数据到文件
|
||||
try {
|
||||
await this.saveUsersToFile();
|
||||
}
|
||||
catch (saveError) {
|
||||
this.logger.error('保存用户数据失败:', saveError);
|
||||
}
|
||||
this.logger.info(`新用户已创建: ${username}`);
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('创建用户失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 更改用户密码(用于扩展功能)
|
||||
*/
|
||||
async changePassword(username, oldPassword, newPassword) {
|
||||
try {
|
||||
const user = this.users.get(username);
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
const isOldPasswordValid = await bcryptjs_1.default.compare(oldPassword, user.passwordHash);
|
||||
if (!isOldPasswordValid) {
|
||||
return false;
|
||||
}
|
||||
const newPasswordHash = await bcryptjs_1.default.hash(newPassword, 10);
|
||||
user.passwordHash = newPasswordHash;
|
||||
// 保存用户数据到文件
|
||||
try {
|
||||
await this.saveUsersToFile();
|
||||
}
|
||||
catch (saveError) {
|
||||
this.logger.error('保存用户数据失败:', saveError);
|
||||
}
|
||||
this.logger.info(`用户密码已更改: ${username}`);
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('更改密码失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取所有用户(用于管理功能)
|
||||
*/
|
||||
getAllUsers() {
|
||||
return Array.from(this.users.values()).map(user => ({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role || 'admin',
|
||||
createdAt: user.createdAt,
|
||||
lastLoginAt: user.lastLoginAt
|
||||
}));
|
||||
}
|
||||
/**
|
||||
* 检查用户是否为超级管理员
|
||||
*/
|
||||
isSuperAdmin(username) {
|
||||
const user = this.users.get(username);
|
||||
return user?.role === 'superadmin';
|
||||
}
|
||||
/**
|
||||
* 获取超级管理员用户名
|
||||
*/
|
||||
getSuperAdminUsername() {
|
||||
return this.SUPERADMIN_USERNAME;
|
||||
}
|
||||
/**
|
||||
* 检查系统是否已初始化(通过检查锁文件)
|
||||
*/
|
||||
isInitialized() {
|
||||
try {
|
||||
return fs_1.default.existsSync(this.INIT_LOCK_FILE);
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('检查初始化锁文件失败:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取初始化锁文件路径
|
||||
*/
|
||||
getInitLockFilePath() {
|
||||
return this.INIT_LOCK_FILE;
|
||||
}
|
||||
/**
|
||||
* 生成唯一标识符
|
||||
*/
|
||||
generateUniqueId() {
|
||||
// 生成32字节的随机字符串,转换为64字符的十六进制字符串
|
||||
return crypto_1.default.randomBytes(32).toString('hex');
|
||||
}
|
||||
/**
|
||||
* 获取初始化信息(如果已初始化)
|
||||
*/
|
||||
getInitializationInfo() {
|
||||
try {
|
||||
if (!this.isInitialized()) {
|
||||
return null;
|
||||
}
|
||||
const content = fs_1.default.readFileSync(this.INIT_LOCK_FILE, 'utf8');
|
||||
const info = JSON.parse(content);
|
||||
// 如果旧版本没有唯一标识符,生成一个并更新
|
||||
if (!info.uniqueId) {
|
||||
info.uniqueId = this.generateUniqueId();
|
||||
try {
|
||||
fs_1.default.writeFileSync(this.INIT_LOCK_FILE, JSON.stringify(info, null, 2));
|
||||
this.logger.info('已为已初始化的系统生成唯一标识符');
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('更新唯一标识符失败:', error);
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('读取初始化信息失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 获取系统唯一标识符
|
||||
*/
|
||||
getSystemUniqueId() {
|
||||
const initInfo = this.getInitializationInfo();
|
||||
return initInfo?.uniqueId || null;
|
||||
}
|
||||
/**
|
||||
* 初始化系统,设置管理员账号
|
||||
*/
|
||||
async initializeSystem(username, password) {
|
||||
try {
|
||||
// 检查是否已经初始化(通过检查锁文件)
|
||||
if (this.isInitialized()) {
|
||||
return {
|
||||
success: false,
|
||||
message: '系统已经初始化,无法重复初始化'
|
||||
};
|
||||
}
|
||||
// 验证输入参数
|
||||
if (!username || username.trim().length < 3) {
|
||||
return {
|
||||
success: false,
|
||||
message: '用户名至少需要3个字符'
|
||||
};
|
||||
}
|
||||
if (!password || password.length < 6) {
|
||||
return {
|
||||
success: false,
|
||||
message: '密码至少需要6个字符'
|
||||
};
|
||||
}
|
||||
const trimmedUsername = username.trim();
|
||||
// 检查用户名是否已存在
|
||||
if (this.users.has(trimmedUsername)) {
|
||||
return {
|
||||
success: false,
|
||||
message: '用户名已存在'
|
||||
};
|
||||
}
|
||||
// 创建管理员用户
|
||||
const passwordHash = await bcryptjs_1.default.hash(password, 10);
|
||||
const adminUser = {
|
||||
id: 'admin_' + Date.now(),
|
||||
username: trimmedUsername,
|
||||
passwordHash,
|
||||
createdAt: new Date()
|
||||
};
|
||||
// 清除默认用户,添加新的管理员用户
|
||||
this.users.clear();
|
||||
this.users.set(trimmedUsername, adminUser);
|
||||
// 保存用户数据到文件
|
||||
try {
|
||||
await this.saveUsersToFile();
|
||||
this.logger.info('用户数据已保存到文件');
|
||||
}
|
||||
catch (saveError) {
|
||||
this.logger.error('保存用户数据失败:', saveError);
|
||||
// 即使保存失败,也不影响初始化过程,但会记录错误
|
||||
}
|
||||
// 生成唯一标识符
|
||||
const uniqueId = this.generateUniqueId();
|
||||
this.logger.info(`生成系统唯一标识符: ${uniqueId.substring(0, 8)}...`);
|
||||
// 创建初始化锁文件
|
||||
try {
|
||||
const initInfo = {
|
||||
initializedAt: new Date().toISOString(),
|
||||
adminUsername: trimmedUsername,
|
||||
version: '1.0.0',
|
||||
uniqueId: uniqueId // 系统唯一标识符
|
||||
};
|
||||
fs_1.default.writeFileSync(this.INIT_LOCK_FILE, JSON.stringify(initInfo, null, 2));
|
||||
this.logger.info(`系统已初始化,管理员用户: ${trimmedUsername},唯一标识符: ${uniqueId.substring(0, 8)}...,锁文件已创建: ${this.INIT_LOCK_FILE}`);
|
||||
}
|
||||
catch (lockError) {
|
||||
this.logger.error('创建初始化锁文件失败:', lockError);
|
||||
// 即使锁文件创建失败,也不影响初始化过程
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
message: '系统初始化成功',
|
||||
uniqueId: uniqueId // 返回系统唯一标识符
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
this.logger.error('系统初始化失败:', error);
|
||||
return {
|
||||
success: false,
|
||||
message: '系统初始化失败,请稍后重试'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.AuthService = AuthService;
|
||||
exports.default = AuthService;
|
||||
//# sourceMappingURL=AuthService.js.map
|
||||
Reference in New Issue
Block a user