Per docs/integration/Heicode-登录接口对接文档.md, the original Heicode
desktop is supposed to take email+password directly, hand them to the
Manager (POST /api/user/login), and use the resulting session to
acquire an LLM access token. The previous flow opened a system browser
and redirected through /heicode/oauth/authorize, which works but
deviates from the design and forces an extra round trip.
This commit adds the documented in-process flow as the primary login
path while keeping browser OAuth as a fallback link:
POST /api/heicode-auth/login-with-credentials
1. POST <baseUrl>/api/user/login (username + password)
2. Capture Set-Cookie from the response
3. GET <baseUrl>/heicode/oauth/authorize?... with that cookie and
redirect: 'manual'
4. Parse Location: ...?token=sk-XXXX, hand it to loginAndActivate
The whole chain stays inside the local cc-haha server — no browser is
opened, no token leaves the user's machine.
UI changes:
- ProviderLoginCard now shows email + password fields as the primary
form, with the existing "or via browser" OAuth path demoted to a
small link below.
- Added store action loginWithCredentials and matching API client
method.
- i18n keys: login.creds.{email,password,submit,submitting} +
login.oauth.altLink (zh + en).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
100 lines
2.4 KiB
TypeScript
100 lines
2.4 KiB
TypeScript
// desktop/src/api/heicodeAuth.ts
|
|
//
|
|
// Heicode 单 Provider 登录后端的客户端封装。
|
|
// 对应路由: src/server/api/heicode-auth.ts
|
|
|
|
import { api } from './client'
|
|
|
|
export type HeicodeProviderId = 'taijiaicloud'
|
|
|
|
export type HeicodeLoginProviderInfo = {
|
|
id: HeicodeProviderId
|
|
name: string
|
|
baseUrl: string
|
|
websiteUrl: string
|
|
apiKeyUrl?: string
|
|
promoText?: string
|
|
defaultModels: { main: string; haiku: string; sonnet: string; opus: string }
|
|
oauthEnabled: boolean
|
|
oauthStartUrl: string | null
|
|
}
|
|
|
|
export type HeicodeAuthStatus = {
|
|
loggedIn: boolean
|
|
source: 'cc-haha-provider' | 'original-settings' | 'env' | 'none'
|
|
activeProvider: {
|
|
id: string
|
|
presetId: string
|
|
name: string
|
|
baseUrl: string
|
|
models: { main: string; haiku: string; sonnet: string; opus: string }
|
|
} | null
|
|
}
|
|
|
|
export type HeicodeLoginInput = {
|
|
providerId: HeicodeProviderId
|
|
apiKey: string
|
|
displayName?: string
|
|
}
|
|
|
|
export type HeicodeCredentialsLoginInput = {
|
|
providerId: HeicodeProviderId
|
|
email: string
|
|
password: string
|
|
displayName?: string
|
|
}
|
|
|
|
export type HeicodeLoginResult = {
|
|
ok: true
|
|
provider: {
|
|
id: string
|
|
presetId: string
|
|
name: string
|
|
baseUrl: string
|
|
apiFormat: 'anthropic' | 'openai_chat' | 'openai_responses'
|
|
models: { main: string; haiku: string; sonnet: string; opus: string }
|
|
}
|
|
availableModels: Array<{ id: string; owned_by?: string }>
|
|
}
|
|
|
|
export const heicodeAuthApi = {
|
|
listProviders() {
|
|
return api.get<{ providers: HeicodeLoginProviderInfo[] }>(
|
|
'/api/heicode-auth/providers',
|
|
)
|
|
},
|
|
|
|
status() {
|
|
return api.get<HeicodeAuthStatus>('/api/heicode-auth/status')
|
|
},
|
|
|
|
loginWithApiKey(input: HeicodeLoginInput) {
|
|
return api.post<HeicodeLoginResult>(
|
|
'/api/heicode-auth/login',
|
|
input,
|
|
// 登录会调上游 /v1/models 探活,给 60s 余量。
|
|
{ timeout: 60_000 },
|
|
)
|
|
},
|
|
|
|
loginWithCredentials(input: HeicodeCredentialsLoginInput) {
|
|
return api.post<HeicodeLoginResult>(
|
|
'/api/heicode-auth/login-with-credentials',
|
|
input,
|
|
{ timeout: 60_000 },
|
|
)
|
|
},
|
|
|
|
// OAuth 当前是占位,但保留方法形态以便未来无痛切换。
|
|
startOAuth(providerId: HeicodeProviderId) {
|
|
return api.post<{ authorizeUrl: string; state: string }>(
|
|
'/api/heicode-auth/oauth/start',
|
|
{ providerId },
|
|
)
|
|
},
|
|
|
|
logout() {
|
|
return api.post<{ ok: true }>('/api/heicode-auth/logout')
|
|
},
|
|
}
|