Files
edgeever/apps/web/local-dev-auth.ts

72 lines
3.7 KiB
TypeScript

import { execFile } from "node:child_process";
import { promisify } from "node:util";
import type { Plugin } from "vite";
const execute = promisify(execFile);
const apiUrl = "http://127.0.0.1:8787/api/v1/auth/session";
const createSession = async () => {
const result = await execute("bun", ["scripts/local-dev.mjs", "auth-session", "local"], {
cwd: new URL("../../", import.meta.url),
timeout: 10000,
});
return JSON.parse(result.stdout) as { token: string; maxAge: number };
};
export const localDevelopmentAuth = (provisionSession = createSession): Plugin => ({
name: "edgeever-local-development-auth",
apply: "serve",
configureServer(server) {
// Only the persistent local profile may create sessions in local SQLite.
// Plain dev:web, remote development, demo, and production never do this.
if (process.env.EDGE_EVER_DEVELOPMENT_PROFILE !== "local"
|| process.env.EDGE_EVER_LOCAL_AUTO_LOGIN === "false") return;
server.middlewares.use(async (request, response, next) => {
const path = request.url?.split("?")[0];
const logout = path === "/api/v1/auth/logout" && request.method === "POST";
if (!logout && (path !== "/api/v1/auth/session" || request.method !== "GET")) return next();
const host = request.headers.host ?? "";
if (!/^(127\.0\.0\.1|localhost|\[::1\])(?::\d+)?$/.test(host)) return next();
if (request.headers.origin && request.headers.origin !== `http://${host}`) return next();
if (request.headers["sec-fetch-site"] === "cross-site") return next();
try {
const headers = new Headers();
if (request.headers.cookie) headers.set("Cookie", request.headers.cookie);
if (request.headers.authorization) headers.set("Authorization", request.headers.authorization);
if (logout) {
const upstream = await fetch(apiUrl.replace(/session$/, "logout"), {
method: "POST", headers, signal: AbortSignal.timeout(5000),
});
response.statusCode = upstream.status;
response.setHeader("Set-Cookie", [
...upstream.headers.getSetCookie(),
...(upstream.ok ? ["edgeever_local_logged_out=1; Path=/; HttpOnly; SameSite=Lax"] : []),
]);
response.setHeader("Content-Type", "application/json");
response.setHeader("Cache-Control", "no-store");
response.end(await upstream.text());
return;
}
const current = await fetch(apiUrl, { headers, signal: AbortSignal.timeout(5000) });
if (!current.ok) return next();
const session = await current.json() as { authRequired: boolean; authenticated: boolean };
if (session.authenticated || request.headers.authorization) return next();
// An explicit logout should still let developers test the login screen.
if (request.headers.cookie?.includes("edgeever_local_logged_out=1")) return next();
const { token, maxAge } = await provisionSession();
const authenticated = await fetch(apiUrl, {
headers: { Authorization: `Bearer ${token}` }, signal: AbortSignal.timeout(5000),
});
if (!authenticated.ok) throw new Error(`Local session check failed: HTTP ${authenticated.status}`);
const body = await authenticated.json() as { authRequired: boolean; authenticated: boolean };
if (!body.authRequired || !body.authenticated) throw new Error("Local API did not accept the development session.");
response.setHeader("Set-Cookie", `edgeever_session=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${maxAge}`);
response.setHeader("Cache-Control", "no-store");
response.setHeader("Content-Type", "application/json");
response.end(JSON.stringify(body));
} catch (error) {
next(error);
}
});
},
});