import { create, getNumericDate } from "@zaubrik/djwt"; import { oauth2Client } from "@lib/auth.ts"; import { getCookies, setCookie } from "@std/http/cookie"; import { codeChallengeMap } from "./login.ts"; import { GITEA_SERVER, JWT_SECRET, SESSION_DURATION } from "@lib/env.ts"; import { GiteaOauthUser } from "@lib/types.ts"; import { BadRequestError } from "@lib/errors.ts"; import { db } from "@lib/db/sqlite.ts"; import { userTable } from "@lib/db/schema.ts"; import { eq } from "drizzle-orm"; import { define } from "../../../utils.ts"; export const handler = define.handlers({ async GET(ctx) { if (!JWT_SECRET) { throw new BadRequestError(); } // Exchange the authorization code for an access token const cookies = getCookies(ctx.req.headers); const stored = codeChallengeMap.get(cookies["code_challenge"]); if (!stored) { throw new BadRequestError(); } const { codeVerifier, redirect } = stored; const tokens = await oauth2Client.code.getToken(ctx.req.url, { codeVerifier, }); // Use the access token to make an authenticated API request const userInfo = `${GITEA_SERVER}/login/oauth/userinfo`; const userResponse = await fetch(userInfo, { headers: { Authorization: `token ${tokens.accessToken}`, }, }); const oauthUser = await userResponse.json() as GiteaOauthUser; let user = await db.select().from(userTable).where( eq(userTable.name, oauthUser.name), ).limit(1).then((users) => users[0]); if (!user) { const res = await db.insert(userTable).values({ id: crypto.randomUUID(), email: oauthUser.email, name: oauthUser.name, }).returning(); user = res[0]; } if (!JWT_SECRET) { throw new BadRequestError(); } const key = await crypto.subtle.importKey( "raw", new TextEncoder().encode(JWT_SECRET), { name: "HMAC", hash: "SHA-512" }, false, ["sign", "verify"], ); const jwt = await create({ alg: "HS512", type: "JWT" }, { id: user.id, name: user.name, exp: getNumericDate(SESSION_DURATION), }, key); const headers = new Headers({ location: redirect || "/", }); setCookie(headers, { name: "session_cookie", value: jwt, path: "/", maxAge: SESSION_DURATION, httpOnly: false, secure: true, sameSite: "Lax", }); return new Response(null, { headers, status: 302, }); }, });