212 lines
6.1 KiB
TypeScript
212 lines
6.1 KiB
TypeScript
import {
|
|
signInWithEmailAndPassword,
|
|
signOut,
|
|
onAuthStateChanged,
|
|
setPersistence,
|
|
browserLocalPersistence,
|
|
sendPasswordResetEmail,
|
|
confirmPasswordReset,
|
|
} from "firebase/auth";
|
|
import type { User, AuthError } from "firebase/auth";
|
|
import { app} from "../features/auth/firebase"
|
|
import { getAuth } from "firebase/auth";
|
|
|
|
const auth = getAuth(app);
|
|
|
|
// Token refresh interval tracking
|
|
let tokenRefreshInterval: number | null = null;
|
|
|
|
// Constants for session management
|
|
const TOKEN_REFRESH_INTERVAL = 40 * 60 * 1000; // Refresh token every 40 minutes (Firebase ID tokens expire in 1 hour)
|
|
|
|
/**
|
|
* Initialize Firebase Auth persistence
|
|
* Ensures user session persists across page refreshes
|
|
*/
|
|
export const initializeAuthPersistence = async () => {
|
|
try {
|
|
await setPersistence(auth, browserLocalPersistence);
|
|
console.log("Auth persistence initialized with localStorage");
|
|
} catch (error) {
|
|
console.error("Error setting auth persistence:", error);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Refresh the current user's ID token to maintain session validity
|
|
* Firebase automatically refreshes tokens, but we can force a refresh to ensure validity
|
|
* @returns Promise<boolean> - true if refresh successful, false otherwise
|
|
*/
|
|
export const refreshUserToken = async (): Promise<boolean> => {
|
|
try {
|
|
const currentUser = auth.currentUser;
|
|
if (currentUser) {
|
|
await currentUser.getIdTokenResult(true); // Force refresh
|
|
console.log("Token refreshed successfully");
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch (error) {
|
|
console.error("Error refreshing token:", error);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Start automatic token refresh mechanism
|
|
* Refreshes token periodically to prevent unexpected logouts
|
|
*/
|
|
export const startTokenRefreshTimer = () => {
|
|
// Clear any existing interval
|
|
if (tokenRefreshInterval) {
|
|
clearInterval(tokenRefreshInterval);
|
|
}
|
|
|
|
// Set up auto-refresh interval
|
|
tokenRefreshInterval = window.setInterval(async () => {
|
|
const currentUser = auth.currentUser;
|
|
if (currentUser) {
|
|
await refreshUserToken();
|
|
} else {
|
|
// If no user, stop the refresh timer
|
|
stopTokenRefreshTimer();
|
|
}
|
|
}, TOKEN_REFRESH_INTERVAL);
|
|
};
|
|
|
|
/**
|
|
* Stop the automatic token refresh timer
|
|
*/
|
|
export const stopTokenRefreshTimer = () => {
|
|
if (tokenRefreshInterval) {
|
|
clearInterval(tokenRefreshInterval);
|
|
tokenRefreshInterval = null;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Login user with email and password
|
|
*/
|
|
export const loginWithEmail = async (email: string, password: string) => {
|
|
try {
|
|
const userCredential = await signInWithEmailAndPassword(auth, email, password);
|
|
return {
|
|
success: true,
|
|
user: userCredential.user,
|
|
error: null,
|
|
};
|
|
} catch (error) {
|
|
const authError = error as AuthError;
|
|
return {
|
|
success: false,
|
|
user: null,
|
|
error: getAuthErrorMessage(authError.code),
|
|
};
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Sign out the current user
|
|
* Clears session data, local storage, and stops token refresh
|
|
*/
|
|
export const logout = async () => {
|
|
try {
|
|
// Stop token refresh interval
|
|
stopTokenRefreshTimer();
|
|
|
|
// Clear any session-related data from localStorage
|
|
localStorage.removeItem('lastActivityTime');
|
|
localStorage.removeItem('sessionStartTime');
|
|
|
|
// Sign out from Firebase
|
|
await signOut(auth);
|
|
|
|
// Clear any other app-specific session data if needed
|
|
sessionStorage.clear();
|
|
|
|
console.log("User logged out successfully");
|
|
return { success: true };
|
|
} catch (error) {
|
|
console.error("Error during logout:", error);
|
|
return { success: false, error: (error as Error).message };
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Send password reset email
|
|
*/
|
|
export const sendPasswordReset = async (email: string) => {
|
|
try {
|
|
await sendPasswordResetEmail(auth, email);
|
|
return { success: true };
|
|
} catch (error) {
|
|
const authError = error as AuthError;
|
|
return { success: false, error: getAuthErrorMessage(authError.code) };
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Reset password with code and new password
|
|
* Used after user clicks the link in the reset email
|
|
*/
|
|
export const resetPassword = async (code: string, newPassword: string) => {
|
|
try {
|
|
await confirmPasswordReset(auth, code, newPassword);
|
|
return { success: true };
|
|
} catch (error) {
|
|
const authError = error as AuthError;
|
|
return { success: false, error: getAuthErrorMessage(authError.code) };
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Subscribe to auth state changes
|
|
* Sets up token refresh timer when user logs in, stops it when logs out
|
|
* Returns unsubscribe function
|
|
*/
|
|
export const subscribeToAuthState = (callback: (user: User | null) => void) => {
|
|
return onAuthStateChanged(auth, (user) => {
|
|
if (user) {
|
|
// User logged in - start token refresh
|
|
startTokenRefreshTimer();
|
|
// Update last activity time
|
|
localStorage.setItem('lastActivityTime', Date.now().toString());
|
|
localStorage.setItem('sessionStartTime', Date.now().toString());
|
|
} else {
|
|
// User logged out - stop token refresh
|
|
stopTokenRefreshTimer();
|
|
}
|
|
callback(user);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Get current user synchronously
|
|
*/
|
|
export const getCurrentUser = () => {
|
|
return auth.currentUser;
|
|
};
|
|
|
|
/**
|
|
* Convert Firebase error codes to user-friendly messages
|
|
*/
|
|
const getAuthErrorMessage = (errorCode: string): string => {
|
|
const errorMessages: Record<string, string> = {
|
|
"auth/invalid-email": "Invalid email address format.",
|
|
"auth/user-disabled": "This user account has been disabled.",
|
|
"auth/user-not-found": "No account found with this email address.",
|
|
"auth/wrong-password": "Invalid email or password.",
|
|
"auth/invalid-credential": "Invalid email or password.",
|
|
"auth/too-many-requests": "Too many login attempts. Please try again later.",
|
|
"auth/operation-not-allowed": "Login is currently disabled. Please try again later.",
|
|
"auth/network-request-failed": "Network error. Please check your connection.",
|
|
"auth/invalid-action-code": "This password reset link is invalid or has expired.",
|
|
"auth/expired-action-code": "This password reset link has expired. Please request a new one.",
|
|
"auth/weak-password": "Password is too weak. Please choose a stronger password.",
|
|
};
|
|
|
|
return errorMessages[errorCode] || "An error occurred. Please try again.";
|
|
};
|
|
export { app };
|
|
|