@@ -32,6 +32,7 @@ import axios from 'axios';
3232import { db , FieldValue , FieldPath } from './firebase-config' ;
3333import { Faq } from './firebase-config/types' ;
3434import authenticate from './auth' ;
35+ import authenticateAdmin , { isAdminEmail } from './authAdmin' ;
3536import { admins } from '../../frontend/src/constants/HomeConsts' ;
3637
3738// Imports for email sending
@@ -53,6 +54,7 @@ const pendingBuildingsCollection = db.collection('pendingBuildings');
5354const contactQuestionsCollection = db . collection ( 'contactQuestions' ) ;
5455const blogPostCollection = db . collection ( 'blogposts' ) ;
5556const folderCollection = db . collection ( 'folders' ) ;
57+ const adminWhitelistCollection = db . collection ( 'adminWhitelist' ) ;
5658const travelTimesCollection = db . collection ( 'travelTimes' ) ;
5759
5860// Middleware setup
@@ -3022,6 +3024,115 @@ app.get('/api/folders/:folderId/apartments', authenticate, async (req, res) => {
30223024 }
30233025} ) ;
30243026
3027+ /**
3028+ * Is Admin – Returns whether the authenticated user has admin privileges.
3029+ *
3030+ * @route GET /api/is-admin
3031+ *
3032+ * @status
3033+ * - 200: { isAdmin: boolean }
3034+ * - 401: Not authenticated
3035+ */
3036+ app . get ( '/api/is-admin' , authenticate , async ( req , res ) => {
3037+ try {
3038+ if ( ! req . user ?. email ) return res . status ( 200 ) . json ( { isAdmin : false } ) ;
3039+ const adminStatus = await isAdminEmail ( req . user . email ) ;
3040+ return res . status ( 200 ) . json ( { isAdmin : adminStatus } ) ;
3041+ } catch ( err ) {
3042+ console . error ( err ) ;
3043+ return res . status ( 200 ) . json ( { isAdmin : false } ) ;
3044+ }
3045+ } ) ;
3046+
3047+ /**
3048+ * Get Admin Whitelist – Returns all emails in the Firestore adminWhitelist plus hardcoded superadmins.
3049+ *
3050+ * @route GET /api/admin/whitelist
3051+ *
3052+ * @status
3053+ * - 200: { superadmins: string[], whitelist: { id: string, email: string }[] }
3054+ * - 403: Not an admin
3055+ */
3056+ app . get ( '/api/admin/whitelist' , authenticateAdmin , async ( req , res ) => {
3057+ try {
3058+ const snapshot = await adminWhitelistCollection . orderBy ( 'addedAt' , 'asc' ) . get ( ) ;
3059+ const whitelist = snapshot . docs . map ( ( doc ) => ( { id : doc . id , ...doc . data ( ) } ) ) ;
3060+ return res . status ( 200 ) . json ( { superadmins : admins , whitelist } ) ;
3061+ } catch ( err ) {
3062+ console . error ( err ) ;
3063+ return res . status ( 500 ) . send ( 'Error fetching admin whitelist' ) ;
3064+ }
3065+ } ) ;
3066+
3067+ /**
3068+ * Add Admin – Adds an email to the Firestore adminWhitelist.
3069+ *
3070+ * @route POST /api/admin/whitelist
3071+ *
3072+ * @input {string} req.body.email – The Cornell email to add.
3073+ *
3074+ * @status
3075+ * - 201: Admin added successfully
3076+ * - 400: Missing or invalid email
3077+ * - 409: Email already in whitelist or superadmin list
3078+ * - 403: Not an admin
3079+ */
3080+ app . post ( '/api/admin/whitelist' , authenticateAdmin , async ( req , res ) => {
3081+ try {
3082+ const { email } = req . body ;
3083+ if ( ! email || typeof email !== 'string' || ! email . endsWith ( '@cornell.edu' ) ) {
3084+ return res . status ( 400 ) . send ( 'Valid Cornell email required' ) ;
3085+ }
3086+ if ( admins . includes ( email ) ) {
3087+ return res . status ( 409 ) . send ( 'Email is already a superadmin' ) ;
3088+ }
3089+ const existing = await adminWhitelistCollection . where ( 'email' , '==' , email ) . limit ( 1 ) . get ( ) ;
3090+ if ( ! existing . empty ) {
3091+ return res . status ( 409 ) . send ( 'Email is already in the whitelist' ) ;
3092+ }
3093+ const docRef = await adminWhitelistCollection . add ( {
3094+ email,
3095+ addedAt : new Date ( ) ,
3096+ addedBy : req . user ! . email ,
3097+ } ) ;
3098+ return res . status ( 201 ) . json ( { id : docRef . id , email } ) ;
3099+ } catch ( err ) {
3100+ console . error ( err ) ;
3101+ return res . status ( 500 ) . send ( 'Error adding admin' ) ;
3102+ }
3103+ } ) ;
3104+
3105+ /**
3106+ * Remove Admin – Removes an email from the Firestore adminWhitelist.
3107+ *
3108+ * @route DELETE /api/admin/whitelist/:email
3109+ *
3110+ * @input {string} req.params.email – URL-encoded email to remove.
3111+ *
3112+ * @status
3113+ * - 200: Admin removed
3114+ * - 400: Email is a superadmin (cannot remove via UI)
3115+ * - 404: Email not found in whitelist
3116+ * - 403: Not an admin
3117+ */
3118+ app . delete ( '/api/admin/whitelist/:email' , authenticateAdmin , async ( req , res ) => {
3119+ try {
3120+ const email = decodeURIComponent ( req . params . email ) ;
3121+ if ( admins . includes ( email ) ) {
3122+ return res . status ( 400 ) . send ( 'Cannot remove a superadmin via this endpoint' ) ;
3123+ }
3124+ const snapshot = await adminWhitelistCollection . where ( 'email' , '==' , email ) . limit ( 1 ) . get ( ) ;
3125+ if ( snapshot . empty ) {
3126+ return res . status ( 404 ) . send ( 'Email not found in whitelist' ) ;
3127+ }
3128+ await snapshot . docs [ 0 ] . ref . delete ( ) ;
3129+ return res . status ( 200 ) . send ( 'Admin removed successfully' ) ;
3130+ } catch ( err ) {
3131+ console . error ( err ) ;
3132+ return res . status ( 500 ) . send ( 'Error removing admin' ) ;
3133+ }
3134+ } ) ;
3135+
30253136/**
30263137 * Init Collections – Ensures required Firestore collections exist with correct schema.
30273138 * Idempotent: safe to run multiple times, never overwrites existing documents.
0 commit comments