113 lines
3.4 KiB
TypeScript
113 lines
3.4 KiB
TypeScript
import { serve } from "@hono/node-server";
|
|
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
import * as Body from "common/Body";
|
|
import { Effect, Layer, Match, pipe } from "effect";
|
|
import { Hono } from "hono";
|
|
import * as path from "node:path";
|
|
import { config } from "./config.ts";
|
|
import * as Authentication from "./services/Authentication.ts";
|
|
import * as Database from "./services/Database.ts";
|
|
import { handle } from "./the_api.ts";
|
|
|
|
const FRONTEND_ROOT = "packages/frontend/build";
|
|
|
|
const CORS_HEADERS: [string, string][] = Match.value(config.NODE_ENV).pipe(
|
|
Match.when("development", (): [string, string][] => [
|
|
["Access-Control-Allow-Origin", "http://localhost:5173"],
|
|
["Access-Control-Allow-Methods", "POST, OPTIONS"],
|
|
["Access-Control-Allow-Credentials", "true"],
|
|
["Access-Control-Allow-Headers", "Content-Type"],
|
|
]),
|
|
Match.when("production", () => []),
|
|
Match.exhaustive,
|
|
);
|
|
|
|
const databaseLayer = Database.FromPath(config.DB_PATH);
|
|
|
|
const app = new Hono()
|
|
.get("/login", (ctx) => Effect.gen(function* () {
|
|
const code = ctx.req.query("code");
|
|
const state = ctx.req.query("state");
|
|
|
|
// Callback URL with query response type
|
|
if (code !== undefined || state !== undefined) {
|
|
yield* Authentication.getAndProcessIdToken(code ?? null, state ?? null);
|
|
return ctx.redirect(config.NODE_ENV === "production" ? `https://${config.HOSTNAME}/` : "http://localhost:5173/", 303);
|
|
}
|
|
|
|
// Initial login request; redirect to identity provider
|
|
const url = yield* pipe(
|
|
Authentication.Authentication,
|
|
Effect.flatMap(({ sessionId }) => Authentication.makeAuthorizationUrl(sessionId)),
|
|
);
|
|
|
|
return ctx.redirect(url);
|
|
}).pipe(
|
|
Effect.provide(Layer.provideMerge(Authentication.Live(ctx), databaseLayer)),
|
|
Effect.runPromise,
|
|
))
|
|
.post("/login", (ctx) => Effect.gen(function* () {
|
|
// Callback URL with form_post response type
|
|
|
|
const data = yield* Body.formData(ctx.req);
|
|
|
|
const code = data.get("code") as string | null;
|
|
const state = data.get("state") as string | null;
|
|
|
|
yield* Authentication.getAndProcessIdToken(code, state);
|
|
|
|
return ctx.redirect(config.NODE_ENV === "production" ? `https://${config.HOSTNAME}/` : "http://localhost:5173/", 303);
|
|
}).pipe(
|
|
Effect.provide(Layer.provideMerge(Authentication.Live(ctx), databaseLayer)),
|
|
Effect.runPromise,
|
|
))
|
|
.all("/api/:key", async (ctx) => {
|
|
const req = ctx.req;
|
|
|
|
const timestamp = new Date().toISOString();
|
|
const connInfo = getConnInfo(ctx);
|
|
console.log(`${timestamp} ${req.method} ${req.url} ${connInfo.remote.address!}`);
|
|
|
|
if (req.method === "OPTIONS") {
|
|
return new Response(null, {
|
|
status: 204,
|
|
headers: CORS_HEADERS,
|
|
});
|
|
}
|
|
|
|
if (req.method !== "POST") {
|
|
return new Response(null, {
|
|
status: 405,
|
|
headers: CORS_HEADERS,
|
|
});
|
|
}
|
|
|
|
const authenticationLayer = Authentication.Live(ctx);
|
|
const layers = Layer.provideMerge(authenticationLayer, databaseLayer);
|
|
|
|
const { body, status } = await pipe(
|
|
handle(req),
|
|
Effect.provide(layers),
|
|
Effect.runPromise,
|
|
);
|
|
|
|
for (const [name, value] of CORS_HEADERS) {
|
|
ctx.header(name, value);
|
|
}
|
|
|
|
if (body !== null) {
|
|
ctx.header("Content-Type", "application/cbor");
|
|
return ctx.body(body, status);
|
|
} else {
|
|
return ctx.body(body, status);
|
|
}
|
|
})
|
|
.use("/assets/*", serveStatic({ root: FRONTEND_ROOT }))
|
|
.use("*", serveStatic({ path: path.join(FRONTEND_ROOT, "index.html") }));
|
|
|
|
serve({
|
|
fetch: app.fetch,
|
|
port: config.PORT,
|
|
});
|