feat(login-ui): dedupe submissions and surface guard error codes
Collapse rapid double-submits into a single in-flight request across admin-web, ops-web and the desktop client so users cannot fire parallel login attempts and trip the new in-progress guard. Map the new login_rate_limited / login_locked / login_in_progress / login_guard_unavailable error codes to localized messages in every login surface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,10 +2,15 @@ import { ApiClientError } from "@geo/http-client";
|
||||
|
||||
const errorMessageMap: Record<string, string> = {
|
||||
invalid_credentials: "邮箱或密码错误",
|
||||
login_rate_limited: "登录请求过于频繁",
|
||||
login_locked: "登录已被临时保护",
|
||||
login_in_progress: "登录请求正在处理中",
|
||||
login_guard_unavailable: "登录保护暂时不可用",
|
||||
no_tenant: "当前账号未关联租户",
|
||||
not_authenticated: "请先登录",
|
||||
missing_bearer_token: "请先登录",
|
||||
invalid_access_token: "登录状态已失效",
|
||||
user_disabled: "用户被停用,请联系客服",
|
||||
invalid_refresh_token: "刷新令牌已失效",
|
||||
refresh_session_expired: "登录已过期,请重新登录",
|
||||
refresh_token_mismatch: "刷新令牌校验失败,请重新登录",
|
||||
|
||||
@@ -137,15 +137,27 @@ export const useAuthStore = defineStore("auth", () => {
|
||||
initialized.value = true;
|
||||
}
|
||||
|
||||
let loginPromise: Promise<UserInfo> | null = null;
|
||||
|
||||
async function login(payload: LoginRequest): Promise<UserInfo> {
|
||||
const response = await authApi.login(payload);
|
||||
setStoredTokens({
|
||||
accessToken: response.access_token,
|
||||
refreshToken: response.refresh_token,
|
||||
expiresAt: response.expires_at,
|
||||
if (loginPromise) {
|
||||
return loginPromise;
|
||||
}
|
||||
|
||||
const loginPayload = { ...payload };
|
||||
loginPromise = authApi.login(loginPayload).then((response) => {
|
||||
setStoredTokens({
|
||||
accessToken: response.access_token,
|
||||
refreshToken: response.refresh_token,
|
||||
expiresAt: response.expires_at,
|
||||
});
|
||||
setStoredUser(response.user);
|
||||
return response.user;
|
||||
}).finally(() => {
|
||||
loginPromise = null;
|
||||
});
|
||||
setStoredUser(response.user);
|
||||
return response.user;
|
||||
|
||||
return loginPromise;
|
||||
}
|
||||
|
||||
async function logout(): Promise<void> {
|
||||
@@ -164,6 +176,7 @@ export const useAuthStore = defineStore("auth", () => {
|
||||
const value = membership.value;
|
||||
return Boolean(
|
||||
value?.blocked ||
|
||||
value?.blocked_reason === "user_disabled" ||
|
||||
value?.status === "expired" ||
|
||||
value?.blocked_reason === "trial_plan_expired" ||
|
||||
value?.blocked_reason === "subscription_required" ||
|
||||
|
||||
Reference in New Issue
Block a user