Files
agent-fox/packages/web/src/lib/auth.tsx
YANG JIANKUAN 9733b82c9c feat: 支持 OAuth 无密码用户设置密码和查看 API Key
- 新增 POST /auth/set-password 端点(仅限无密码用户)
- /auth/me 返回 hasPassword 字段
- SettingsDialog:无密码用户显示"设置密码"表单(无需当前密码)
- API Key reveal/copy:无密码时引导用户先设置密码
- 中英双语 i18n 支持

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 13:39:46 +08:00

75 lines
2.4 KiB
TypeScript

import { createContext, useContext, useState, useEffect, type ReactNode } from 'react';
import { getAccessToken, clearTokens, setTokens, apiFetch } from './api';
type User = { id: string; email: string; name: string; hasPassword?: boolean };
type AuthContextType = {
user: User | null;
loading: boolean;
login: (email: string, password: string) => Promise<void>;
register: (email: string, password: string, name: string) => Promise<void>;
logout: () => void;
updateUser: (updates: Partial<User>) => void;
loginWithTokens: (accessToken: string, refreshToken: string) => Promise<void>;
};
const AuthContext = createContext<AuthContextType | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (getAccessToken()) {
apiFetch<User>('/auth/me')
.then(setUser)
.catch(() => clearTokens())
.finally(() => setLoading(false));
} else {
setLoading(false);
}
}, []);
const login = async (email: string, password: string) => {
const data = await apiFetch<{ user: User; accessToken: string; refreshToken: string }>(
'/auth/login',
{ method: 'POST', body: JSON.stringify({ email, password }) },
);
setTokens(data.accessToken, data.refreshToken);
setUser(data.user);
};
const register = async (email: string, password: string, name: string) => {
const data = await apiFetch<{ user: User; accessToken: string; refreshToken: string }>(
'/auth/register',
{ method: 'POST', body: JSON.stringify({ email, password, name }) },
);
setTokens(data.accessToken, data.refreshToken);
setUser(data.user);
};
const logout = () => { clearTokens(); setUser(null); };
const updateUser = (updates: Partial<User>) => {
setUser(prev => prev ? { ...prev, ...updates } : null);
};
const loginWithTokens = async (access: string, refresh: string) => {
setTokens(access, refresh);
const user = await apiFetch<User>('/auth/me');
setUser(user);
};
return (
<AuthContext.Provider value={{ user, loading, login, register, logout, updateUser, loginWithTokens }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
return ctx;
}