NapCatQQ/packages/napcat-webui-frontend/src/controllers/webui_manager.ts
手瓜一十雪 afb6ef421a Add Passkey (WebAuthn) authentication support
Introduces Passkey (WebAuthn) registration and authentication to both backend and frontend. Backend adds new API endpoints, middleware exceptions, and a PasskeyHelper for credential management using @simplewebauthn/server. Frontend integrates @simplewebauthn/browser, updates login and config pages for Passkey registration and login flows, and adds related UI and controller methods.
2025-11-22 16:00:32 +08:00

247 lines
6.6 KiB
TypeScript

import CryptoJS from 'crypto-js';
import { EventSourcePolyfill } from 'event-source-polyfill';
import { LogLevel } from '@/const/enum';
import { serverRequest } from '@/utils/request';
export interface Log {
level: LogLevel;
message: string;
}
export default class WebUIManager {
public static async checkWebUiLogined () {
const { data } =
await serverRequest.post<ServerResponse<boolean>>('/auth/check');
return data.data;
}
public static async loginWithToken (token: string) {
const sha256 = CryptoJS.SHA256(token + '.napcat').toString();
const { data } = await serverRequest.post<ServerResponse<AuthResponse>>(
'/auth/login',
{ hash: sha256 }
);
return data.data.Credential;
}
public static async changePassword (oldToken: string, newToken: string) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/auth/update_token',
{ oldToken, newToken }
);
return data.data;
}
public static async proxy<T> (url = '') {
const data = await serverRequest.get<ServerResponse<string>>(
'/base/proxy?url=' + encodeURIComponent(url)
);
data.data.data = JSON.parse(data.data.data);
return data.data as ServerResponse<T>;
}
public static async GetNapCatVersion () {
const { data } =
await serverRequest.get<ServerResponse<PackageInfo>>('/base/GetNapCatVersion');
return data.data;
}
public static async getLatestTag () {
const { data } =
await serverRequest.get<ServerResponse<string>>('/base/getLatestTag');
return data.data;
}
public static async UpdateNapCat () {
const { data } = await serverRequest.post<ServerResponse<any>>(
'/UpdateNapCat/update',
{},
{ timeout: 60000 } // 1分钟超时
);
return data;
}
public static async getQQVersion () {
const { data } =
await serverRequest.get<ServerResponse<string>>('/base/QQVersion');
return data.data;
}
public static async getThemeConfig () {
const { data } =
await serverRequest.get<ServerResponse<ThemeConfig>>('/base/Theme');
return data.data;
}
public static async setThemeConfig (theme: ThemeConfig) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/base/SetTheme',
{ theme }
);
return data.data;
}
public static async getLogList () {
const { data } =
await serverRequest.get<ServerResponse<string[]>>('/Log/GetLogList');
return data.data;
}
public static async getLogContent (logName: string) {
const { data } = await serverRequest.get<ServerResponse<string>>(
`/Log/GetLog?id=${logName}`
);
return data.data;
}
public static getRealTimeLogs (writer: (data: Log[]) => void) {
const token = localStorage.getItem('token');
if (!token) {
throw new Error('未登录');
}
const _token = JSON.parse(token);
const eventSource = new EventSourcePolyfill('/api/Log/GetLogRealTime', {
headers: {
Authorization: `Bearer ${_token}`,
Accept: 'text/event-stream',
},
withCredentials: true,
});
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
data.message = data.message.replace(/\n/g, '\r\n');
writer([data]);
} catch (error) {
console.error(error);
}
};
eventSource.onerror = (error) => {
console.error('SSE连接出错:', error);
eventSource.close();
};
return eventSource;
}
public static getSystemStatus (writer: (data: SystemStatus) => void) {
const token = localStorage.getItem('token');
if (!token) {
throw new Error('未登录');
}
const _token = JSON.parse(token);
const eventSource = new EventSourcePolyfill(
'/api/base/GetSysStatusRealTime',
{
headers: {
Authorization: `Bearer ${_token}`,
Accept: 'text/event-stream',
},
withCredentials: true,
}
);
eventSource.onmessage = (event) => {
try {
const data = JSON.parse(event.data) as SystemStatus;
writer(data);
} catch (error) {
console.error(error);
}
};
eventSource.onerror = (error) => {
console.error('SSE连接出错:', error);
eventSource.close();
};
return eventSource;
}
// 获取WebUI基础配置
public static async getWebUIConfig () {
const { data } = await serverRequest.get<ServerResponse<WebUIConfig>>(
'/WebUIConfig/GetConfig'
);
return data.data;
}
// 更新WebUI基础配置
public static async updateWebUIConfig (config: Partial<WebUIConfig>) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/WebUIConfig/UpdateConfig',
config
);
return data.data;
}
// 获取是否禁用WebUI
public static async getDisableWebUI () {
const { data } = await serverRequest.get<ServerResponse<boolean>>(
'/WebUIConfig/GetDisableWebUI'
);
return data.data;
}
// 更新是否禁用WebUI
public static async updateDisableWebUI (disable: boolean) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/WebUIConfig/UpdateDisableWebUI',
{ disable }
);
return data.data;
}
// 获取是否禁用非局域网访问
public static async getDisableNonLANAccess () {
const { data } = await serverRequest.get<ServerResponse<boolean>>(
'/WebUIConfig/GetDisableNonLANAccess'
);
return data.data;
}
// 更新是否禁用非局域网访问
public static async updateDisableNonLANAccess (disable: boolean) {
const { data } = await serverRequest.post<ServerResponse<boolean>>(
'/WebUIConfig/UpdateDisableNonLANAccess',
{ disable }
);
return data.data;
}
// Passkey相关方法
public static async generatePasskeyRegistrationOptions () {
const { data } = await serverRequest.post<ServerResponse<any>>(
'/auth/passkey/generate-registration-options'
);
return data.data;
}
public static async verifyPasskeyRegistration (response: any) {
const { data } = await serverRequest.post<ServerResponse<any>>(
'/auth/passkey/verify-registration',
{ response }
);
return data.data;
}
public static async generatePasskeyAuthenticationOptions () {
const { data } = await serverRequest.post<ServerResponse<any>>(
'/auth/passkey/generate-authentication-options'
);
return data.data;
}
public static async verifyPasskeyAuthentication (response: any) {
const { data } = await serverRequest.post<ServerResponse<any>>(
'/auth/passkey/verify-authentication',
{ response }
);
return data.data;
}
}