mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-12-30 21:49:04 +08:00
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.
247 lines
6.6 KiB
TypeScript
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;
|
|
}
|
|
}
|