@zuzjs/flare-admin
v0.1.2
Published
Server-side admin SDK for FlareServer — mint custom auth tokens from your backend (like firebase-admin)
Maintainers
Readme
@zuzjs/flare-admin
Server-side admin SDK for FlareServer — the self-hosted Firebase alternative.
Works likefirebase-admin: runs only on your backend, never in a browser.
How it works
Your Express app HTTP REST FlareServer
────────────────── ──────────► ─────────────────────
connectApp({ POST /admin/token Validates adminKey
serverUrl, { uid, role, claims } Signs JWT w/ jwtSecret
appId, ◄──────────────── Returns { token }
adminKey,
})
admin.auth()
.createCustomToken(uid)
→ Promise<string> ──────────────────────► client: flare.auth(token)
socket elevated ✓No MongoDB URI is ever shared. The SDK talks to FlareServer's /admin/token REST endpoint using only the adminKey. This works identically whether you self-host Flare or use it as SaaS at https://flare.zuz.com.pk.
Installation
npm install @zuzjs/flare-admin
# or
pnpm add @zuzjs/flare-adminQuick start
1. Get your keys
flare app create my-appThis prints two configs:
| Config | Safe for... |
|---|---|
| apiKey | Browser / client-side |
| adminKey | Server-side only — never expose to browser |
2. Set environment variables
# .env (server-side only)
FLARE_URL=hhttps://flare.zuzcdn.net # your FlareServer URL
FLARE_APP_ID=my-app
FLARE_ADMIN_KEY=FA_ADMIN_xxxxxxxxxxxx3. Initialize once at boot
import { connectApp } from "@zuzjs/flare-admin";
const admin = connectApp({
serverUrl: process.env.FLARE_URL!,
appId: process.env.FLARE_APP_ID!,
adminKey: process.env.FLARE_ADMIN_KEY!,
});4. Mint a token in your login route
import { getApp } from "@zuzjs/flare-admin";
app.post("/login", async (req, res) => {
// --- your existing auth logic ---
const user = await myAuthLogic(req.body);
if (!user) return res.status(401).json({ error: "invalid credentials" });
// Set your own session / cookie as normal
req.session.userId = user.id;
// --- the bridge ---
const flareToken = await getApp().auth().createCustomToken(user.id, {
role: user.isAdmin ? "admin" : "user",
claims: { email: user.email, plan: user.plan },
});
res.json({ user, flareToken });
});5. Authenticate the Flare client
// Browser / React Native
import FlareClient from "@zuzjs/flare";
const flare = new FlareClient({
endpoint: "https://flare.zuzcdn.net",
appId: "my-app",
apiKey: "FA_xxxxxxxx", // browser-safe key
});
flare.connect();
// After login:
const { flareToken } = await fetch("/login", { method: "POST", body: ... }).then(r => r.json());
await flare.auth(flareToken);
// ✅ Socket is now elevated — auth.uid and auth.role are setAPI
connectApp(config, name?)
Initialize a FlareAdmin app. Idempotent — safe to call at module scope.
const admin = connectApp({
serverUrl: string; // FlareServer base URL
appId: string; // App ID from `flare app create`
adminKey: string; // Admin key — server-side only
defaultTtl?: string; // Default token TTL, e.g. "24h" (default)
});getApp(name?)
Retrieve an already-initialized app instance.
auth(name?)
Shorthand for getApp(name).auth().
admin.auth().createCustomToken(uid, opts?)
Mint a custom auth token.
const token = await admin.auth().createCustomToken(uid, {
role?: "user" | "admin" | "anon", // default: "user"
claims?: Record<string, unknown>, // extra JWT payload fields
ttl?: string, // e.g. "1h", "7d"
});Security rules on the server
Once a client calls flare.auth(token), the FlareServer socket has:
auth.uid = the uid you passed to createCustomToken()
auth.role = "user" | "admin" | "anon"Use these in your security rules:
{
"users": { ".read": "auth != null", ".write": "auth.uid == $docId" },
"posts": { ".read": "true", ".write": "auth != null" },
"settings": { ".read": "auth != null", ".write": "auth.role == 'admin'" },
"*": { ".read": "false", ".write": "false" }
}Multi-tenant / multiple apps
const adminA = connectApp({ serverUrl, appId: "app-a", adminKey: "..." }, "a");
const adminB = connectApp({ serverUrl, appId: "app-b", adminKey: "..." }, "b");
const tokenA = await getApp("a").auth().createCustomToken(userId);
const tokenB = await getApp("b").auth().createCustomToken(userId);Admin-Only Auth Join Field Control
Auth-user joins in admin SDK default to full fields.
Use allowSensitiveAuthUserFields(false) per query when you want public-profile-only output:
const safeBoards = await admin.db()
.collection("boards")
.join("users", { source: "team.uid", target: "id", as: "team" })
.allowSensitiveAuthUserFields(false)
.get();Important:
- This option is admin-only.
- Normal client/system query paths cannot enable sensitive auth-user fields.
Data Mapper (Admin)
You can pass dataMapper in connectApp(...) to shape inbound data in admin SDK reads.
Mapping rules:
- Base collection rows use the mapper key matching the collection name.
- Join payload rows use the mapper key matching join
as.
const admin = connectApp({
serverUrl: process.env.FLARE_URL!,
appId: process.env.FLARE_APP_ID!,
adminKey: process.env.FLARE_ADMIN_KEY!,
dataMapper: {
boards: (row) => ({
id: row.id,
name: row.name,
description: row.description,
createdAt: new Date(row.createdAt ?? row.created_at),
}),
team: (row) => ({
id: row.id,
name: row.authMeta?.additionalParams?.name || "Unknown",
email: row.email,
createdAt: new Date(row.createdAt ?? row.created_at),
}),
},
});
const rows = await admin.db()
.collection("boards")
.where({ boardId: "123" })
.join("users", {
source: "team.uid",
target: "uid",
as: "team",
})
.get();
// rows[*] is mapped by dataMapper.boards
// rows[*].team[*] is mapped by dataMapper.team (join alias)Tip: for as: "team", define dataMapper.team.
Query Builder Parity (Admin)
admin.db().collection(...) and admin.connection().collection(...) are aligned with the client-style query builder API.
Supported filter helpers
where, and, or, in, andIn, orIn, notIn, andNotIn, orNotIn, arrayContains, andArrayContains, orArrayContains, arrayContainsAny, andArrayContainsAny, orArrayContainsAny, some, andSome, orSome, like, andLike, orLike, notLike, andNotLike, orNotLike, exists, andExists, orExists, notExists, andNotExists, orNotExists
Supported sort / cursor / aggregate helpers
latest, newest, oldest, orderBy, limit, offset, startAt, startAfter, endAt, endBefore, count, sum, avg, min, max, distinct, groupBy, having, select, distinctField, vectorSearch
Supported join helpers
join, Join, joinNested, JoinNested, withRelation
Example:
const rows = await admin.db()
.collection("boards")
.where({ uid: userId })
.orSome("team", { uid: userId })
.join("lists", { source: "id", target: "boardId", as: "lists" })
.join("users", { source: "team.uid", target: "id", as: "teamMembers" })
.joinNested("lists", "cards", { source: "id", target: "listId", as: "cards" })
.withRelation("team.uid->users.id as collaborators")
.orderBy("updatedAt", "desc")
.limit(20)
.get();Realtime parity
The same query builder surface is available on socket subscriptions:
const stop = admin.connection()
.collection("boards")
.where({ uid: userId })
.join("lists", { source: "id", target: "boardId", as: "lists" })
.onSnapshot((event) => {
console.log(event.type, event.data);
});Debugging structured query output
Both DB and realtime collection references provide getRawQuery():
const raw = admin.db()
.collection("boards")
.where({ uid: userId })
.withRelation("team.uid->users.id as collaborators")
.getRawQuery();
console.log(raw.collection, raw.query);