App 打开 app.yanmeiai.com/sso?t=<ticket>&redirect=<路径>;本页 exchangeWebTicket→setSession (与正常登录同路径)→跳白名单相对路径。镜像 jiu sso.njk。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
This commit is contained in:
@@ -18,6 +18,7 @@ export const ERROR_TEXT: Record<string, { zh: string; en: string }> = {
|
||||
device_limit: { zh: '设备数量已达上限', en: 'Device limit reached' },
|
||||
device_not_found: { zh: '设备不存在', en: 'Device not found' },
|
||||
unauthorized: { zh: '登录已失效,请重新登录', en: 'Session expired, please log in again' },
|
||||
'auth.ticket_invalid': { zh: '登录票据无效或已过期,请重新从 App 打开', en: 'Login ticket is invalid or expired, please reopen from the app' },
|
||||
network: { zh: '网络异常,请稍后重试', en: 'Network error, please retry' },
|
||||
unknown: { zh: '操作失败,请稍后重试', en: 'Something went wrong, please retry' },
|
||||
};
|
||||
|
||||
@@ -142,6 +142,21 @@ export class HttpClient implements ApiClient {
|
||||
return session;
|
||||
}
|
||||
|
||||
// App→Web 免登录:POST /v1/auth/web-ticket/exchange(公开端点,无 Authorization 头)。
|
||||
// 服务端消费一次性票据后返回与 /v1/auth/login 相同的扁平 TokenPair(不会走 TOTP 分支——
|
||||
// 票据本身已代表 App 内已完成的完整登录),故直接映射 session 并 setSession,与
|
||||
// login()/loginTotp() 落地同一份会话状态。
|
||||
async exchangeWebTicket(ticket: string): Promise<Session> {
|
||||
const r = await this.request<RawTokenPair>('/v1/auth/web-ticket/exchange', {
|
||||
method: 'POST',
|
||||
body: { ticket },
|
||||
auth: false,
|
||||
});
|
||||
const session = mapSession(r);
|
||||
setSession(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
async refresh(): Promise<Session> {
|
||||
const rt = getRefreshToken();
|
||||
if (!rt) throw new ApiError({ code: 'unauthorized', message_zh: '登录已失效', message_en: 'Session expired' });
|
||||
|
||||
@@ -64,6 +64,21 @@ export class MockClient implements ApiClient {
|
||||
return s;
|
||||
}
|
||||
|
||||
// 演示态:任意非 'invalid' 票据都兑换成功;'invalid' 用于验收失败态文案。
|
||||
async exchangeWebTicket(ticket: string): Promise<Session> {
|
||||
await delay(300);
|
||||
if (ticket === 'invalid') {
|
||||
throw new ApiError({
|
||||
code: 'auth.ticket_invalid',
|
||||
message_zh: '登录票据无效或已过期,请重新从 App 打开',
|
||||
message_en: 'Login ticket is invalid or expired, please reopen from the app',
|
||||
});
|
||||
}
|
||||
const s = makeSession();
|
||||
setSession(s);
|
||||
return s;
|
||||
}
|
||||
|
||||
async refresh(): Promise<Session> {
|
||||
await delay(150);
|
||||
const s = makeSession();
|
||||
|
||||
@@ -82,6 +82,8 @@ export interface ApiClient {
|
||||
login(email: string, password: string): Promise<LoginResult>;
|
||||
/** 登录二段式:提交 TOTP 动态码换取 session */
|
||||
loginTotp(pendingToken: string, code: string): Promise<Session>;
|
||||
/** App→Web 免登录:凭一次性票据兑换与 login() 同形状的会话(公开端点,无需 TOTP) */
|
||||
exchangeWebTicket(ticket: string): Promise<Session>;
|
||||
refresh(): Promise<Session>;
|
||||
getMe(): Promise<Me>;
|
||||
getSubscription(): Promise<SubscriptionInfo>;
|
||||
|
||||
Reference in New Issue
Block a user