Initial commit
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
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 userDbName = DatabaseNameService.getUserPrivateDB(
|
||||
userType,
|
||||
username
|
||||
);
|
||||
const schoolDbName = DatabaseNameService.getDevelopmentSchoolDB();
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user