206 lines
4.7 KiB
TypeScript
206 lines
4.7 KiB
TypeScript
/**
|
|
* Authentication Service - Handles user authentication operations
|
|
* Feature: api-interface-optimization
|
|
* Validates: Requirements 12, 13
|
|
*/
|
|
|
|
import api from './api';
|
|
|
|
// Token storage keys
|
|
const ACCESS_TOKEN_KEY = 'access_token';
|
|
const REFRESH_TOKEN_KEY = 'refresh_token';
|
|
|
|
// Types
|
|
export interface User {
|
|
id: number;
|
|
email: string;
|
|
username: string;
|
|
avatar?: string;
|
|
is_active: boolean;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
|
|
export interface TokenPair {
|
|
access_token: string;
|
|
refresh_token: string;
|
|
expires_in: number;
|
|
}
|
|
|
|
export interface RegisterInput {
|
|
email: string;
|
|
password: string;
|
|
username: string;
|
|
}
|
|
|
|
export interface LoginInput {
|
|
email: string;
|
|
password: string;
|
|
}
|
|
|
|
export interface AuthResponse {
|
|
success: boolean;
|
|
data: {
|
|
user: User;
|
|
tokens: TokenPair;
|
|
};
|
|
}
|
|
|
|
export interface RefreshResponse {
|
|
success: boolean;
|
|
data: TokenPair;
|
|
}
|
|
|
|
// Token management
|
|
export function getAccessToken(): string | null {
|
|
return localStorage.getItem(ACCESS_TOKEN_KEY);
|
|
}
|
|
|
|
export function getRefreshToken(): string | null {
|
|
return localStorage.getItem(REFRESH_TOKEN_KEY);
|
|
}
|
|
|
|
export function setTokens(tokens: TokenPair): void {
|
|
localStorage.setItem(ACCESS_TOKEN_KEY, tokens.access_token);
|
|
localStorage.setItem(REFRESH_TOKEN_KEY, tokens.refresh_token);
|
|
}
|
|
|
|
export function clearTokens(): void {
|
|
localStorage.removeItem(ACCESS_TOKEN_KEY);
|
|
localStorage.removeItem(REFRESH_TOKEN_KEY);
|
|
}
|
|
|
|
export function isAuthenticated(): boolean {
|
|
return !!getAccessToken();
|
|
}
|
|
|
|
|
|
/**
|
|
* Register a new user
|
|
* Validates: Requirements 12.1, 12.2
|
|
*/
|
|
export async function register(input: RegisterInput): Promise<{ user: User; tokens: TokenPair }> {
|
|
const response = await api.post<AuthResponse>('/auth/register', input);
|
|
if (response.success && response.data) {
|
|
setTokens(response.data.tokens);
|
|
return response.data;
|
|
}
|
|
throw new Error('Registration failed');
|
|
}
|
|
|
|
/**
|
|
* Login with email and password
|
|
* Validates: Requirements 12.2
|
|
*/
|
|
export async function login(input: LoginInput): Promise<{ user: User; tokens: TokenPair }> {
|
|
const response = await api.post<AuthResponse>('/auth/login', input);
|
|
if (response.success && response.data) {
|
|
setTokens(response.data.tokens);
|
|
return response.data;
|
|
}
|
|
throw new Error('Login failed');
|
|
}
|
|
|
|
/**
|
|
* Refresh access token using refresh token
|
|
* Validates: Requirements 12.4
|
|
*/
|
|
export async function refreshAccessToken(): Promise<TokenPair> {
|
|
const refreshToken = getRefreshToken();
|
|
if (!refreshToken) {
|
|
throw new Error('No refresh token available');
|
|
}
|
|
|
|
const response = await api.post<RefreshResponse>('/auth/refresh', {
|
|
refresh_token: refreshToken,
|
|
});
|
|
|
|
if (response.success && response.data) {
|
|
setTokens(response.data);
|
|
return response.data;
|
|
}
|
|
throw new Error('Token refresh failed');
|
|
}
|
|
|
|
/**
|
|
* Logout - clear tokens
|
|
*/
|
|
export function logout(): void {
|
|
clearTokens();
|
|
}
|
|
|
|
/**
|
|
* Get GitHub OAuth login URL
|
|
* Validates: Requirements 13.1
|
|
*/
|
|
export function getGitHubLoginUrl(state?: string): string {
|
|
const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:2612/api/v1';
|
|
const url = new URL(`${baseUrl}/auth/github`);
|
|
if (state) {
|
|
url.searchParams.append('state', state);
|
|
}
|
|
return url.toString();
|
|
}
|
|
|
|
/**
|
|
* Redirect to GitHub OAuth login
|
|
* Validates: Requirements 13.1
|
|
*/
|
|
export function loginWithGitHub(state?: string): void {
|
|
window.location.href = getGitHubLoginUrl(state);
|
|
}
|
|
|
|
/**
|
|
* Handle GitHub OAuth callback
|
|
* Validates: Requirements 13.4, 13.5
|
|
*/
|
|
export async function handleGitHubCallback(code: string): Promise<{ user: User; tokens: TokenPair }> {
|
|
const response = await api.get<AuthResponse>('/auth/github/callback', { code });
|
|
if (response.success && response.data) {
|
|
setTokens(response.data.tokens);
|
|
return response.data;
|
|
}
|
|
throw new Error('GitHub authentication failed');
|
|
}
|
|
|
|
/**
|
|
* Get current user information
|
|
*/
|
|
export async function getCurrentUser(): Promise<User> {
|
|
const response = await api.get<{ success: boolean; data: User }>('/auth/me');
|
|
if (response.success && response.data) {
|
|
return response.data;
|
|
}
|
|
throw new Error('Failed to get user information');
|
|
}
|
|
|
|
/**
|
|
* Update user password
|
|
*/
|
|
export async function updatePassword(oldPassword: string, newPassword: string): Promise<void> {
|
|
const response = await api.post<{ success: boolean }>('/auth/password', {
|
|
old_password: oldPassword,
|
|
new_password: newPassword,
|
|
});
|
|
if (!response.success) {
|
|
throw new Error('Failed to update password');
|
|
}
|
|
}
|
|
|
|
export default {
|
|
register,
|
|
login,
|
|
logout,
|
|
refreshAccessToken,
|
|
getAccessToken,
|
|
getRefreshToken,
|
|
setTokens,
|
|
clearTokens,
|
|
isAuthenticated,
|
|
loginWithGitHub,
|
|
getGitHubLoginUrl,
|
|
handleGitHubCallback,
|
|
getCurrentUser,
|
|
updatePassword,
|
|
};
|