Files
heicode/new-api/web/default/src/features/auth/api.ts
T
gongzhiyong 9bf3a73a75 feat: align heicode web-manager branding and deployment docs
Unify website and manager experience with updated logo and manager entry links, and document the current production topology and URLs in CLAUDE.md for consistent future operations.

Made-with: Cursor
2026-04-30 20:23:46 +08:00

263 lines
7.2 KiB
TypeScript
Vendored

import { api } from '@/lib/api'
import type {
LoginPayload,
LoginResponse,
Login2FAResponse,
TwoFAPayload,
RegisterPayload,
ApiResponse,
} from './types'
const AUTH_BASE_URL = (
(import.meta.env.VITE_HEICODE_AUTH_BASE_URL as string | undefined) ||
'https://apimtaiji.azure-api.net/api/mcp'
).trim()
const ACCESS_TOKEN_KEY = 'heicode_access_token'
const REFRESH_TOKEN_KEY = 'heicode_refresh_token'
function readToken(key: string): string {
if (typeof window === 'undefined') return ''
return window.localStorage.getItem(key) || ''
}
function writeTokens(accessToken?: string, refreshToken?: string) {
if (typeof window === 'undefined') return
if (accessToken) {
window.localStorage.setItem(ACCESS_TOKEN_KEY, accessToken)
}
if (refreshToken) {
window.localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken)
}
}
export function clearHeicodeTokens() {
if (typeof window === 'undefined') return
window.localStorage.removeItem(ACCESS_TOKEN_KEY)
window.localStorage.removeItem(REFRESH_TOKEN_KEY)
}
async function callHeicodeAuth<T>(
path: string,
init: RequestInit = {},
useRefreshToken = false
): Promise<T> {
const requestId = `heicode-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`
const token = useRefreshToken
? readToken(REFRESH_TOKEN_KEY)
: readToken(ACCESS_TOKEN_KEY)
const res = await fetch(`${AUTH_BASE_URL}${path}`, {
...init,
headers: {
'Content-Type': 'application/json',
'X-Request-Id': requestId,
...(init.headers || {}),
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
})
let data: unknown = null
try {
data = await res.json()
} catch {
data = null
}
if (!res.ok) {
const message =
(data as { detail?: string; message?: string } | null)?.detail ||
(data as { detail?: string; message?: string } | null)?.message ||
`Auth request failed (${res.status})`
throw new Error(message)
}
return data
}
// ============================================================================
// Authentication APIs
// ============================================================================
// ----------------------------------------------------------------------------
// Login & Logout
// ----------------------------------------------------------------------------
// User login with username and password
export async function login(payload: LoginPayload) {
const res = await callHeicodeAuth<{
success: boolean
message?: string
detail?: string
data?: {
token?: string
refreshToken?: string
user?: {
id?: string
name?: string
email?: string
role?: string
channelId?: string
}
}
}>('/api/auth/login', {
method: 'POST',
body: JSON.stringify({
email: payload.username,
password: payload.password,
role: 'user',
}),
})
if (res?.success) {
writeTokens(res.data?.token, res.data?.refreshToken)
} else if (res?.detail || res?.message) {
throw new Error(res.detail || res.message || 'Login failed')
}
return {
success: Boolean(res?.success),
message: res?.message || res?.detail || '',
data: {
id: 1,
user: res?.data?.user,
},
}
}
// Two-factor authentication login
export async function login2fa(payload: TwoFAPayload) {
const res = await api.post<Login2FAResponse>('/api/user/login/2fa', payload)
return res.data
}
// User logout
export async function logout(): Promise<ApiResponse> {
try {
await callHeicodeAuth('/api/auth/logout', { method: 'POST' })
} finally {
clearHeicodeTokens()
}
return { success: true, message: '' }
}
export async function refreshHeicodeTokenIfNeeded() {
const refreshToken = readToken(REFRESH_TOKEN_KEY)
if (!refreshToken) return false
const res = await callHeicodeAuth<{
success: boolean
detail?: string
data?: { token?: string; refreshToken?: string }
}>('/api/auth/refresh', { method: 'POST' }, true).catch(() => null)
if (res?.success) {
writeTokens(res.data?.token, res.data?.refreshToken)
return true
}
clearHeicodeTokens()
return false
}
export async function getHeicodeCurrentUser() {
let me = await callHeicodeAuth<{
success: boolean
data?: {
id?: string
email?: string
name?: string
role?: string
channelId?: string
status?: string
}
}>('/api/auth/me', { method: 'GET' })
if (!me?.success) {
const refreshed = await refreshHeicodeTokenIfNeeded()
if (!refreshed) return null
me = await callHeicodeAuth('/api/auth/me', { method: 'GET' })
}
if (!me?.success || !me.data) return null
const roleStr = String(me.data.role || 'user').toLowerCase()
const role =
roleStr === 'root' ? 100 : roleStr === 'admin' ? 10 : roleStr === 'user' ? 1 : 1
return {
id: 1,
username: me.data.email || me.data.name || 'heicode-user',
display_name: me.data.name || me.data.email || 'Heicode User',
email: me.data.email || '',
role,
group: me.data.channelId || 'default',
status: me.data.status === 'active' ? 1 : 1,
}
}
// ----------------------------------------------------------------------------
// Password Management
// ----------------------------------------------------------------------------
// Send password reset email
export async function sendPasswordResetEmail(
email: string,
turnstile?: string
): Promise<ApiResponse> {
const res = await api.get('/api/reset_password', {
params: { email, turnstile },
})
return res.data
}
// ----------------------------------------------------------------------------
// OAuth
// ----------------------------------------------------------------------------
// Start GitHub OAuth flow
export async function githubOAuthStart(clientId: string, state: string) {
const url = `https://github.com/login/oauth/authorize?client_id=${clientId}&state=${state}&scope=user:email`
window.open(url)
}
// Get OAuth state for CSRF protection
export async function getOAuthState(): Promise<string> {
const aff =
typeof window !== 'undefined' ? (localStorage.getItem('aff') ?? '') : ''
const res = await api.get('/api/oauth/state', { params: { aff } })
if (res.data?.success) return res.data.data
return ''
}
// WeChat login by authorization code
export async function wechatLoginByCode(code: string): Promise<ApiResponse> {
const res = await api.get('/api/oauth/wechat', { params: { code } })
return res.data
}
// ----------------------------------------------------------------------------
// Registration
// ----------------------------------------------------------------------------
// User registration
export async function register(payload: RegisterPayload): Promise<ApiResponse> {
const res = await api.post(`/api/user/register`, payload, {
params: { turnstile: payload.turnstile ?? '' },
})
return res.data
}
// Send email verification code
export async function sendEmailVerification(
email: string,
turnstile?: string
): Promise<ApiResponse> {
const res = await api.get('/api/verification', {
params: { email, turnstile },
})
return res.data
}
// Bind email to OAuth account
export async function bindEmail(
email: string,
code: string
): Promise<ApiResponse> {
const res = await api.get('/api/oauth/email/bind', {
params: { email, code },
})
return res.data
}