import type { Error, ProvisionalTokenResponse } from "@purrkit/types"; import config, { IS_PROD } from "@stealth-developers/config"; import db, { getActor, 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"; const SCOPES = { regular: ["identify", "guilds"], widgets: ["sdk.social_layer", "sdk.social_layer_presence", "openid"], }; export const authRoutes = new Elysia().group("/auth", (app) => app .get( "/login", ({ query: { actorId }, redirect }) => { const isWidgetFlow = !!actorId; const scopes = isWidgetFlow ? SCOPES.widgets : SCOPES.regular; let redirectUri = `https://discord.com/oauth2/authorize?client_id=${CLIENT_ID}&response_type=code&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&scope=${encodeURIComponent(scopes.join(" "))}`; if (isWidgetFlow) redirectUri += `&state=${encodeURIComponent(actorId)}`; return redirect(redirectUri, 302); }, { tags: ["auth"], query: t.Object({ actorId: t.Optional(t.String()), }), }, ) .get( "/callback", async ({ query: { code, state }, 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, }); if (state) { const actor = await getActor(Number(state)); if (!actor) throw new Error("Actor not found"); if (!actor.robloxId) throw new Error("Actor does not have a linked Roblox account"); const botToken = config.discord.token; if (!botToken) throw new Error("config.discord.token is not defined"); const provisionResponse = await fetch( "https://discord.com/api/v10/partner-sdk/token/bot", { method: "POST", headers: { Authorization: `Bot ${botToken}`, "Content-Type": "application/json", }, body: JSON.stringify({ external_user_id: actor.robloxId, }), }, ); if (!provisionResponse.ok) { const responseText = await provisionResponse.text(); let isNonProvisional = false; try { const provisionErrorData = JSON.parse(responseText) as { code?: number; message?: string; }; if (provisionErrorData.code === 530010) { isNonProvisional = true; } } catch {} if (!isNonProvisional) throw new Error(`Provisional account creation failed: ${responseText}`); } else { const provisionData = (await provisionResponse.json()) as { access_token: string }; params.append("external_auth_type", "DISCORD_BOT_ISSUED_ACCESS_TOKEN"); params.append("external_auth_token", provisionData.access_token); } } 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(), state: t.Optional(t.String()), }), }, ), );