sitcod3's picture
Upload src/server/middleware/auth.ts with huggingface_hub
8b499f7 verified
Raw History Blame Contribute Delete
4.77 kB
// src/server/middleware/auth.ts
//
// Supabase JWT verification + license gating for the paid API.
//
// The central Supabase project issues the session JWT; this backend verifies
// it against the project's PUBLIC JWKS endpoint (no shared database needed).
// License rows are read from the central project via its service-role REST
// API (server-side only).
//
// Enforcement is OFF by default (LICENSE_ENFORCEMENT != "true") so existing
// open/demo deployments keep working. Turn it on at go-live:
//
// SUPABASE_URL=https://ciytvulujiqemqljablq.supabase.co
// SUPABASE_SERVICE_ROLE_KEY=<service role key, server only>
// LICENSE_ENFORCEMENT=true
//
// Frontend equivalent lives in the FortyDN website repo (src/lib/productApi.ts).
// Ported from BRIDGE Modul-X (src/server/middleware/auth.ts) — same contract.
import type { Request, Response, NextFunction } from "express";
import { createRemoteJWKSet, jwtVerify } from "jose";
export const CENTRAL_SUPABASE_URL =
process.env.SUPABASE_URL || "https://ciytvulujiqemqljablq.supabase.co";
const JWKS_URL = new URL(`${CENTRAL_SUPABASE_URL}/auth/v1/.well-known/jwks.json`);
let jwks: ReturnType<typeof createRemoteJWKSet> | null = null;
function getJwks() {
if (!jwks) jwks = createRemoteJWKSet(JWKS_URL);
return jwks;
}
function enforcementOn(): boolean {
return process.env.LICENSE_ENFORCEMENT === "true";
}
export interface AuthClaims {
userId: string;
email?: string;
}
function bearerToken(req: Request): string | null {
const header = req.headers.authorization || "";
if (!header.startsWith("Bearer ")) return null;
const token = header.slice(7).trim();
return token.length > 0 ? token : null;
}
export async function verifySupabaseJwt(token: string): Promise<AuthClaims> {
const { payload } = await jwtVerify(token, getJwks(), {
audience: "authenticated",
});
const sub = payload.sub;
if (!sub) throw new Error("JWT has no sub claim");
return {
userId: sub,
email: typeof payload.email === "string" ? payload.email : undefined,
};
}
/** Combos that unlock Modul-S (mirrors the website catalog). */
const MODULS_GRANTS = new Set([
"modul-s",
"combo-validate-send",
"combo-full-bridge",
]);
export async function hasModulSLicense(userId: string): Promise<boolean> {
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!serviceKey) {
console.warn("[auth] SUPABASE_SERVICE_ROLE_KEY not set — cannot check licenses.");
return false;
}
const params = new URLSearchParams({
user_id: `eq.${userId}`,
status: "eq.active",
select: "product",
});
const res = await fetch(`${CENTRAL_SUPABASE_URL}/rest/v1/licenses?${params}`, {
headers: {
apikey: serviceKey,
Authorization: `Bearer ${serviceKey}`,
},
});
if (!res.ok) {
const body = await res.text().catch(() => "");
// Missing table (migrations not applied yet) => fail closed with a hint.
console.warn(`[auth] license lookup HTTP ${res.status}: ${body.slice(0, 200)}`);
return false;
}
const rows = (await res.json()) as { product?: string }[];
return (
Array.isArray(rows) && rows.some((r) => r.product && MODULS_GRANTS.has(r.product))
);
}
/**
* Requires a valid Supabase JWT. No-op (with a one-time warning) while
* LICENSE_ENFORCEMENT is off so demo deployments keep working.
*/
export function requireAuth(req: Request, res: Response, next: NextFunction) {
if (!enforcementOn()) return next();
void (async () => {
const token = bearerToken(req);
if (!token) {
return res.status(401).json({ detail: "Missing bearer token — sign in first." });
}
try {
const claims = await verifySupabaseJwt(token);
(req as Request & { auth?: AuthClaims }).auth = claims;
return next();
} catch {
return res.status(401).json({ detail: "Invalid or expired token." });
}
})();
}
/**
* Requires a valid JWT AND an active Modul-S license (direct or via combo).
* No-op while LICENSE_ENFORCEMENT is off.
*/
export function requireModulSLicense(req: Request, res: Response, next: NextFunction) {
if (!enforcementOn()) return next();
void (async () => {
const token = bearerToken(req);
if (!token) {
return res.status(401).json({ detail: "Missing bearer token — sign in first." });
}
try {
const claims = await verifySupabaseJwt(token);
(req as Request & { auth?: AuthClaims }).auth = claims;
const ok = await hasModulSLicense(claims.userId);
if (!ok) {
return res
.status(402)
.json({ detail: "No active Modul-S license — see /pricing." });
}
return next();
} catch {
return res.status(401).json({ detail: "Invalid or expired token." });
}
})();
}