mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-11 23:40:24 +00:00
* feat: pnpm new * Refactor build and release workflows, update dependencies Switch build scripts and workflows from npm to pnpm, update build and artifact paths, and simplify release workflow by removing version detection and changelog steps. Add new dependencies (silk-wasm, express, ws, node-pty-prebuilt-multiarch), update exports in package.json files, and add vite config for napcat-framework. Also, rename manifest.json for framework package and fix static asset copying in shell build config.
153 lines
5.0 KiB
TypeScript
153 lines
5.0 KiB
TypeScript
import { RequestHandler } from 'express';
|
||
|
||
import { WebUiConfig, getInitialWebUiToken, setInitialWebUiToken } from '@/napcat-webui-backend/index';
|
||
|
||
import { AuthHelper } from '@/napcat-webui-backend/helper/SignToken';
|
||
import { WebUiDataRuntime } from '@/napcat-webui-backend/helper/Data';
|
||
import { sendSuccess, sendError } from '@/napcat-webui-backend/utils/response';
|
||
import { isEmpty } from '@/napcat-webui-backend/utils/check';
|
||
|
||
// 登录
|
||
export const LoginHandler: RequestHandler = async (req, res) => {
|
||
// 获取WebUI配置
|
||
const WebUiConfigData = await WebUiConfig.GetWebUIConfig();
|
||
// 获取请求体中的hash
|
||
const { hash } = req.body;
|
||
// 获取客户端IP
|
||
const clientIP = req.ip || req.socket.remoteAddress || '';
|
||
|
||
// 如果token为空,返回错误信息
|
||
if (isEmpty(hash)) {
|
||
return sendError(res, 'token is empty');
|
||
}
|
||
// 检查登录频率
|
||
if (!WebUiDataRuntime.checkLoginRate(clientIP, WebUiConfigData.loginRate)) {
|
||
return sendError(res, 'login rate limit');
|
||
}
|
||
// 使用启动时缓存的token进行验证,而不是动态读取配置文件
|
||
const initialToken = getInitialWebUiToken();
|
||
if (!initialToken) {
|
||
return sendError(res, 'Server token not initialized');
|
||
}
|
||
// 验证初始token hash是否等于提交的token hash
|
||
if (!AuthHelper.comparePasswordHash(initialToken, hash)) {
|
||
return sendError(res, 'token is invalid');
|
||
}
|
||
|
||
// 签发凭证
|
||
const signCredential = Buffer.from(JSON.stringify(AuthHelper.signCredential(hash))).toString(
|
||
'base64'
|
||
);
|
||
// 返回成功信息
|
||
return sendSuccess(res, {
|
||
Credential: signCredential,
|
||
});
|
||
};
|
||
|
||
// 退出登录
|
||
export const LogoutHandler: RequestHandler = async (req, res) => {
|
||
const authorization = req.headers.authorization;
|
||
try {
|
||
const CredentialBase64: string = authorization?.split(' ')[1] as string;
|
||
const Credential = JSON.parse(Buffer.from(CredentialBase64, 'base64').toString());
|
||
AuthHelper.revokeCredential(Credential);
|
||
return sendSuccess(res, 'Logged out successfully');
|
||
} catch (_e) {
|
||
return sendError(res, 'Logout failed');
|
||
}
|
||
};
|
||
|
||
// 检查登录状态
|
||
export const checkHandler: RequestHandler = async (req, res) => {
|
||
// 获取请求头中的Authorization
|
||
const authorization = req.headers.authorization;
|
||
// 检查凭证
|
||
try {
|
||
// 从Authorization中获取凭证
|
||
const CredentialBase64: string = authorization?.split(' ')[1] as string;
|
||
// 解析凭证
|
||
const Credential = JSON.parse(Buffer.from(CredentialBase64, 'base64').toString());
|
||
|
||
// 检查凭证是否已被注销
|
||
if (AuthHelper.isCredentialRevoked(Credential)) {
|
||
return sendError(res, 'Token has been revoked');
|
||
}
|
||
|
||
// 使用启动时缓存的token进行验证
|
||
const initialToken = getInitialWebUiToken();
|
||
if (!initialToken) {
|
||
return sendError(res, 'Server token not initialized');
|
||
}
|
||
// 验证凭证是否在一小时内有效
|
||
const valid = AuthHelper.validateCredentialWithinOneHour(initialToken, Credential);
|
||
// 返回成功信息
|
||
if (valid) return sendSuccess(res, null);
|
||
// 返回错误信息
|
||
return sendError(res, 'Authorization Failed');
|
||
} catch (_e) {
|
||
// 返回错误信息
|
||
return sendError(res, 'Authorization Failed');
|
||
}
|
||
};
|
||
|
||
// 修改密码(token)
|
||
export const UpdateTokenHandler: RequestHandler = async (req, res) => {
|
||
const { oldToken, newToken } = req.body;
|
||
const authorization = req.headers.authorization;
|
||
|
||
if (isEmpty(newToken)) {
|
||
return sendError(res, 'newToken is empty');
|
||
}
|
||
|
||
// 强制要求旧密码
|
||
if (isEmpty(oldToken)) {
|
||
return sendError(res, 'oldToken is required');
|
||
}
|
||
|
||
// 检查新旧密码是否相同
|
||
if (oldToken === newToken) {
|
||
return sendError(res, '新密码不能与旧密码相同');
|
||
}
|
||
|
||
// 检查新密码强度
|
||
if (newToken.length < 6) {
|
||
return sendError(res, '新密码至少需要6个字符');
|
||
}
|
||
|
||
// 检查是否包含字母
|
||
if (!/[a-zA-Z]/.test(newToken)) {
|
||
return sendError(res, '新密码必须包含字母');
|
||
}
|
||
|
||
// 检查是否包含数字
|
||
if (!/[0-9]/.test(newToken)) {
|
||
return sendError(res, '新密码必须包含数字');
|
||
}
|
||
|
||
try {
|
||
// 注销当前的Token
|
||
if (authorization) {
|
||
const CredentialBase64: string = authorization.split(' ')[1] as string;
|
||
const Credential = JSON.parse(Buffer.from(CredentialBase64, 'base64').toString());
|
||
AuthHelper.revokeCredential(Credential);
|
||
}
|
||
|
||
// 使用启动时缓存的token进行验证
|
||
const initialToken = getInitialWebUiToken();
|
||
if (!initialToken) {
|
||
return sendError(res, 'Server token not initialized');
|
||
}
|
||
if (initialToken !== oldToken) {
|
||
return sendError(res, '旧 token 不匹配');
|
||
}
|
||
// 直接更新配置文件中的token,不需要通过WebUiConfig.UpdateToken方法
|
||
await WebUiConfig.UpdateWebUIConfig({ token: newToken });
|
||
// 更新内存中的缓存token,使新密码立即生效
|
||
setInitialWebUiToken(newToken);
|
||
|
||
return sendSuccess(res, 'Token updated successfully');
|
||
} catch (e: any) {
|
||
return sendError(res, `Failed to update token: ${e.message}`);
|
||
}
|
||
};
|