import GetPermissions from "../../permissions.js"; 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: { "set-cookie": "_s=; Max-Age=0", }, 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 = GetPermissions(userData.id, memberData.roles); userData.roles = memberData.roles; } else { userData.permissions = GetPermissions(userData.id); } const tokenPrefixes = [ "ABOVE-THE-SKY", "BANDITO", "BE-CONCERNED", "CAR-RADIO", "CHEESE", "CHLORINE", "CRAZY-EQUALS-GENIUS", "CUBICLES", "DEAD", "DEMOLITION-LOVERS", "DEVIL-DOGS", "DOUBT", "DREADNOUGHT", "DYING-IN-LA", "FAIRLY-LOCAL", "FORMIDABLE", "GATES-OF-GLORY", "GIRLS-GIRLS-BOYS", "GONER", "HEATHENS", "HEAVYDIRTYSOUL", "HELENA", "HYDRA", "I-WRITE-SINS-NOT-TRAGEDIES", "KITCHEN-SINK", "LEVITATE", "LOCAL-GOD", "MAGGIE", "MAMA", "MONTANA", "NERO-FORTE", "NOOB", "NOT-TODAY", "NO-CHANCES", "POLARIZE", "PSYCHO", "ROMANCE", "SAD-CLOWN", "SATURDAY", "SAY-IT-LOUDER", "SEMI-AUTOMATIC", "TEENAGERS", "THUNDERSWORD", "TOKYO-DRIFTING", "TRAPDOOR", "TREES", "UMA-THURMAN", "UNSAINTED", "VERMILION", "VERSAILLES", "VICTORIOUS", "VIVA-LAS-VENGEANCE", "XIX", ]; 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, }); }