all repos — stealth-developers @ fa9b0a4f697756561c2736f8bce24a278c9aef53

apps/api/src/routes/auth.ts (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
import type { Error, ProvisionalTokenResponse } from "@purrkit/types";

import { IS_PROD } from "@stealth-developers/config";
import db, { sessions, upsertUser } from "@stealth-developers/db";
import Elysia, { t } from "elysia";

import { createClient } from "../discord";
import { CLIENT_ID, CLIENT_SECRET, FRONTEND_URL, REDIRECT_URI } from "../lib/constants";

export const authRoutes = new Elysia().group("/auth", (app) =>
	app
		.get(
			"/login",
			({ redirect }) => {
				const redirectUri = `https://discord.com/oauth2/authorize?client_id=${CLIENT_ID}&response_type=code&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&scope=identify+guilds`;
				return redirect(redirectUri, 302);
			},
			{
				tags: ["auth"],
			},
		)
		.get(
			"/callback",
			async ({ query: { code }, cookie: { session_id }, redirect }) => {
				if (!session_id) throw new Error("No session ID");

				const params = new URLSearchParams({
					code,
					client_id: CLIENT_ID,
					client_secret: CLIENT_SECRET,
					grant_type: "authorization_code",
					redirect_uri: REDIRECT_URI,
				});

				const tokenResponse = await fetch("https://discord.com/api/oauth2/token", {
					method: "POST",
					body: params,
					headers: { "Content-Type": "application/x-www-form-urlencoded" },
				});
				const tokenData = (await tokenResponse.json()) as Error | ProvisionalTokenResponse;
				if ("code" in tokenData) throw new Error(tokenData.message);

				const client = createClient(`${tokenData.token_type} ${tokenData.access_token}`);
				const userRes = await client.users.me.get();
				if (!userRes.ok) throw new Error(userRes.data.message);
				const userData = userRes.data;

				const upsertedUser = await upsertUser({
					avatar: userData.avatar,
					username: userData.username,
					displayName: userData.global_name,
					id: userData.id,
				});
				if (!upsertedUser) throw new Error("Failed to upsert user");

				const sessionId = crypto.randomUUID();
				const expiresAt = new Date().getTime() + tokenData.expires_in * 1_000;

				await db.insert(sessions).values({
					id: sessionId,
					userId: upsertedUser.id,

					accessToken: tokenData.access_token,
					refreshToken: tokenData.refresh_token!,
					scope: tokenData.scope,
					tokenType: tokenData.token_type,

					createdAt: new Date(),
					expiresAt: new Date(expiresAt),
				});

				session_id.set({
					value: sessionId,
					httpOnly: true,
					path: "/",
					secure: IS_PROD,
					sameSite: "lax",
					expires: new Date(expiresAt),
				});

				return redirect(FRONTEND_URL);
			},
			{
				tags: ["auth"],
				query: t.Object({
					code: t.String(),
				}),
			},
		),
);