import GetPermissions from "../../permissions.js"; import tokenPrefixes from "../../../data/token_prefixes.json"; async function generateTokenHash(token: string): Promise { const hash = await crypto.subtle.digest( "SHA-512", new TextEncoder().encode(token), ); return btoa(String.fromCharCode(...new Uint8Array(hash))) .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=/g, ""); } function response(body: string, status: number) { return new Response(body, { headers: { "content-type": "application/json", }, status, }); } export async function onRequestDelete(context: RequestContext) { const cookies = context.request.headers.get("cookie")?.split("; "); if (!cookies) return response('{"error":"Not logged in"}', 401); for (const cookie of cookies) { const [name, value] = cookie.split("="); if (name !== "_s") continue; await context.env.DATA.delete(`auth_${await generateTokenHash(value)}`); return new Response(null, { headers: { "clear-site-data": '"cookies"', }, status: 204, }); } } export async function onRequestGet(context: RequestContext) { const { host, protocol, searchParams } = new URL(context.request.url); const code = searchParams.get("code"); const state = searchParams.get("state"); if (!code) return response('{"error":"Missing code"}', 400); if (!state) return response('{"error":"Missing state"}', 400); const stateRedirect = await context.env.DATA.get(`state_${state}`); if (!stateRedirect) return response('{"error":"Invalid state"}', 400); const tokenReq = await fetch("https://discord.com/api/oauth2/token", { body: new URLSearchParams({ code, grant_type: "authorization_code", redirect_uri: `${protocol}//${host}/api/auth/session`, }).toString(), headers: { authorization: `Basic ${btoa( context.env.DISCORD_ID + ":" + context.env.DISCORD_SECRET, )}`, "content-type": "application/x-www-form-urlencoded", }, method: "POST", }); if (!tokenReq.ok) { console.log(await tokenReq.text()); return response('{"error":"Failed to redeem code"}', 500); } const tokenData: { access_token: string; expires_in: number; refresh_token: string; scope: string; token_type: string; } = await tokenReq.json(); if (tokenData.scope.search("guilds.members.read") === -1) return response('{"error":"Do not touch the scopes!"}', 400); let userData: { [k: string]: any } = { ...tokenData, refresh_at: Date.now() + tokenData.expires_in * 1000 - 86400000, }; const userReq = await fetch("https://discord.com/api/v10/users/@me", { headers: { authorization: `Bearer ${tokenData.access_token}`, }, }); if (!userReq.ok) { console.log(await userReq.text()); return response('{"error":"Failed to retrieve user"}', 500); } const apiUser: { [k: string]: any } = await userReq.json(); userData = { ...userData, ...apiUser, }; const serverMemberReq = await fetch( "https://discord.com/api/v10/users/@me/guilds/242263977986359297/member", { headers: { authorization: `Bearer ${tokenData.access_token}`, }, }, ); const memberData: { [k: string]: any } = await serverMemberReq.json(); if (serverMemberReq.ok) { userData.permissions = await GetPermissions(userData.id, memberData.roles); userData.roles = memberData.roles; } else { userData.permissions = await GetPermissions(userData.id); } const selectedTokenStart = tokenPrefixes[Math.round(Math.random() * (tokenPrefixes.length - 1))] + "_"; const authToken = selectedTokenStart + `${crypto.randomUUID()}${crypto.randomUUID()}${crypto.randomUUID()}${crypto.randomUUID()}`.replaceAll( "-", "", ); const tokenHash = await generateTokenHash(authToken); await context.env.DATA.put(`auth_${tokenHash}`, JSON.stringify(userData), { expirationTtl: tokenData.expires_in, }); return new Response(null, { headers: { location: stateRedirect, "set-cookie": `_s=${authToken}; HttpOnly; Max-Age=${tokenData.expires_in}; Path=/; SameSite=Lax; Secure`, }, status: 302, }); }