feat: auth service + security audit fixes + cleanup legacy services

Major changes:
- Add auth-svc: JWT auth, register/login/refresh, password reset
- Add auth UI: modals, pages (/login, /register, /forgot-password)
- Add usage tracking (usage_metrics table, daily limits)
- Add tiered rate limiting (free/pro/business)
- Add LLM usage limits per tier

Security fixes:
- All repos now require userID for Update/Delete operations
- JWT middleware in chat-svc, llm-svc, agent-svc, discover-svc
- ErrNotFound/ErrForbidden errors for proper access control

Cleanup:
- Remove legacy TypeScript services/ directory
- Remove computer-svc (to be reimplemented)
- Remove old deploy/docker configs

New files:
- backend/cmd/auth-svc/main.go
- backend/internal/auth/{types,repository}.go
- backend/internal/usage/{types,repository}.go
- backend/pkg/middleware/{llm_limits,ratelimit_tiered}.go
- backend/webui/src/components/auth/*
- backend/webui/src/app/(auth)/*

Made-with: Cursor
This commit is contained in:
home
2026-02-28 01:33:49 +03:00
parent 120fbbaafb
commit a0e3748dde
523 changed files with 10776 additions and 59630 deletions

View File

@@ -0,0 +1,276 @@
const API_BASE = process.env.NEXT_PUBLIC_API_URL || '';
export interface User {
id: string;
email: string;
name: string;
avatar?: string;
role: 'user' | 'admin';
tier: 'free' | 'pro' | 'business';
emailVerified: boolean;
provider: string;
createdAt: string;
updatedAt: string;
}
export interface AuthTokens {
accessToken: string;
refreshToken: string;
expiresIn: number;
tokenType: string;
user: User;
}
export interface RegisterRequest {
email: string;
password: string;
name: string;
}
export interface LoginRequest {
email: string;
password: string;
}
export interface ChangePasswordRequest {
currentPassword: string;
newPassword: string;
}
export interface ResetPasswordRequest {
email: string;
}
export interface ResetPasswordConfirm {
token: string;
newPassword: string;
}
export interface UpdateProfileRequest {
name: string;
avatar?: string;
}
const TOKEN_KEY = 'token';
const REFRESH_TOKEN_KEY = 'refreshToken';
const USER_KEY = 'user';
export function getStoredToken(): string | null {
if (typeof window === 'undefined') return null;
return localStorage.getItem(TOKEN_KEY);
}
export function getStoredRefreshToken(): string | null {
if (typeof window === 'undefined') return null;
return localStorage.getItem(REFRESH_TOKEN_KEY);
}
export function getStoredUser(): User | null {
if (typeof window === 'undefined') return null;
const data = localStorage.getItem(USER_KEY);
if (!data) return null;
try {
return JSON.parse(data);
} catch {
return null;
}
}
export function storeAuth(tokens: AuthTokens): void {
localStorage.setItem(TOKEN_KEY, tokens.accessToken);
localStorage.setItem(REFRESH_TOKEN_KEY, tokens.refreshToken);
localStorage.setItem(USER_KEY, JSON.stringify(tokens.user));
}
export function clearAuth(): void {
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(REFRESH_TOKEN_KEY);
localStorage.removeItem(USER_KEY);
}
async function handleResponse<T>(response: Response): Promise<T> {
if (!response.ok) {
const data = await response.json().catch(() => ({}));
throw new Error(data.error || `Request failed: ${response.status}`);
}
return response.json();
}
export async function register(data: RegisterRequest): Promise<AuthTokens> {
const response = await fetch(`${API_BASE}/api/v1/auth/register`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
const tokens = await handleResponse<AuthTokens>(response);
storeAuth(tokens);
return tokens;
}
export async function login(data: LoginRequest): Promise<AuthTokens> {
const response = await fetch(`${API_BASE}/api/v1/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
const tokens = await handleResponse<AuthTokens>(response);
storeAuth(tokens);
return tokens;
}
export async function refreshTokens(): Promise<AuthTokens | null> {
const refreshToken = getStoredRefreshToken();
if (!refreshToken) return null;
try {
const response = await fetch(`${API_BASE}/api/v1/auth/refresh`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken }),
});
if (!response.ok) {
clearAuth();
return null;
}
const tokens = await response.json();
storeAuth(tokens);
return tokens;
} catch {
clearAuth();
return null;
}
}
export async function logout(): Promise<void> {
const token = getStoredToken();
const refreshToken = getStoredRefreshToken();
if (token) {
try {
await fetch(`${API_BASE}/api/v1/auth/logout`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify({ refreshToken }),
});
} catch {
// Ignore errors during logout
}
}
clearAuth();
}
export async function logoutAll(): Promise<void> {
const token = getStoredToken();
if (token) {
try {
await fetch(`${API_BASE}/api/v1/auth/logout-all`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
},
});
} catch {
// Ignore errors
}
}
clearAuth();
}
export async function getMe(): Promise<User | null> {
const token = getStoredToken();
if (!token) return null;
try {
const response = await fetch(`${API_BASE}/api/v1/auth/me`, {
headers: {
'Authorization': `Bearer ${token}`,
},
});
if (!response.ok) {
if (response.status === 401) {
const refreshed = await refreshTokens();
if (refreshed) {
return getMe();
}
return null;
}
return null;
}
return response.json();
} catch {
return null;
}
}
export async function updateProfile(data: UpdateProfileRequest): Promise<User> {
const token = getStoredToken();
if (!token) throw new Error('Not authenticated');
const response = await fetch(`${API_BASE}/api/v1/auth/me`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify(data),
});
const user = await handleResponse<User>(response);
const storedUser = getStoredUser();
if (storedUser) {
localStorage.setItem(USER_KEY, JSON.stringify({ ...storedUser, ...user }));
}
return user;
}
export async function changePassword(data: ChangePasswordRequest): Promise<void> {
const token = getStoredToken();
if (!token) throw new Error('Not authenticated');
const response = await fetch(`${API_BASE}/api/v1/auth/change-password`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify(data),
});
await handleResponse<{ message: string }>(response);
}
export async function forgotPassword(email: string): Promise<void> {
const response = await fetch(`${API_BASE}/api/v1/auth/forgot-password`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
await handleResponse<{ message: string }>(response);
}
export async function resetPassword(data: ResetPasswordConfirm): Promise<void> {
const response = await fetch(`${API_BASE}/api/v1/auth/reset-password`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
await handleResponse<{ message: string }>(response);
}
export function isAuthenticated(): boolean {
return !!getStoredToken();
}