Switch to internally managed roles, adapt frontend

This commit is contained in:
2025-10-07 02:04:35 +02:00
parent dc0ec5c635
commit 90729736ca
11 changed files with 248 additions and 351 deletions

View File

@@ -35,89 +35,88 @@ const homepage = new Response(Bun.file(path.join(FRONTEND_ROOT, "index.html")));
const databaseLayer = Database.FromPath(config.DB_PATH);
const login = (code: string | null, state: string | null) => Effect.gen(function* () {
const { sessionId } = yield* Authentication.Authentication;
const db = yield* Database.Database;
const session = yield* db
.selectFrom("Session")
.select(["codeVerifier"])
.where("sessionId", "=", sessionId)
.$call(Database.executeTakeFirst);
const codeVerifier = Option.fromNullable(session.codeVerifier);
if (code !== null && state !== null && Option.isSome(codeVerifier)) {
const res = yield* fetch(config.OAUTH_TOKEN_ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
"client_id": config.CLIENT_ID,
"code": code,
"redirect_uri": Authentication.REDIRECT_URI,
"grant_type": "authorization_code",
"code_verifier": codeVerifier.value,
"client_secret": Redacted.value(config.CLIENT_SECRET),
}).toString(),
});
const body = yield* Body.json(res);
const { id_token: idToken } = body as { id_token: string };
const idTokenPayload = yield* pipe(
idToken,
Authentication.getJwtTokenPayload(Authentication.IdTokenPayload),
);
const { userId } = yield* Authentication.upsertUser(idTokenPayload);
yield* db
.updateTable("Session")
.set({
codeVerifier: null,
state: null,
userId,
})
.where("sessionId", "=", sessionId)
.$call(Database.execute);
}
});
Bun.serve({
routes: {
...assetRoutes,
"/login": {
GET: async (req) => {
const res = await pipe(
GET: (req) => Effect.gen(function* () {
const searchParams = new URL(req.url).searchParams;
if (searchParams.has("code") || searchParams.has("state")) {
const code = searchParams.get("code");
const state = searchParams.get("state");
yield* login(code, state);
return Response.redirect(config.NODE_ENV === "production" ? `https://${config.HOSTNAME}/` : "http://localhost:5173/", 303);
}
const res = yield* pipe(
Authentication.Authentication,
Effect.flatMap(({ sessionId }) => Authentication.makeAuthorizationUrl({
external: new URL(req.url).searchParams.has("external"),
sessionId,
})),
Effect.flatMap(({ sessionId }) => Authentication.makeAuthorizationUrl(sessionId)),
Effect.map((url) => Response.redirect(url)),
Effect.provide(Layer.provideMerge(Authentication.Live(req), databaseLayer)),
Effect.runPromise,
);
return res;
},
}).pipe(
Effect.provide(Layer.provideMerge(Authentication.Live(req), databaseLayer)),
Effect.runPromise,
),
POST: (req) => Effect.gen(function* () {
const { sessionId } = yield* Authentication.Authentication;
const db = yield* Database.Database;
const data = yield* Body.formData(req);
const code = data.get("code") as string | null;
const state = data.get("state") as string | null;
const session = yield* db
.selectFrom("Session")
.select(["external", "codeVerifier"])
.where("sessionId", "=", sessionId)
.$call(Database.executeTakeFirst);
const external = pipe(
session.external,
Option.fromNullable,
Option.map((external) => external !== 0),
);
const codeVerifier = Option.fromNullable(session.codeVerifier);
if (code !== null && state !== null && Option.isSome(external) && Option.isSome(codeVerifier)) {
const { tokenEndpoint } = external.value
? Authentication.EXTERNAL_OAUTH_CONFIGURATION
: Authentication.INTERNAL_OAUTH_CONFIGURATION;
const res = yield* fetch(tokenEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: new URLSearchParams({
"client_id": config.CLIENT_ID,
"code": code,
"redirect_uri": Authentication.REDIRECT_URI,
"grant_type": "authorization_code",
"code_verifier": codeVerifier.value,
"client_secret": Redacted.value(config.CLIENT_SECRET),
}).toString(),
});
const {
access_token: accessToken,
refresh_token: refreshToken,
id_token: idToken,
} = (yield* Body.json(res)) as {
access_token: string,
refresh_token: string,
id_token: string,
};
yield* db
.updateTable("Session")
.set({
accessToken,
refreshToken,
idToken,
codeVerifier: null,
state: null,
})
.where("sessionId", "=", sessionId)
.$call(Database.execute);
}
yield* login(code, state);
return Response.redirect(config.NODE_ENV === "production" ? `https://${config.HOSTNAME}/` : "http://localhost:5173/", 303);
}).pipe(