feat: add authentication
This commit is contained in:
75
routes/api/auth/callback.ts
Normal file
75
routes/api/auth/callback.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import { Handlers } from "$fresh/server.ts";
|
||||
import { create, getNumericDate } from "https://deno.land/x/djwt@v2.2/mod.ts";
|
||||
import { oauth2Client } from "@lib/auth.ts";
|
||||
import {
|
||||
getCookies,
|
||||
setCookie,
|
||||
} from "https://deno.land/std@0.197.0/http/cookie.ts";
|
||||
import { codeChallengeMap } from "./login.ts";
|
||||
import { GITEA_SERVER, JWT_SECRET, SESSION_DURATION } from "@lib/env.ts";
|
||||
import { userDB } from "@lib/db.ts";
|
||||
import { GiteaOauthUser } from "@lib/types.ts";
|
||||
import { BadRequestError } from "@lib/errors.ts";
|
||||
|
||||
export const handler: Handlers = {
|
||||
async GET(request, ctx) {
|
||||
if (!JWT_SECRET) {
|
||||
throw new BadRequestError();
|
||||
}
|
||||
|
||||
// Exchange the authorization code for an access token
|
||||
const cookies = getCookies(request.headers);
|
||||
|
||||
const codeVerifier = codeChallengeMap.get(cookies["code_challenge"]);
|
||||
|
||||
const tokens = await oauth2Client.code.getToken(request.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;
|
||||
|
||||
const allUsers = await userDB.findAll();
|
||||
let user = allUsers.find((u) => u.name === oauthUser.name);
|
||||
|
||||
if (!user) {
|
||||
user = await userDB.create({
|
||||
createdAt: new Date(),
|
||||
email: oauthUser.email,
|
||||
name: oauthUser.name,
|
||||
});
|
||||
}
|
||||
|
||||
const jwt = await create({ alg: "HS512", type: "JWT" }, {
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
exp: getNumericDate(SESSION_DURATION),
|
||||
}, JWT_SECRET);
|
||||
|
||||
const headers = new Headers({
|
||||
location: "/",
|
||||
});
|
||||
|
||||
setCookie(headers, {
|
||||
name: "session_cookie",
|
||||
value: jwt,
|
||||
path: "/",
|
||||
maxAge: SESSION_DURATION,
|
||||
httpOnly: false,
|
||||
secure: true,
|
||||
sameSite: "Lax",
|
||||
});
|
||||
|
||||
return new Response(null, {
|
||||
headers,
|
||||
status: 302,
|
||||
});
|
||||
},
|
||||
};
|
29
routes/api/auth/login.ts
Normal file
29
routes/api/auth/login.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { Handlers } from "$fresh/server.ts";
|
||||
import { oauth2Client } from "@lib/auth.ts";
|
||||
import { sha256 } from "@lib/string.ts";
|
||||
import { setCookie } from "https://deno.land/std@0.197.0/http/cookie.ts";
|
||||
|
||||
export const codeChallengeMap = new Map();
|
||||
|
||||
export const handler: Handlers = {
|
||||
async GET() {
|
||||
const { codeVerifier, uri } = await oauth2Client.code.getAuthorizationUri();
|
||||
|
||||
const codeChallenge = uri.searchParams.get("code_challenge");
|
||||
if (!codeChallenge) return new Response();
|
||||
|
||||
codeChallengeMap.set(codeChallenge, codeVerifier);
|
||||
|
||||
const headers = new Headers();
|
||||
setCookie(headers, {
|
||||
name: "code_challenge",
|
||||
value: codeChallenge,
|
||||
});
|
||||
headers.append("location", uri.href);
|
||||
|
||||
return new Response(null, {
|
||||
headers,
|
||||
status: 302,
|
||||
});
|
||||
},
|
||||
};
|
17
routes/api/auth/logout.ts
Normal file
17
routes/api/auth/logout.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { deleteCookie } from "https://deno.land/std@0.197.0/http/cookie.ts";
|
||||
import { Handlers } from "$fresh/server.ts";
|
||||
|
||||
export const handler: Handlers = {
|
||||
GET() {
|
||||
const headers = new Headers();
|
||||
headers.append("location", "/");
|
||||
deleteCookie(headers, "session_cookie", {
|
||||
path: "/",
|
||||
});
|
||||
|
||||
return new Response(null, {
|
||||
headers,
|
||||
status: 302,
|
||||
});
|
||||
},
|
||||
};
|
Reference in New Issue
Block a user