274 lines
7.0 KiB
TypeScript
274 lines
7.0 KiB
TypeScript
import { User, AuthChangeEvent, Session } from '@supabase/supabase-js';
|
|
import { TLUserPreferences } from '@tldraw/tldraw';
|
|
import { supabase } from '../../supabaseClient';
|
|
import { storageService, StorageKeys } from './localStorageService';
|
|
import { logger } from '../../debugConfig';
|
|
import { DatabaseNameService } from '../graph/databaseNameService';
|
|
|
|
export interface CCUser {
|
|
id: string;
|
|
email?: string;
|
|
user_type: string;
|
|
username: string;
|
|
display_name: string;
|
|
user_db_name: string;
|
|
school_db_name: string;
|
|
created_at?: string;
|
|
updated_at?: string;
|
|
}
|
|
|
|
export interface CCUserMetadata {
|
|
username?: string;
|
|
user_type?: string;
|
|
display_name?: string;
|
|
email?: string;
|
|
name?: string;
|
|
preferred_username?: string;
|
|
[key: string]: string | undefined;
|
|
}
|
|
|
|
export function convertToCCUser(user: User, metadata: CCUserMetadata): CCUser {
|
|
// Extract username from various possible sources
|
|
const username = metadata.username ||
|
|
metadata.preferred_username ||
|
|
metadata.email?.split('@')[0] ||
|
|
user.email?.split('@')[0] ||
|
|
'user';
|
|
|
|
// Extract display name from various possible sources
|
|
const displayName = metadata.display_name ||
|
|
metadata.name ||
|
|
metadata.preferred_username ||
|
|
username;
|
|
|
|
// Default to student if no user type specified
|
|
const userType = metadata.user_type || 'student';
|
|
|
|
const storedUserDb = DatabaseNameService.getStoredUserDatabase();
|
|
const storedSchoolDb = DatabaseNameService.getStoredSchoolDatabase();
|
|
|
|
const userDbName = storedUserDb || DatabaseNameService.getUserPrivateDB(
|
|
userType,
|
|
user.id
|
|
);
|
|
const schoolDbName = metadata.school_db_name || metadata.worker_db_name || storedSchoolDb || '';
|
|
|
|
DatabaseNameService.rememberDatabaseNames({
|
|
userDbName,
|
|
schoolDbName
|
|
});
|
|
|
|
return {
|
|
id: user.id,
|
|
email: user.email,
|
|
user_type: userType,
|
|
username: username,
|
|
display_name: displayName,
|
|
user_db_name: userDbName,
|
|
school_db_name: schoolDbName,
|
|
created_at: user.created_at,
|
|
updated_at: user.updated_at,
|
|
};
|
|
}
|
|
|
|
export type UserRole =
|
|
| 'email_teacher'
|
|
| 'email_student'
|
|
| 'cc_admin'
|
|
| 'cc_developer'
|
|
| 'super_admin';
|
|
|
|
// Login response
|
|
export interface LoginResponse {
|
|
user: CCUser | null;
|
|
accessToken: string | null;
|
|
userRole: string;
|
|
message: string | null;
|
|
}
|
|
|
|
// Session response
|
|
export interface SessionResponse {
|
|
user: CCUser | null;
|
|
accessToken: string | null;
|
|
message: string | null;
|
|
}
|
|
|
|
// Registration response
|
|
export interface RegistrationResponse extends LoginResponse {
|
|
user: CCUser;
|
|
accessToken: string | null;
|
|
userRole: UserRole;
|
|
message: string | null;
|
|
}
|
|
|
|
export interface EmailCredentials {
|
|
email: string;
|
|
password: string;
|
|
role: 'email_teacher' | 'email_student';
|
|
}
|
|
|
|
export type AuthCredentials = EmailCredentials;
|
|
|
|
export const getTldrawPreferences = (user: CCUser): TLUserPreferences => {
|
|
return {
|
|
id: user.id,
|
|
colorScheme: 'system',
|
|
};
|
|
};
|
|
|
|
class AuthService {
|
|
private static instance: AuthService;
|
|
|
|
private constructor() {}
|
|
|
|
onAuthStateChange(
|
|
callback: (event: AuthChangeEvent, session: Session | null) => void
|
|
) {
|
|
return supabase.auth.onAuthStateChange((event, session) => {
|
|
logger.info('auth-service', '🔄 Auth state changed', {
|
|
event,
|
|
hasSession: !!session,
|
|
userId: session?.user?.id,
|
|
eventType: event,
|
|
});
|
|
|
|
// Ensure we clear storage on signout
|
|
if (event === 'SIGNED_OUT') {
|
|
storageService.clearAll();
|
|
}
|
|
|
|
callback(event, session);
|
|
});
|
|
}
|
|
|
|
static getInstance(): AuthService {
|
|
if (!AuthService.instance) {
|
|
AuthService.instance = new AuthService();
|
|
}
|
|
return AuthService.instance;
|
|
}
|
|
|
|
async getCurrentSession(): Promise<SessionResponse> {
|
|
try {
|
|
const {
|
|
data: { session },
|
|
error,
|
|
} = await supabase.auth.getSession();
|
|
if (error) {
|
|
throw error;
|
|
}
|
|
|
|
if (!session) {
|
|
return { user: null, accessToken: null, message: 'No active session' };
|
|
}
|
|
|
|
return {
|
|
user: convertToCCUser(
|
|
session.user,
|
|
session.user.user_metadata as CCUserMetadata
|
|
),
|
|
accessToken: session.access_token,
|
|
message: 'Session retrieved',
|
|
};
|
|
} catch (error) {
|
|
logger.error('auth-service', 'Failed to get current session:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async getCurrentUser(): Promise<CCUser | null> {
|
|
try {
|
|
const {
|
|
data: { user },
|
|
error,
|
|
} = await supabase.auth.getUser();
|
|
if (error || !user) {
|
|
return null;
|
|
}
|
|
return convertToCCUser(user, user.user_metadata as CCUserMetadata);
|
|
} catch (error) {
|
|
logger.error('auth-service', 'Failed to get current user:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async login({
|
|
email,
|
|
password,
|
|
role,
|
|
}: EmailCredentials): Promise<LoginResponse> {
|
|
try {
|
|
logger.info('auth-service', '🔄 Attempting login', {
|
|
email,
|
|
role,
|
|
});
|
|
|
|
const { data, error } = await supabase.auth.signInWithPassword({
|
|
email,
|
|
password,
|
|
});
|
|
|
|
if (error) {
|
|
logger.error('auth-service', '❌ Supabase auth error', {
|
|
error: error.message,
|
|
status: error.status,
|
|
});
|
|
throw error;
|
|
}
|
|
|
|
if (!data.session) {
|
|
logger.error('auth-service', '❌ No session after login');
|
|
throw new Error('No session after login');
|
|
}
|
|
|
|
const ccUser = convertToCCUser(
|
|
data.user,
|
|
data.user.user_metadata as CCUserMetadata
|
|
);
|
|
|
|
// Store auth session in storage
|
|
storageService.set(StorageKeys.USER_ROLE, ccUser.user_type);
|
|
storageService.set(StorageKeys.USER, ccUser);
|
|
storageService.set(StorageKeys.SUPABASE_TOKEN, data.session.access_token);
|
|
storageService.set(StorageKeys.SUPABASE_SESSION, data.session);
|
|
|
|
logger.info('auth-service', '✅ Login successful', {
|
|
userId: ccUser.id,
|
|
role: ccUser.user_type,
|
|
username: ccUser.username,
|
|
});
|
|
|
|
return {
|
|
user: ccUser,
|
|
accessToken: data.session.access_token,
|
|
userRole: ccUser.user_type,
|
|
message: 'Login successful',
|
|
};
|
|
} catch (error) {
|
|
logger.error('auth-service', '❌ Login failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async logout(): Promise<void> {
|
|
try {
|
|
logger.debug('auth-service', '🔄 Attempting logout');
|
|
const { error } = await supabase.auth.signOut({ scope: 'local' });
|
|
if (error) {
|
|
logger.error('auth-service', '❌ Logout failed:', error);
|
|
throw error;
|
|
}
|
|
// Clear all stored data
|
|
storageService.clearAll();
|
|
// Force a refresh of the auth state
|
|
await supabase.auth.refreshSession();
|
|
logger.debug('auth-service', '✅ Logout successful');
|
|
} catch (error) {
|
|
logger.error('auth-service', '❌ Logout failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
export const authService = AuthService.getInstance();
|