fix(deepseek-account): rename login source parameter
This commit is contained in:
1 parent
04debcbd65
commit
82ae763772
17 files changed
+51
-41
No files matched your search
@@ -78,7 +78,7 @@ export function desktopAccountBackend(origin: string, invoke: AccountInvoke, coo
|
||||
const call = async (method: string, args: Record<string, unknown> = {}): Promise<AccountView> =>
|
||||
accountView(await invoke({ namespace: 'account', method, args }))
|
||||
return {
|
||||
state: () => call('getState'), start: locale => call('startSignIn', { locale, callbackOrigin: new URL(origin).origin, client: 'desktop' }),
|
||||
state: () => call('getState'), start: locale => call('startSignIn', { locale, callbackOrigin: new URL(origin).origin, loginSource: 'desktop' }),
|
||||
cancel: attemptId => call('cancelSignIn', { attemptId }), signOut: () => call('signOut'),
|
||||
watch(listener, failed) {
|
||||
let closed = false
|
||||
|
||||
@@ -22,5 +22,5 @@ it('uses account Remote commands without returning additional wire fields', asyn
|
||||
return Promise.resolve({ links: { usageUrl: 'http://localhost/usage', topUpUrl: 'http://localhost/top_up' }, status: 'signed-out', attempt: null, token: 'private' })
|
||||
}, () => Promise.resolve(''))
|
||||
expect(await backend.start('en')).toEqual({ links: { usageUrl: 'http://localhost/usage', topUpUrl: 'http://localhost/top_up' }, status: 'signed-out', attempt: null })
|
||||
expect(requests).toEqual([{ namespace: 'account', method: 'startSignIn', args: { locale: 'en', callbackOrigin: 'http://127.0.0.1:1234', client: 'desktop' } }])
|
||||
expect(requests).toEqual([{ namespace: 'account', method: 'startSignIn', args: { locale: 'en', callbackOrigin: 'http://127.0.0.1:1234', loginSource: 'desktop' } }])
|
||||
})
|
||||
@@ -153,7 +153,7 @@ describe.skipIf(!existsSync(builtHost))('built Desktop welcome flow', () => {
|
||||
await expect.poll(async () => (await backend!.account.state()).attempt?.phase).toBe('waiting-browser')
|
||||
const response = await fetch(platform.callback(), { redirect: 'manual' })
|
||||
expect(response.status).toBe(302)
|
||||
expect(response.headers.get('location')).toBe(`${platform.origin}/dsh/authorized?result=test&locale=zh_CN&client_type=desktop`)
|
||||
expect(response.headers.get('location')).toBe(`${platform.origin}/dsh/authorized?result=test&locale=zh_CN&login_source=desktop`)
|
||||
expect(await status()).toMatchObject({ hasApiKey: true, loggedIn: true })
|
||||
await restart()
|
||||
expect(await status()).toMatchObject({ hasApiKey: true, loggedIn: true })
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/sign-in-readiness.md
|
||||
sign-in-readiness.md: 25244b1a5939c7b0f1337b23d2e2d14bfabe3125
|
||||
sign-in-readiness.zh.md: b19db05620af8cf733b12c49cc459834827f7e70
|
||||
sign-in-readiness.md: 0d13f38060a7ed7feeef48b3c56e6e5a6ffc3ec0
|
||||
sign-in-readiness.zh.md: 38672b53dec5a4deeb2b8f9830162347f343b508
|
||||
@@ -15,7 +15,7 @@ Keep completed items and their evidence; do not delete the checklist. Before com
|
||||
## Pending work and completion criteria
|
||||
|
||||
- [x] **Loopback address policy (DSH / Platform / backend)**: User confirmed on 2026-09-15 that the backend supports localhost. Preserve the hostname and browser-visible port without conversion to an IP. DSH, Platform, and mock validation now accept exact localhost and reject lookalike domains. Evidence on 2026-09-15: 40 DSH account tests and lint passed; 72 Platform tests, two type checks, and lint passed, including successful exchange with the original URI and rejection after conversion to an IP. The callback validator also accepts [::1] and requires an explicit port under the updated backend protocol; real IPv6 listener/forwarding acceptance remains pending.
|
||||
- [ ] **Real authorization API integration (backend / both applications)**: Verify auth_init client_type binding, authorize business codes, exchange token/authorized_url, auth_cancel verifier validation and idempotency, and invalidation of canceled requests and unexchanged codes against the real backend. Verify exchange cannot override desktop/web origin type. Mock implementation is not evidence of backend completion.
|
||||
- [ ] **Real authorization API integration (backend / both applications)**: Verify auth_init login_source binding, authorize business codes, exchange token/authorized_url, auth_cancel verifier validation and idempotency, and invalidation of canceled requests and unexchanged codes against the real backend. Verify exchange cannot override desktop/web origin type. Mock implementation is not evidence of backend completion.
|
||||
- [ ] **Real DSH token usage and logout (backend / DSH)**: Verify current, balance, inference, and file endpoints with a real DSH token; account credentials must still accompany inference/file requests only at allowed api.deepseek.com destinations. Verify restart restoration, explicit Platform logout, local credential deletion, and remote revocation outcomes. Use a dedicated test account to avoid revoking a shared daily-use dev token.
|
||||
- [ ] **Latest Web tab integration (both applications)**: At localhost:8081, verify new-tab authorization, automatic closure on success and exchange failure, original-tab failure feedback, and explicit retry. Cover timeout, cancellation, blocked automatic closure, manually copied links, and localhost access through SSH with different local/remote ports and IPv4/IPv6 listener configurations. Confirm no second Web UI opens and Web completion has no desktop button.
|
||||
- [ ] **Native client and installer acceptance (DSH)**: With current artifacts, verify Host readiness before login, workspace loading after login, failure/timeout recovery, and window focus. Verify installed dsh://open registration, cold launch, and activation of an existing app on supported release platforms. Earlier manual checks do not replace acceptance of the latest changes.
|
||||
@@ -40,4 +40,4 @@ When completing an item, append the date, implementation location, and test comm
|
||||
|
||||
2026-09-16: macOS development `Harness Dev.app` was registered and verified through the real Platform completion-page button in Chrome. Both opening an already running window and cold-starting after quitting succeeded, preserving the account display. This does not replace installed release-platform acceptance.
|
||||
|
||||
- [ ] **Completion-page context (backend / both applications)**: The supplied backend auth_init schema does not store client_type (desktop/web) or locale. Confirm how authorized_url selects desktop open-app versus Web close-tab behavior and language. DSH continues sending both fields until a replacement is agreed; sending them alone does not prove backend support.
|
||||
- [ ] **Completion-page context (backend / both applications)**: The supplied backend auth_init schema does not store login_source (desktop/web) or locale. Confirm how authorized_url selects desktop open-app versus Web close-tab behavior and language. DSH continues sending both fields until a replacement is agreed; sending them alone does not prove backend support.
|
||||
@@ -15,7 +15,7 @@
|
||||
## 待办与完成条件
|
||||
|
||||
- [x] **Loopback 地址策略(DSH/Platform/后端)**:用户于 2026-09-15 明确后端支持 localhost,保留主机名及浏览器实际访问端口,不转换 IP。DSH、Platform 与 mock 已对齐精确 localhost 校验并拒绝仿冒域名。验收:2026-09-15,DSH 40 项账号测试及 lint 通过;Platform 72 项测试、两个类型检查及 lint 通过,覆盖原 URI 兑换成功、转换 IP 后兑换失败。新版后端协议下,回调校验也接受 [::1] 并要求显式端口;真实 IPv6 监听和转发仍待验收。
|
||||
- [ ] **真实授权接口联调(后端/两端)**:在真实后端验证 auth_init 的 client_type 绑定、authorize 业务码、exchange 的 token/authorized_url、auth_cancel 的 verifier 校验和幂等、取消后申请及未兑换 code 失效。验证 desktop 与 web 来源不能在兑换时被覆盖;mock 实现不算后端完成证据。
|
||||
- [ ] **真实授权接口联调(后端/两端)**:在真实后端验证 auth_init 的 login_source 绑定、authorize 业务码、exchange 的 token/authorized_url、auth_cancel 的 verifier 校验和幂等、取消后申请及未兑换 code 失效。验证 desktop 与 web 来源不能在兑换时被覆盖;mock 实现不算后端完成证据。
|
||||
- [ ] **真实 DSH token 使用与退登(后端/DSH)**:验证 current、余额、推理和文件接口均可使用真实 DSH token;验证推理/文件请求仍仅在允许的 api.deepseek.com 地址附带账号凭证。验证重启恢复、主动退登调用 Platform logout、本地凭证删除与远端撤销结果。使用独立测试账号,避免撤销日常账号的共享 dev token。
|
||||
- [ ] **最新 Web 标签页流程联调(两端)**:使用 localhost:8081 跑通新标签页授权、成功自动关闭、exchange 失败关闭、原页失败弹窗和手动重试;覆盖超时、取消、自动关页受限、手动复制链接,以及 SSH 本地/远端端口不同和 IPv4/IPv6 监听配置下的 localhost 访问。确认不再打开第二个 Web UI,Web 完成页不显示桌面按钮。
|
||||
- [ ] **原生客户端与安装包验收(DSH)**:在最新产物验证登录前 Host ready、登录后主界面加载、失败/超时恢复和窗口聚焦;在支持的发布平台验证安装包 dsh://open 注册、冷启动及已运行时唤起。既有旧版本手测不能替代最新改动验收。
|
||||
@@ -40,4 +40,4 @@
|
||||
|
||||
2026-09-16:已注册 macOS 开发用 `Harness Dev.app`,并通过 Chrome 中真实 Platform 完成页按钮验证运行中唤起和完全退出后的冷启动,账号展示保持正常。这不替代正式安装包及其他发布平台验收。
|
||||
|
||||
- [ ] **完成页上下文(后端/两端)**:提供的后端 auth_init schema 未保存 client_type(desktop/web)或 locale。确认 authorized_url 如何区分桌面打开应用与 Web 关闭标签页及语言。替代方案确定前 DSH 继续发送两字段;发送字段本身不代表后端支持。
|
||||
- [ ] **完成页上下文(后端/两端)**:提供的后端 auth_init schema 未保存 login_source(desktop/web)或 locale。确认 authorized_url 如何区分桌面打开应用与 Web 关闭标签页及语言。替代方案确定前 DSH 继续发送两字段;发送字段本身不代表后端支持。
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/subsystems/credentials.md
|
||||
credentials.md: 75d592bc15d4de8da7225253e2a310e74b9f15fd
|
||||
credentials.zh.md: 23df660253af3dc035e78cac4856ddb872b2e574
|
||||
credentials.md: f0d64c47cbe886941ed8ad8d3d41304dcc76a493
|
||||
credentials.zh.md: c216cc58505f9f300626e5943e479df19537a9fa
|
||||
@@ -288,10 +288,10 @@ abstract getBalance(): Promise<AccountDetails['balance'] | null>
|
||||
* Join an active attempt or start browser authorization.
|
||||
* @param locale - active UI language for a new attempt; joining retains its original language.
|
||||
* @param callbackOrigin - browser-accessible loopback HTTP origin, including any SSH local port.
|
||||
* @param client - initiating UI, used to return from a failed exchange.
|
||||
* @param loginSource - initiating UI, used to return from a failed exchange.
|
||||
* @returns the initial snapshot without waiting for browser approval.
|
||||
*/
|
||||
abstract startSignIn(locale: string, callbackOrigin: string, client: 'web' | 'desktop'): Promise<AccountView>
|
||||
abstract startSignIn(locale: string, callbackOrigin: string, loginSource: 'web' | 'desktop'): Promise<AccountView>
|
||||
|
||||
/**
|
||||
* Cancel only the named attempt; committing attempts settle before returning.
|
||||
|
||||
@@ -288,10 +288,10 @@ abstract getBalance(): Promise<AccountDetails['balance'] | null>
|
||||
* Join an active attempt or start browser authorization.
|
||||
* @param locale - active UI language for a new attempt; joining retains its original language.
|
||||
* @param callbackOrigin - browser-accessible loopback HTTP origin, including any SSH local port.
|
||||
* @param client - initiating UI, used to return from a failed exchange.
|
||||
* @param loginSource - initiating UI, used to return from a failed exchange.
|
||||
* @returns the initial snapshot without waiting for browser approval.
|
||||
*/
|
||||
abstract startSignIn(locale: string, callbackOrigin: string, client: 'web' | 'desktop'): Promise<AccountView>
|
||||
abstract startSignIn(locale: string, callbackOrigin: string, loginSource: 'web' | 'desktop'): Promise<AccountView>
|
||||
|
||||
/**
|
||||
* Cancel only the named attempt; committing attempts settle before returning.
|
||||
|
||||
@@ -32,11 +32,11 @@ export class AccountController extends TypertRemoteService {
|
||||
* Begin browser sign-in.
|
||||
* @param locale - active UI language for a new attempt.
|
||||
* @param callbackOrigin - browser-accessible loopback HTTP origin.
|
||||
* @param client - initiating UI, used to return from a failed exchange.
|
||||
* @param loginSource - initiating UI, used to return from a failed exchange.
|
||||
* @returns a new or already-active login attempt.
|
||||
*/
|
||||
@Remote
|
||||
startSignIn(locale: string, callbackOrigin: string, client: 'web' | 'desktop'): Promise<AccountView> { return this.ctx.deepseekAccount.startSignIn(locale, callbackOrigin, client) }
|
||||
startSignIn(locale: string, callbackOrigin: string, loginSource: 'web' | 'desktop'): Promise<AccountView> { return this.ctx.deepseekAccount.startSignIn(locale, callbackOrigin, loginSource) }
|
||||
/**
|
||||
* Cancel the named local attempt.
|
||||
* @param attemptId - attempt to cancel.
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/credentials/deepseek-account-platform/README.md
|
||||
README.md: db337c7f999c1ce8055e3a427813189e84d53194
|
||||
README.zh.md: 510ee10be0d58dc8c4e6b4d78a8ce1f7a837e80a
|
||||
README.md: 248f679dd7b27af35eafa59579df7538db213b23
|
||||
README.zh.md: 36c3f861d60f2c02cca76feabed85862630ead98
|
||||
@@ -30,7 +30,7 @@ The profile projection maps `id_profile.picture` to `avatarUrl`, using null when
|
||||
|
||||
getProfile / getBalance sends the stored grant in the x-dsh-auth-token header to GET /auth-api/v0/users/current and GET /api/v0/users/get_user_summary on platformOrigin. The grant issuer must match that origin. Host projects only the account UID, profile name, avatar URL, phone or email exactly as masked by Platform, and normal_wallets / bonus_wallets currency/balance strings; response tokens and other fields are discarded. Bonus wallets remain separate from recharge balances. Credential changes and disposal invalidate in-flight queries.
|
||||
|
||||
Configure platformOrigin, allowLoopbackHttp, requestTimeoutMs, and attemptTimeoutMs in the plugin row. HTTP is accepted only for explicitly enabled loopback development. The provider registers /oauth/callback on the existing Host webServer before auth_init, validates state, exchanges an S256 PKCE code once, and commits a grant before redirecting to auth_exchange.biz_data.authorized_url. Browser destinations default to the configured platform origin and always require the fixed /dsh/authorize or /dsh/authorized path. The completion URL sets `client_type` to the initiating client (`web` or `desktop`) and preserves other Platform-supplied query parameters. Device identity is a separate random UUID record shared by processes using the same credential store; device_model reports OS and architecture.
|
||||
Configure platformOrigin, allowLoopbackHttp, requestTimeoutMs, and attemptTimeoutMs in the plugin row. HTTP is accepted only for explicitly enabled loopback development. The provider registers /oauth/callback on the existing Host webServer before auth_init, validates state, exchanges an S256 PKCE code once, and commits a grant before redirecting to auth_exchange.biz_data.authorized_url. Browser destinations default to the configured platform origin and always require the fixed /dsh/authorize or /dsh/authorized path. The completion URL sets `login_source` to the initiating client (`web` or `desktop`) and preserves other Platform-supplied query parameters. Device identity is a separate random UUID record shared by processes using the same credential store; device_model reports OS and architecture.
|
||||
|
||||
Platform URLs have one configuration owner: `platformOrigin`. Authorization requests, browser validation, completion redirects, usage, and top-up destinations all use that origin. Set it in a private `$DSH_HOME/cordis.patch.yml`; deployment addresses do not belong in source control. HTTPS is required except for loopback HTTP with `allowLoopbackHttp: true`. Model and file token destinations use the separate `inferenceOrigin` configuration.
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ getPlatformSession 仅在已存授权的 issuer 与 platformOrigin 一致时导
|
||||
|
||||
getProfile / getBalance 将保存的授权 token 通过 x-dsh-auth-token 请求头,向 platformOrigin 上的 GET /auth-api/v0/users/current 和 GET /api/v0/users/get_user_summary 发起请求。授权签发来源必须与该来源一致。Host 只投影账号 UID、资料名称、头像 URL、由 Platform 脱敏的手机号或邮箱(原样保留),以及 normal_wallets / bonus_wallets 的币种和余额字符串,丢弃响应 token 与其他字段。赠送钱包不计入充值余额。凭证变化和销毁会使进行中的查询失效。
|
||||
|
||||
在插件行配置 platformOrigin、allowLoopbackHttp、requestTimeoutMs 和 attemptTimeoutMs。HTTP 仅用于显式启用的本机开发。提供者先在现有 Host webServer 注册 /oauth/callback,再调用 auth_init;校验 state、使用 S256 PKCE 授权码兑换一次,并在跳转 auth_exchange.biz_data.authorized_url 前提交授权记录。浏览器地址默认要求匹配配置的平台来源,并始终要求固定的 /dsh/authorize 或 /dsh/authorized 路径。完成页地址将 `client_type` 设为发起登录的客户端类型(`web` 或 `desktop`),并保留平台返回的其他查询参数。设备标识是独立的随机 UUID 记录,由使用同一凭证存储的进程共享;device_model 报告操作系统和架构。
|
||||
在插件行配置 platformOrigin、allowLoopbackHttp、requestTimeoutMs 和 attemptTimeoutMs。HTTP 仅用于显式启用的本机开发。提供者先在现有 Host webServer 注册 /oauth/callback,再调用 auth_init;校验 state、使用 S256 PKCE 授权码兑换一次,并在跳转 auth_exchange.biz_data.authorized_url 前提交授权记录。浏览器地址默认要求匹配配置的平台来源,并始终要求固定的 /dsh/authorize 或 /dsh/authorized 路径。完成页地址将 `login_source` 设为发起登录的客户端类型(`web` 或 `desktop`),并保留平台返回的其他查询参数。设备标识是独立的随机 UUID 记录,由使用同一凭证存储的进程共享;device_model 报告操作系统和架构。
|
||||
|
||||
开放平台 URL 统一由 `platformOrigin` 配置管理。授权请求、浏览器地址校验、完成跳转、用量和充值入口都使用该来源。将配置放在私有的 `$DSH_HOME/cordis.patch.yml`,部署地址不进入源码仓库。默认要求 HTTPS;只有本机 HTTP 可通过 `allowLoopbackHttp: true` 显式启用。模型及文件 token 的目标由独立的 `inferenceOrigin` 配置管理。
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ interface Attempt {
|
||||
done: Promise<void>
|
||||
running: Promise<void>
|
||||
origin: string
|
||||
client: 'web' | 'desktop'
|
||||
loginSource: 'web' | 'desktop'
|
||||
disposeCallback?: () => Promise<void>
|
||||
callback?: ServerResponse
|
||||
initialProfile?: { token: string; value: AccountDetails['profile'] }
|
||||
@@ -238,7 +238,7 @@ export class PlatformAccount extends DeepSeekAccount {
|
||||
return result.data.token
|
||||
}
|
||||
|
||||
override async startSignIn(locale: string, callbackOrigin: string, client: 'web' | 'desktop'): Promise<AccountView> {
|
||||
override async startSignIn(locale: string, callbackOrigin: string, loginSource: 'web' | 'desktop'): Promise<AccountView> {
|
||||
if (this.removing !== undefined) await this.removing
|
||||
const origin = loginOrigin(callbackOrigin)
|
||||
if (this.closed) throw new PlatformAuthError('protocol')
|
||||
@@ -254,7 +254,7 @@ export class PlatformAccount extends DeepSeekAccount {
|
||||
if (this.attempt !== previous) return this.getState()
|
||||
}
|
||||
const attempt: Attempt = {
|
||||
origin, client,
|
||||
origin, loginSource,
|
||||
locale: locale.toLowerCase().split(/[-_]/)[0] === 'zh' ? 'zh_CN' : 'en_US',
|
||||
view: { id: randomUUID() as SignInAttemptId, phase: 'initializing' },
|
||||
controller: new AbortController(), done: Promise.resolve(), running: Promise.resolve(),
|
||||
@@ -398,7 +398,7 @@ export class PlatformAccount extends DeepSeekAccount {
|
||||
const redirectUri = `${attempt.origin}/oauth/callback`
|
||||
const init = initialization.safeParse(await this.request('auth_init', {
|
||||
code_challenge: challenge, code_challenge_method: 'S256', state, redirect_uri: redirectUri, locale: attempt.locale,
|
||||
client_type: attempt.client,
|
||||
login_source: attempt.loginSource,
|
||||
}, signal), { reportInput: true })
|
||||
if (!init.success) {
|
||||
console.info('[deepseek-account] payload rejected', {
|
||||
@@ -437,7 +437,7 @@ export class PlatformAccount extends DeepSeekAccount {
|
||||
throw new PlatformAuthError('protocol')
|
||||
}
|
||||
const completionUrl = new URL(browserUrl(result.data.authorized_url, this.origin, '/dsh/authorized', this.rewriteBrowserOrigin))
|
||||
completionUrl.searchParams.set('client_type', attempt.client)
|
||||
completionUrl.searchParams.set('login_source', attempt.loginSource)
|
||||
attempt.completionUrl = completionUrl.href
|
||||
signal.throwIfAborted()
|
||||
if (result.data.user != null) {
|
||||
@@ -476,7 +476,7 @@ export class PlatformAccount extends DeepSeekAccount {
|
||||
}
|
||||
|
||||
private finishFailedCallback(attempt: Attempt): void {
|
||||
if (attempt.client === 'web') {
|
||||
if (attempt.loginSource === 'web') {
|
||||
const nonce = randomBytes(16).toString('base64url')
|
||||
const message = attempt.locale === 'zh_CN' ? '登录失败,请关闭此标签页并在原页面重试。'
|
||||
: 'Sign-in failed. Close this tab and try again in the original tab.'
|
||||
|
||||
@@ -171,8 +171,9 @@ it('stores a grant before redirecting, restores account presence, and signs out
|
||||
expect((await fetch(f.callback('wrong'))).status).toBe(400)
|
||||
const response = await fetch(f.callback(), { redirect: 'manual' })
|
||||
expect(response.status).toBe(302)
|
||||
expect(f.init().client_type).toBe('desktop')
|
||||
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=test&locale=zh_CN&client_type=desktop`)
|
||||
expect(f.init().login_source).toBe('desktop')
|
||||
expect(f.init()).not.toHaveProperty('client_type')
|
||||
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=test&locale=zh_CN&login_source=desktop`)
|
||||
expect((await f.account.getState()).status).toBe('credential-stored')
|
||||
expect(await readFile(join(f.home, 'credentials.yaml'), 'utf8')).toContain('dsh_mock_test')
|
||||
expect(await f.account.getPlatformSession()).toEqual({ origin: f.origin, token: 'dsh_mock_test' })
|
||||
@@ -230,23 +231,32 @@ it('maps both returned browser pages to the development origin across the login
|
||||
expect((await f.wait('waiting-browser')).attempt?.authorizeUrl).toBe(`${f.origin}/dsh/authorize?authorize_id=test`)
|
||||
const response = await fetch(f.callback(), { redirect: 'manual' })
|
||||
expect(response.status).toBe(302)
|
||||
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=a%2Fb&locale=zh_CN&client_type=desktop`)
|
||||
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=a%2Fb&locale=zh_CN&login_source=desktop`)
|
||||
expect((await f.account.getState()).status).toBe('credential-stored')
|
||||
})
|
||||
|
||||
it.each([
|
||||
['web', ''],
|
||||
['desktop', ''],
|
||||
['web', '&client_type=desktop&client_type=desktop'],
|
||||
['desktop', '&client_type=web&client_type=web'],
|
||||
] as const)('redirects the %s login completion with its client type when Platform returns %s', async (client, query) => {
|
||||
['web', '&login_source=desktop&login_source=desktop'],
|
||||
['desktop', '&login_source=web&login_source=web'],
|
||||
] as const)('redirects the %s login completion with its login source when Platform returns %s', async (client, query) => {
|
||||
const f = await fixture()
|
||||
f.exchangeResponse({ authorized_url: `${f.origin}/dsh/authorized?result=a%2Fb&locale=zh_CN${query}` })
|
||||
await f.account.startSignIn('en', f.callbackOrigin, client)
|
||||
await f.wait('waiting-browser')
|
||||
const response = await fetch(f.callback(), { redirect: 'manual' })
|
||||
expect(response.status).toBe(302)
|
||||
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=a%2Fb&locale=zh_CN&client_type=${client}`)
|
||||
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=a%2Fb&locale=zh_CN&login_source=${client}`)
|
||||
})
|
||||
|
||||
it('preserves Platform business client_type in the completion URL', async () => {
|
||||
const f = await fixture()
|
||||
f.exchangeResponse({ authorized_url: `${f.origin}/dsh/authorized?client_type=DSH` })
|
||||
await f.account.startSignIn('en', f.callbackOrigin, 'web')
|
||||
await f.wait('waiting-browser')
|
||||
const response = await fetch(f.callback(), { redirect: 'manual' })
|
||||
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?client_type=DSH&login_source=web`)
|
||||
})
|
||||
|
||||
it.each([undefined, 'https://other.example/dsh/authorized', '/dsh/authorized'])('rejects an invalid exchange completion URL %s before storing a token', async (authorizedUrl) => {
|
||||
@@ -454,7 +464,7 @@ it('closes the failed Web authorization tab without redirecting and keeps the sh
|
||||
expect(response.headers.get('content-security-policy')).toContain(`script-src 'nonce-${nonce}'`)
|
||||
await expect(`${page.match(/<p>(.*?)<\/p>/)![1]}\n`)
|
||||
.toMatchFileSnapshot('./expected/web-exchange-failure.txt')
|
||||
expect(f.init().client_type).toBe('web')
|
||||
expect(f.init().login_source).toBe('web')
|
||||
expect(await f.wait('failed')).toMatchObject({ status: 'signed-out', attempt: { errorCode: 'protocol' } })
|
||||
expect(f.count()).toBe(1)
|
||||
expect(await (await fetch(`${f.callbackOrigin}/health`)).text()).toBe('alive')
|
||||
@@ -462,7 +472,7 @@ it('closes the failed Web authorization tab without redirecting and keeps the sh
|
||||
await f.account.startSignIn('en', f.callbackOrigin, 'web')
|
||||
await f.wait('waiting-browser')
|
||||
const success = await fetch(f.callback(), { redirect: 'manual' })
|
||||
expect(success.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=test&locale=zh_CN&client_type=web`)
|
||||
expect(success.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=test&locale=zh_CN&login_source=web`)
|
||||
expect(f.count()).toBe(2)
|
||||
})
|
||||
|
||||
@@ -491,7 +501,7 @@ it('completes login through a local TCP forward using the browser port rather th
|
||||
await f.wait('waiting-browser')
|
||||
expect(f.init().redirect_uri).toBe(`${forwardedOrigin}/oauth/callback`)
|
||||
const response = await fetch(f.callback(), { redirect: 'manual' })
|
||||
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=test&locale=zh_CN&client_type=web`)
|
||||
expect(response.headers.get('location')).toBe(`${f.origin}/dsh/authorized?result=test&locale=zh_CN&login_source=web`)
|
||||
expect((await f.account.getState()).status).toBe('credential-stored')
|
||||
})
|
||||
|
||||
|
||||
@@ -42,10 +42,10 @@ export abstract class DeepSeekAccount extends Service {
|
||||
* Join an active attempt or start browser authorization.
|
||||
* @param locale - active UI language for a new attempt; joining retains its original language.
|
||||
* @param callbackOrigin - browser-accessible loopback HTTP origin, including any SSH local port.
|
||||
* @param client - initiating UI, used to return from a failed exchange.
|
||||
* @param loginSource - initiating UI, used to return from a failed exchange.
|
||||
* @returns the initial snapshot without waiting for browser approval.
|
||||
*/
|
||||
abstract startSignIn(locale: string, callbackOrigin: string, client: 'web' | 'desktop'): Promise<AccountView>
|
||||
abstract startSignIn(locale: string, callbackOrigin: string, loginSource: 'web' | 'desktop'): Promise<AccountView>
|
||||
/**
|
||||
* Cancel only the named attempt; committing attempts settle before returning.
|
||||
* @param id - attempt identity from this Host.
|
||||
|
||||
@@ -882,9 +882,9 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
|
||||
returns: 'balance outcome, or null if signed out or the grant changed during the query.',
|
||||
},
|
||||
{
|
||||
signature: 'abstract startSignIn(locale: string, callbackOrigin: string, client: \'web\' | \'desktop\'): Promise<AccountView>',
|
||||
signature: 'abstract startSignIn(locale: string, callbackOrigin: string, loginSource: \'web\' | \'desktop\'): Promise<AccountView>',
|
||||
description: 'Join an active attempt or start browser authorization.',
|
||||
parameters: [{ name: 'locale', description: 'active UI language for a new attempt; joining retains its original language.' }, { name: 'callbackOrigin', description: 'browser-accessible loopback HTTP origin, including any SSH local port.' }, { name: 'client', description: 'initiating UI, used to return from a failed exchange.' }],
|
||||
parameters: [{ name: 'locale', description: 'active UI language for a new attempt; joining retains its original language.' }, { name: 'callbackOrigin', description: 'browser-accessible loopback HTTP origin, including any SSH local port.' }, { name: 'loginSource', description: 'initiating UI, used to return from a failed exchange.' }],
|
||||
returns: 'the initial snapshot without waiting for browser approval.',
|
||||
},
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user