4a382144d1
- lint-staged: 16.2.7 → 17.0.5 - jsdom: 25.0.0 → 29.1.1 - @vitejs/plugin-react: 4.3.4 → 6.0.2 - vitest: 2.0.0 → 4.1.7 - @vitest/coverage-v8: upgraded to match vitest - @webext-core/messaging: 2.3.0 → 3.0.1 - eslint: 9.39.2 → 10.4.0 - @eslint/js: added as new dependency - @types/marked: removed (marked now provides its own types) Fixes: - Fix ref update during render in useQrCode.ts - Fix ref update during render in useStorageCleaner.ts - Add error cause in jwt.ts decode function - Add type assertion for mock functions in htmlToMarkdown.test.ts
130 lines
2.7 KiB
TypeScript
130 lines
2.7 KiB
TypeScript
/**
|
|
* JWT 解析工具
|
|
*/
|
|
|
|
import i18n from '@/i18n';
|
|
|
|
export interface JwtHeader {
|
|
alg: string;
|
|
typ?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export interface JwtPayload {
|
|
iss?: string;
|
|
sub?: string;
|
|
aud?: string | string[];
|
|
exp?: number;
|
|
nbf?: number;
|
|
iat?: number;
|
|
jti?: string;
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export interface JwtResult {
|
|
header: JwtHeader | null;
|
|
payload: JwtPayload | null;
|
|
signature: string;
|
|
raw: {
|
|
header: string;
|
|
payload: string;
|
|
signature: string;
|
|
};
|
|
error?: string;
|
|
}
|
|
|
|
/**
|
|
* Base64URL 解码
|
|
* @param str Base64URL 编码字符串
|
|
*/
|
|
export function decodeBase64Url(str: string): string {
|
|
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
|
|
|
|
const pad = base64.length % 4;
|
|
if (pad) {
|
|
if (pad === 1) {
|
|
throw new Error(i18n.t('jwt:errors.invalidBase64String'));
|
|
}
|
|
base64 += new Array(5 - pad).join('=');
|
|
}
|
|
|
|
try {
|
|
const binStr = atob(base64);
|
|
const binLen = binStr.length;
|
|
const bytes = new Uint8Array(binLen);
|
|
for (let i = 0; i < binLen; i++) {
|
|
bytes[i] = binStr.charCodeAt(i);
|
|
}
|
|
const decoder = new TextDecoder('utf-8');
|
|
return decoder.decode(bytes);
|
|
} catch (e) {
|
|
throw new Error(
|
|
i18n.t('jwt:errors.failedToDecode') + (e instanceof Error ? e.message : String(e)),
|
|
{ cause: e },
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 解析 JWT 字符串
|
|
* @param token JWT 字符串
|
|
*/
|
|
export function parseJwt(token: string): JwtResult {
|
|
const parts = token.trim().split('.');
|
|
|
|
if (parts.length !== 3) {
|
|
return {
|
|
header: null,
|
|
payload: null,
|
|
signature: '',
|
|
raw: { header: '', payload: '', signature: '' },
|
|
error: i18n.t('jwt:errors.invalidFormat'),
|
|
};
|
|
}
|
|
|
|
const [headerB64, payloadB64, signatureB64] = parts;
|
|
const result: JwtResult = {
|
|
header: null,
|
|
payload: null,
|
|
signature: signatureB64,
|
|
raw: {
|
|
header: headerB64,
|
|
payload: payloadB64,
|
|
signature: signatureB64,
|
|
},
|
|
};
|
|
|
|
try {
|
|
const headerJson = decodeBase64Url(headerB64);
|
|
result.header = JSON.parse(headerJson);
|
|
} catch (e) {
|
|
result.error =
|
|
i18n.t('jwt:errors.parseHeaderFailed') + (e instanceof Error ? e.message : String(e));
|
|
return result;
|
|
}
|
|
|
|
try {
|
|
const payloadJson = decodeBase64Url(payloadB64);
|
|
result.payload = JSON.parse(payloadJson);
|
|
} catch (e) {
|
|
result.error =
|
|
i18n.t('jwt:errors.parsePayloadFailed') + (e instanceof Error ? e.message : String(e));
|
|
return result;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 将对象格式化为 JSON 字符串
|
|
* @param obj 对象
|
|
*/
|
|
export function stringifyJson(obj: unknown): string {
|
|
try {
|
|
return JSON.stringify(obj, null, 2);
|
|
} catch (e) {
|
|
console.error('格式化 JSON 失败:', e);
|
|
return String(obj);
|
|
}
|
|
}
|