import * as admin from "firebase-admin"; import {onCall, HttpsError} from "firebase-functions/v2/https"; import {Logger} from "../core/logger/logger"; import {handleListUsers, AdminUser} from "./list_users"; type AdminCallerRole = "admin" | "demo_admin"; async function callerAdminRole( db: admin.firestore.Firestore, uid: string, ): Promise { const callerDoc = await db.collection("users").doc(uid).get(); if (!callerDoc.exists) return null; const role = callerDoc.get("role"); if (role === "admin" || role === "demo_admin") return role; return null; } function maskEmail(email: string | null | undefined): string | null { if (!email || email.length === 0) return null; const at = email.indexOf("@"); if (at <= 0) return "***"; const local = email.slice(0, at); const domain = email.slice(at + 1); const maskedLocal = local.length <= 1 ? "*" : `${local[0]}***`; const dot = domain.indexOf("."); const maskedDomain = dot > 0 ? `${domain[0]}***${domain.slice(dot)}` : `${domain[0]}***`; return `${maskedLocal}@${maskedDomain}`; } function maskUsersPayload( data: Record, ): Record { const rawUsers = data.users; if (!Array.isArray(rawUsers)) return data; const users = rawUsers as AdminUser[]; return { ...data, users: users.map((u) => ({...u, email: maskEmail(u.email)})), }; } /** * Admin console users API. * * Paginated list (default): * { page?, pageSize?, search?, subscribersOnly?, sort?, sortAsc? } * → { users, totalUsers, page, pageSize, pageCount, searchCapped? } * * Overview metrics (cheap aggregates, no full scan): * { overview: true } * → { totalUsers, subscribers, new7d, daily[14], firstDayMs, lastDayMs } * * Security: caller must be authenticated with role == "admin" or "demo_admin". * `demo_admin` receives masked emails in list responses (public web demo). */ export const listUsers = onCall(async (request) => { if (!request.auth) { throw new HttpsError("unauthenticated", "Authentication required"); } const callerUid = request.auth.uid; const db = admin.firestore(); const logger = new Logger("listUsers"); const role = await callerAdminRole(db, callerUid); if (!role) { throw new HttpsError("permission-denied", "Admin role required"); } try { const data = await handleListUsers( db, request.data as Record, logger, ); if (role === "demo_admin") { return maskUsersPayload(data as Record); } return data; } catch (e) { if (e instanceof HttpsError) throw e; logger.error(`listUsers error: ${e}`); throw new HttpsError("internal", "Failed to list users"); } });