"use server"; import type { LoginInput, RegisterInput, AdminLoginInput } from "@/lib/schemas"; import { getDb } from "@/lib/mongodb"; import bcrypt from 'bcryptjs'; import { cookies, headers as nextHeaders } from 'next/headers'; import { redirect } from 'next/navigation'; import type { User } from "@/lib/types"; import { revalidatePath } from "next/cache"; import { ObjectId } from 'mongodb'; // Import ObjectId const ADMIN_EMAIL = 'ogdavidcyril@gmail.com'; const REFERRAL_COIN_BONUS_FOR_REFERRER = 10; const REFERRAL_WELCOME_BONUS_FOR_NEW_USER = 5; const ADMIN_INITIAL_COINS = 999999; const MAX_ACCOUNTS_PER_IP = 1; // Limit for accounts per IP export async function loginUser(data: LoginInput) { try { const db = await getDb(); const usersCollection = db.collection("users"); const user = await usersCollection.findOne({ email: data.email }); if (!user) { return { success: false, message: "Invalid email or password." }; } const isPasswordValid = await bcrypt.compare(data.password, user.passwordHash); if (!isPasswordValid) { return { success: false, message: "Invalid email or password." }; } cookies().set('loggedInUserId', user._id.toString(), { path: '/', httpOnly: true, secure: process.env.NODE_ENV === 'production', maxAge: 60 * 60 * 24 * 7, sameSite: 'lax', }); redirect('/dashboard'); } catch (error) { if (typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string' && error.message.includes('NEXT_REDIRECT')) { throw error; } console.error("Login error:", error); return { success: false, message: "An unexpected error occurred during login." }; } } function getClientIp(): string | null { const headersList = nextHeaders(); const xForwardedFor = headersList.get('x-forwarded-for'); if (xForwardedFor) { return xForwardedFor.split(',')[0].trim(); } const xRealIp = headersList.get('x-real-ip'); if (xRealIp) { return xRealIp.trim(); } // Fallback, might not be available or accurate depending on deployment // For Firebase App Hosting, 'x-forwarded-for' should be populated by the load balancer. // Other headers like `fastly-client-ip` or `true-client-ip` might be available too. return headersList.get('remoteAddress') || null; // 'remoteAddress' is not standard here } export async function registerUser(data: RegisterInput) { try { const db = await getDb(); const usersCollection = db.collection("users"); const existingUser = await usersCollection.findOne({ email: data.email }); if (existingUser) { return { success: false, message: "This email is already registered." }; } const clientIp = getClientIp(); if (clientIp) { const existingAccountsWithIp = await usersCollection.countDocuments({ registrationIp: clientIp }); if (existingAccountsWithIp >= MAX_ACCOUNTS_PER_IP) { return { success: false, message: "Registration limit reached for this network. Please try again later or contact support if you believe this is an error." }; } } else { console.warn("Could not determine client IP address for registration. IP-based limit will not be applied for this user."); // Decide if you want to block registration if IP is not found, or proceed with a warning. // For now, we'll proceed but log it. } const passwordHash = await bcrypt.hash(data.password, 10); let roleToAssign: 'user' | 'admin' = 'user'; let initialCoins = 0; if (data.email === ADMIN_EMAIL) { roleToAssign = 'admin'; initialCoins = ADMIN_INITIAL_COINS; } const newUserObjectIdString = new ObjectId().toString(); const namePart = data.name.substring(0, 5).toLowerCase().replace(/[^a-z0-9]/g, ''); const randomPart = newUserObjectIdString.slice(-6); const referralCode = `${namePart}${randomPart}`; const newUserDocument: User = { _id: newUserObjectIdString, name: data.name, email: data.email, passwordHash, role: roleToAssign, coins: initialCoins, lastCoinClaim: null, referralCode: referralCode, referredBy: null, createdAt: new Date(), registrationIp: clientIp || undefined, // Store IP if available }; if (data.referralCode) { const referrer = await usersCollection.findOne({ referralCode: data.referralCode }); if (referrer) { await usersCollection.updateOne( { _id: referrer._id }, { $inc: { coins: REFERRAL_COIN_BONUS_FOR_REFERRER } } ); newUserDocument.referredBy = referrer._id.toString(); if (data.email !== ADMIN_EMAIL) { newUserDocument.coins += REFERRAL_WELCOME_BONUS_FOR_NEW_USER; } } else { console.log(`Referral code ${data.referralCode} not found or invalid.`); } } const result = await usersCollection.insertOne(newUserDocument); if (!result.insertedId) { return { success: false, message: "Failed to register user." }; } return { success: true, message: "Registration successful! You can now log in." }; } catch (error) { console.error("Registration error:", error); return { success: false, message: "An unexpected error occurred during registration." }; } } export async function adminLoginUser(data: AdminLoginInput) { try { const db = await getDb(); const usersCollection = db.collection("users"); const user = await usersCollection.findOne({ email: data.email }); if (!user) { console.log(`Admin login attempt: User not found for email ${data.email}`); return { success: false, message: "Invalid email or password." }; } const isPasswordValid = await bcrypt.compare(data.password, user.passwordHash); if (!isPasswordValid) { console.log(`Admin login attempt: Invalid password for email ${data.email}`); return { success: false, message: "Invalid email or password." }; } console.log(`Admin login attempt: Password valid for ${data.email}`); // For the designated admin email, ensure role is 'admin' and coins are set correctly. if (user.email === ADMIN_EMAIL) { const updates: Partial> = {}; let needsDatabaseUpdate = false; if (user.role !== 'admin') { updates.role = 'admin'; needsDatabaseUpdate = true; console.log(`Admin login: ${user.email} role is ${user.role}, needs update to admin.`); } if (user.coins !== ADMIN_INITIAL_COINS) { updates.coins = ADMIN_INITIAL_COINS; needsDatabaseUpdate = true; console.log(`Admin login: ${user.email} coins are ${user.coins}, needs update to ${ADMIN_INITIAL_COINS}.`); } if (needsDatabaseUpdate) { await usersCollection.updateOne({ _id: user._id }, { $set: updates }); console.log(`Admin user ${user.email} DB record updated/ensured: role to ${updates.role || user.role}, coins to ${updates.coins !== undefined ? updates.coins : user.coins}`); // Update the local user object to reflect changes immediately if (updates.role) user.role = updates.role; if (updates.coins !== undefined) user.coins = updates.coins; } else { console.log(`Admin login: ${user.email} already has correct admin role and coins.`); } } else if (user.role !== 'admin') { // If it's not the ADMIN_EMAIL, then they must already be an admin to use this login route. console.log(`Admin login attempt: ${data.email} is not the ADMIN_EMAIL and does not have admin role.`); return { success: false, message: "Access denied. Not an admin account." }; } console.log(`Admin login: Setting cookie for user ${user._id} (${user.email}) with role ${user.role}`); cookies().set('loggedInUserId', user._id.toString(), { path: '/', httpOnly: true, secure: process.env.NODE_ENV === 'production', maxAge: 60 * 60 * 24 * 7, // 1 week sameSite: 'lax', }); revalidatePath('/admin/dashboard'); console.log(`Admin login: Revalidated /admin/dashboard, redirecting...`); redirect('/admin/dashboard'); } catch (error) { if (typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string' && error.message.includes('NEXT_REDIRECT')) { throw error; } console.error("Admin login error:", error); return { success: false, message: "An unexpected error occurred during admin login." }; } } export async function logoutUser() { try { cookies().delete('loggedInUserId'); revalidatePath('/'); redirect('/login'); } catch (error) { if (typeof error === 'object' && error !== null && 'message' in error && typeof error.message === 'string' && error.message.includes('NEXT_REDIRECT')) { throw error; } console.error("Logout error:", error); return { success: false, message: "Logout failed due to an unexpected error." }; } } export interface LoggedInUser { _id: string; name: string; email: string; role: 'user' | 'admin'; coins: number; lastCoinClaim: Date | null; referralCode: string; createdAt: Date; referredUsersCount: number; referralCoinsEarned: number; // registrationIp is not typically exposed in LoggedInUser for privacy, but available in DB User type. } export async function getLoggedInUser(): Promise { try { const userIdCookie = cookies().get('loggedInUserId'); if (!userIdCookie?.value) { return null; } const userIdString = userIdCookie.value; const db = await getDb(); const usersCollection = db.collection('users'); const user = await usersCollection.findOne({ _id: userIdString }); if (user) { // Ensure admin user has correct role and coins, self-correct if needed if (user.email === ADMIN_EMAIL) { let adminNeedsUpdate = false; const adminUpdates: Partial> = {}; if (user.role !== 'admin') { adminUpdates.role = 'admin'; adminNeedsUpdate = true; } if (user.coins !== ADMIN_INITIAL_COINS) { adminUpdates.coins = ADMIN_INITIAL_COINS; adminNeedsUpdate = true; } if (adminNeedsUpdate) { await usersCollection.updateOne({ _id: user._id }, { $set: adminUpdates }); if(adminUpdates.role) user.role = adminUpdates.role; if(adminUpdates.coins !== undefined ) user.coins = adminUpdates.coins; console.log(`Self-correction in getLoggedInUser for ${user.email}: role to ${user.role}, coins to ${user.coins}`); } } const referredUsersCount = await usersCollection.countDocuments({ referredBy: user._id }); const referralCoinsEarned = referredUsersCount * REFERRAL_COIN_BONUS_FOR_REFERRER; return { _id: user._id, name: user.name, email: user.email, role: user.role || 'user', coins: user.coins || 0, lastCoinClaim: user.lastCoinClaim ? new Date(user.lastCoinClaim) : null, referralCode: user.referralCode || 'N/A', createdAt: user.createdAt ? new Date(user.createdAt) : new Date(0), referredUsersCount: referredUsersCount, referralCoinsEarned: referralCoinsEarned, }; } console.log(`getLoggedInUser: No user found for ID ${userIdString}`); return null; } catch (error) { console.error("Error fetching logged in user:", error); return null; } }