72 lines
3.7 KiB
TypeScript
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);
|
|
}
|
|
});
|
|
},
|
|
});
|