feat: 内置 SVG 本地验证码(captcha_type 切换,替代/并存 CF Turnstile)+ proof 一次性机制 + 前端双模式

This commit is contained in:
2026-08-29 21:59:26 +08:00
parent bd240551a8
commit 8f65677408
8 changed files with 560 additions and 129 deletions
+26 -6
View File
@@ -27,9 +27,11 @@ api.interceptors.response.use(
// Auth
export const authApi = {
login: (username: string, password: string, turnstileToken?: string) => api.post("/auth/login", { username, password, turnstileToken }).then((r) => r.data),
login: (username: string, password: string, turnstileToken?: string, captchaProof?: string) =>
api.post("/auth/login", { username, password, ...(turnstileToken ? { turnstileToken } : {}), ...(captchaProof ? { captcha_proof: captchaProof } : {}) }).then((r) => r.data),
sendCode: (email: string) => api.post("/auth/send-code", { email }).then((r) => r.data),
register: (name: string, email: string, code: string, password: string, turnstileToken?: string) => api.post("/auth/register", { name, email, code, password, turnstileToken }).then((r) => r.data),
register: (name: string, email: string, code: string, password: string, turnstileToken?: string, captchaProof?: string) =>
api.post("/auth/register", { name, email, code, password, ...(turnstileToken ? { turnstileToken } : {}), ...(captchaProof ? { captcha_proof: captchaProof } : {}) }).then((r) => r.data),
me: () => api.get("/auth/me").then((r) => r.data),
oidcExchange: (ticket: string) => api.post("/auth/oidc/exchange", { ticket }).then((r) => r.data),
// 游客通道:POST /api/auth/guest -> { success, data: { token, user: { isGuest: true } } }guest JWT 2h
@@ -42,9 +44,24 @@ export const settingsApi = {
};
// Captcha
export interface CaptchaStatus {
siteKey: string;
actions: Record<string, boolean>;
type?: "turnstile" | "builtin" | "none" | "both";
builtin?: boolean;
}
export interface BuiltinCaptchaData {
token: string;
svg: string;
}
export const captchaApi = {
status: () => api.get("/captcha/status").then((r) => r.data.data),
status: () => api.get<ApiResponse<CaptchaStatus>>("/captcha/status").then((r) => r.data.data),
verify: (token: string) => api.post("/captcha/verify", { token }).then((r) => r.data),
image: () => api.get<ApiResponse<BuiltinCaptchaData>>("/captcha/image").then((r) => r.data.data),
verifyBuiltin: (token: string, answer: string) =>
api.post("/captcha/verify", { token, answer }).then((r) => r.data.data as { proof: string }),
};
// Scales
@@ -74,12 +91,15 @@ export const aiApi = {
export const userScaleApi = {
list: (status?: string) => api.get("/user-scales", { params: status ? { status } : {} }).then((r) => r.data.data),
get: (id: string) => api.get(`/user-scales/${id}`).then((r) => r.data.data),
create: (id: string, content: any, turnstileToken?: string) => api.post("/user-scales", { id, content, turnstileToken }).then((r) => r.data.data),
create: (id: string, content: any, turnstileToken?: string, captchaProof?: string) =>
api.post("/user-scales", { id, content, ...(turnstileToken ? { turnstileToken } : {}), ...(captchaProof ? { captcha_proof: captchaProof } : {}) }).then((r) => r.data.data),
update: (id: string, content: any) => api.put(`/user-scales/${id}`, { content }),
delete: (id: string) => api.delete(`/user-scales/${id}`),
share: (id: string, turnstileToken?: string) => api.post(`/user-scales/${id}/share`, { turnstileToken }).then((r) => r.data.data),
share: (id: string, turnstileToken?: string, captchaProof?: string) =>
api.post(`/user-scales/${id}/share`, { ...(turnstileToken ? { turnstileToken } : {}), ...(captchaProof ? { captcha_proof: captchaProof } : {}) }).then((r) => r.data.data),
unshare: (id: string) => api.post(`/user-scales/${id}/unshare`).then((r) => r.data),
submitReview: (id: string, turnstileToken?: string) => api.post(`/user-scales/${id}/submit-review`, { turnstileToken }).then((r) => r.data),
submitReview: (id: string, turnstileToken?: string, captchaProof?: string) =>
api.post(`/user-scales/${id}/submit-review`, { ...(turnstileToken ? { turnstileToken } : {}), ...(captchaProof ? { captcha_proof: captchaProof } : {}) }).then((r) => r.data),
getByShareCode: (code: string) => api.get(`/s/${code}`).then((r) => r.data.data),
};