All files / src/lib firebase-admin.ts

3.33% Statements 2/60
100% Branches 0/0
0% Functions 0/5
3.33% Lines 2/60

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 861x                         1x                                                                                                                                                
import { readFileSync } from "node:fs";
import { type App, cert, getApps, initializeApp } from "firebase-admin/app";
import { getAuth } from "firebase-admin/auth";
 
export type AuthUserMeta = {
  uid: string;
  email: string | null;
  emailVerified: boolean;
  authProvider: string | null;
  registeredAt: string | null;
  lastSignInAt: string | null;
};
 
let cached: App | null | undefined;
 
/** Ленивая инициализация Admin SDK из service-account JSON. null — если не настроено/ошибка. */
function getApp(path: string | undefined): App | null {
  if (cached !== undefined) return cached;
  if (!path) {
    cached = null;
    return cached;
  }
  try {
    const json = JSON.parse(readFileSync(path, "utf8")) as Record<string, unknown>;
    cached = getApps().length > 0 ? getApps()[0]! : initializeApp({ credential: cert(json) });
  } catch (err) {
    console.error("firebase-admin init failed:", err);
    cached = null;
  }
  return cached;
}
 
function toIso(value: string | undefined): string | null {
  if (!value) return null;
  const d = new Date(value);
  return Number.isNaN(d.getTime()) ? null : d.toISOString();
}
 
/**
 * Полный список пользователей Firebase Auth (постранично), либо null, если
 * Admin SDK не настроен — тогда вызывающий падает на список из БД.
 */
export async function listAuthUsers(path: string | undefined): Promise<AuthUserMeta[] | null> {
  const app = getApp(path);
  if (!app) return null;
 
  const auth = getAuth(app);
  const out: AuthUserMeta[] = [];
  let pageToken: string | undefined;
  do {
    const res = await auth.listUsers(1000, pageToken);
    for (const u of res.users) {
      out.push({
        uid: u.uid,
        email: u.email ?? null,
        emailVerified: u.emailVerified,
        authProvider: u.providerData[0]?.providerId ?? null,
        registeredAt: toIso(u.metadata.creationTime),
        lastSignInAt: toIso(u.metadata.lastSignInTime),
      });
    }
    pageToken = res.pageToken;
  } while (pageToken);
  return out;
}
 
/** Помечает email подтверждённым. Возвращает false, если Admin SDK не настроен. */
export async function setEmailVerified(path: string | undefined, uid: string): Promise<boolean> {
  const app = getApp(path);
  if (!app) return false;
  await getAuth(app).updateUser(uid, { emailVerified: true });
  return true;
}
 
/** Удаляет пользователя из Firebase Auth. No-op, если Admin SDK не настроен или юзер уже отсутствует. */
export async function deleteAuthUser(path: string | undefined, uid: string): Promise<void> {
  const app = getApp(path);
  if (!app) return;
  try {
    await getAuth(app).deleteUser(uid);
  } catch (err) {
    if ((err as { code?: string })?.code === "auth/user-not-found") return;
    throw err;
  }
}