Download src/server/middleware/auth.ts from sitcod3/modul-s: direct link, hf CLI and curl.
- Browser
- Download file 4.77 kB
-
https://huggingface.co/spaces/sitcod3/modul-s/resolve/main/src/server/middleware/auth.ts
- Command line
-
hf download hf://spaces/sitcod3/modul-s/src/server/middleware/auth.ts
-
curl -L -o auth.ts https://huggingface.co/spaces/sitcod3/modul-s/resolve/main/src/server/middleware/auth.ts
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." }); | |
| } | |
| })(); | |
| } | |