Spaces:
Running
Running
| import { auth, db } from "./firebase.js"; | |
| import { | |
| signInWithEmailAndPassword, | |
| createUserWithEmailAndPassword, | |
| signOut, | |
| sendPasswordResetEmail, | |
| getRedirectResult | |
| } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-auth.js"; | |
| import { | |
| doc, | |
| getDoc, | |
| setDoc, | |
| updateDoc, | |
| collection, | |
| getDocs, | |
| deleteDoc, | |
| serverTimestamp, | |
| query | |
| } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore.js"; | |
| const INSTRUCTORS_COLLECTION = "instructors"; | |
| const SUPER_ADMIN_EMAIL = "t92206@gmail.com"; | |
| /** | |
| * Handle Redirect Result (for OAuth flows like Google Sign In) | |
| */ | |
| export async function handleRedirectResult() { | |
| try { | |
| const result = await getRedirectResult(auth); | |
| return result ? result.user : null; | |
| } catch (error) { | |
| console.error("Redirect Result Error:", error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * Sign in with Email/Password | |
| */ | |
| export async function loginWithEmail(email, password) { | |
| try { | |
| const result = await signInWithEmailAndPassword(auth, email, password); | |
| return result.user; | |
| } catch (error) { | |
| console.error("Login Error:", error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * Register with Email/Password | |
| * (Used for new instructors to create their auth account matching their whitelisted email) | |
| */ | |
| export async function registerWithEmail(email, password) { | |
| try { | |
| const result = await createUserWithEmailAndPassword(auth, email, password); | |
| return result.user; | |
| } catch (error) { | |
| console.error("Register Error:", error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * Send Password Reset Email | |
| */ | |
| export async function resetPassword(email) { | |
| try { | |
| await sendPasswordResetEmail(auth, email); | |
| } catch (error) { | |
| console.error("Reset Password Error:", error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * Sign out | |
| */ | |
| export async function signOutUser() { | |
| try { | |
| await signOut(auth); | |
| } catch (error) { | |
| console.error("Sign Out Error:", error); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * Check if user is an instructor and get permissions | |
| * Bootstraps the Super Admin if not exists | |
| * @param {object} user - Firebase User object | |
| * @returns {Promise<object|null>} Instructor data or null if not authorized | |
| */ | |
| export async function checkInstructorPermission(user) { | |
| if (!user || !user.email) return null; | |
| const email = user.email; | |
| const instructorRef = doc(db, INSTRUCTORS_COLLECTION, email); | |
| let snap; | |
| try { | |
| console.log(`[Permission Check] Checking whitelist for email: '${email}'`); | |
| snap = await getDoc(instructorRef); | |
| console.log(`[Permission Check] Result for '${email}': exists=${snap.exists()}`); | |
| } catch (error) { | |
| console.warn(`[Permission Check] Failed for '${email}'. Error:`, error); | |
| console.warn("Instructor Permission Check Failed (likely not whitelisted):", error.code); | |
| // If permission denied, it means they are not allowed to read the doc => likely not in whitelist | |
| return null; | |
| } | |
| // Bootstrap Super Admin | |
| if (email === SUPER_ADMIN_EMAIL) { | |
| const adminData = { | |
| name: user.displayName || "Super Admin", | |
| email: email, | |
| role: 'admin', | |
| permissions: ['create_room', 'add_question', 'manage_instructors'], | |
| lastLogin: serverTimestamp() | |
| }; | |
| try { | |
| if (!snap.exists()) { | |
| await setDoc(instructorRef, { | |
| ...adminData, | |
| createdAt: serverTimestamp() | |
| }); | |
| } else { | |
| // Ensure admin always has full permissions | |
| await updateDoc(instructorRef, { | |
| role: 'admin', | |
| permissions: ['create_room', 'add_question', 'manage_instructors'], | |
| lastLogin: serverTimestamp() | |
| }); | |
| } | |
| } catch (e) { | |
| console.warn("Admin bootstrap failed (likely permission issues), but allowing login as admin.", e); | |
| // We continue because we return adminData anyway, effectively granting admin rights in UI. | |
| } | |
| return adminData; | |
| } | |
| if (snap.exists()) { | |
| const data = snap.data(); | |
| try { | |
| await updateDoc(instructorRef, { lastLogin: serverTimestamp() }); | |
| } catch (e) { | |
| console.warn("Failed to update lastLogin (likely permission), proceeding anyway.", e); | |
| } | |
| return data; | |
| } | |
| return null; // Not an instructor | |
| } | |
| /** | |
| * Get all instructors (Admin Only) | |
| */ | |
| export async function getInstructors() { | |
| const q = query(collection(db, INSTRUCTORS_COLLECTION)); | |
| const snapshot = await getDocs(q); | |
| return snapshot.docs.map(doc => doc.data()); | |
| } | |
| /** | |
| * Add new instructor (Admin Only) | |
| */ | |
| export async function addInstructor(email, name, permissions) { | |
| const safeEmail = email.trim(); // Ensure no leading/trailing spaces | |
| const instructorRef = doc(db, INSTRUCTORS_COLLECTION, safeEmail); | |
| await setDoc(instructorRef, { | |
| email: safeEmail, | |
| name, | |
| role: 'instructor', | |
| permissions, | |
| createdAt: serverTimestamp() | |
| }); | |
| } | |
| /** | |
| * Update instructor (Admin Only) | |
| */ | |
| export async function updateInstructor(email, data) { | |
| const instructorRef = doc(db, INSTRUCTORS_COLLECTION, email); | |
| await updateDoc(instructorRef, data); | |
| } | |
| /** | |
| * Remove instructor (Admin Only) | |
| */ | |
| export async function removeInstructor(email) { | |
| if (email === SUPER_ADMIN_EMAIL) throw new Error("Cannot remove Super Admin"); | |
| await deleteDoc(doc(db, INSTRUCTORS_COLLECTION, email)); | |
| } | |