Implements complete authentication system using Supabase Auth SDK following Archon's vertical slice architecture. Frontend Changes: - Install @supabase/supabase-js dependency - Create auth feature in vertical slice pattern: * AuthContext and Provider for global auth state * authService with Supabase Auth methods (signIn, signUp, signOut, etc.) * Auth query hooks with TanStack Query integration * TypeScript types for User, Session, AuthState * ProtectedRoute component for route guards - Add LoginPage and SignUpPage with Tron-themed design - Update App.tsx with AuthProvider and protected routes - Configure Supabase client with environment variables Backend Changes: - Create auth_service.py for JWT token validation - Create auth_middleware.py for protecting API routes (optional, commented by default) - Create auth_api.py with endpoints: * POST /api/auth/verify - Verify JWT token * GET /api/auth/user - Get current user * GET /api/auth/health - Auth service health check - Register auth router in main.py - Add middleware configuration (disabled by default) Configuration: - Update .env.example with VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY - Add comprehensive AUTHENTICATION_SETUP.md documentation Features: - Email/password authentication - Persistent sessions with localStorage - Auto token refresh - Route protection with loading states - Integration with existing TanStack Query patterns - Optional backend middleware for API protection - Row Level Security (RLS) ready Architecture follows CLAUDE.md guidelines: - Vertical slice architecture for auth feature - TanStack Query for state management - No backwards compatibility needed (beta) - KISS principle - Fail fast with detailed errors Notes: - Auth middleware is commented out by default to avoid breaking existing installations - Users can enable it when ready by uncommenting in main.py - Frontend auth works independently of backend middleware - Comprehensive setup guide included in AUTHENTICATION_SETUP.md
112 lines
2.3 KiB
TypeScript
112 lines
2.3 KiB
TypeScript
import { supabase } from "../config/supabaseClient";
|
|
import type { LoginCredentials, SignUpCredentials, User, Session } from "../types";
|
|
|
|
export const authService = {
|
|
async signIn(credentials: LoginCredentials): Promise<{ user: User; session: Session }> {
|
|
const { data, error } = await supabase.auth.signInWithPassword({
|
|
email: credentials.email,
|
|
password: credentials.password,
|
|
});
|
|
|
|
if (error) {
|
|
throw new Error(error.message);
|
|
}
|
|
|
|
if (!data.user || !data.session) {
|
|
throw new Error("Login failed: No user or session returned");
|
|
}
|
|
|
|
return {
|
|
user: data.user,
|
|
session: data.session,
|
|
};
|
|
},
|
|
|
|
async signUp(credentials: SignUpCredentials): Promise<{ user: User; session: Session | null }> {
|
|
const { data, error } = await supabase.auth.signUp({
|
|
email: credentials.email,
|
|
password: credentials.password,
|
|
options: {
|
|
data: credentials.metadata || {},
|
|
},
|
|
});
|
|
|
|
if (error) {
|
|
throw new Error(error.message);
|
|
}
|
|
|
|
if (!data.user) {
|
|
throw new Error("Sign up failed: No user returned");
|
|
}
|
|
|
|
return {
|
|
user: data.user,
|
|
session: data.session,
|
|
};
|
|
},
|
|
|
|
async signOut(): Promise<void> {
|
|
const { error } = await supabase.auth.signOut();
|
|
|
|
if (error) {
|
|
throw new Error(error.message);
|
|
}
|
|
},
|
|
|
|
async getCurrentUser(): Promise<User | null> {
|
|
const {
|
|
data: { user },
|
|
error,
|
|
} = await supabase.auth.getUser();
|
|
|
|
if (error) {
|
|
throw new Error(error.message);
|
|
}
|
|
|
|
return user;
|
|
},
|
|
|
|
async getSession(): Promise<Session | null> {
|
|
const {
|
|
data: { session },
|
|
error,
|
|
} = await supabase.auth.getSession();
|
|
|
|
if (error) {
|
|
throw new Error(error.message);
|
|
}
|
|
|
|
return session;
|
|
},
|
|
|
|
async resetPassword(email: string): Promise<void> {
|
|
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
|
redirectTo: `${window.location.origin}/reset-password`,
|
|
});
|
|
|
|
if (error) {
|
|
throw new Error(error.message);
|
|
}
|
|
},
|
|
|
|
async updatePassword(newPassword: string): Promise<void> {
|
|
const { error } = await supabase.auth.updateUser({
|
|
password: newPassword,
|
|
});
|
|
|
|
if (error) {
|
|
throw new Error(error.message);
|
|
}
|
|
},
|
|
|
|
onAuthStateChange(callback: (user: User | null, session: Session | null) => void) {
|
|
const {
|
|
data: { subscription },
|
|
} = supabase.auth.onAuthStateChange((_event, session) => {
|
|
callback(session?.user ?? null, session);
|
|
});
|
|
|
|
return subscription;
|
|
},
|
|
};
|