fix some issue s with cors and discord

This commit is contained in:
creations 2025-05-27 19:11:19 -04:00
parent 81749d24d3
commit bf6fed2684
Signed by: creations
GPG key ID: 8F553AA4320FC711
4 changed files with 67 additions and 37 deletions

View file

@ -1,4 +1,5 @@
import { discordConfig } from "@config";
import { withCors } from "@lib/cors";
import { randomUUIDv7, redis } from "bun";
export class DiscordAuth {
@ -6,23 +7,27 @@ export class DiscordAuth {
#clientSecret = discordConfig.clientSecret;
#redirectUri = discordConfig.redirectUri;
startOAuthRedirect(): Response {
startOAuthRedirect(req: Request): Response {
const query = new URLSearchParams({
client_id: this.#clientId,
redirect_uri: this.#redirectUri,
response_type: "code",
scope: "identify",
});
return Response.redirect(
`https://discord.com/oauth2/authorize?${query}`,
302,
return withCors(
Response.redirect(`https://discord.com/oauth2/authorize?${query}`, 302),
req,
);
}
async handleCallback(req: Request): Promise<Response> {
const url = new URL(req.url);
const code = url.searchParams.get("code");
if (!code) return Response.json({ error: "Missing code" }, { status: 400 });
if (!code)
return withCors(
Response.json({ error: "Missing code" }, { status: 400 }),
req,
);
const tokenRes = await fetch("https://discord.com/api/oauth2/token", {
method: "POST",
@ -38,7 +43,10 @@ export class DiscordAuth {
const tokenData: { access_token?: string } = await tokenRes.json();
if (!tokenData.access_token)
return Response.json({ error: "Unauthorized" }, { status: 401 });
return withCors(
Response.json({ error: "Unauthorized" }, { status: 401 }),
req,
);
const userRes = await fetch("https://discord.com/api/users/@me", {
headers: { Authorization: `Bearer ${tokenData.access_token}` },
@ -48,14 +56,17 @@ export class DiscordAuth {
const sessionId = randomUUIDv7();
await redis.set(sessionId, JSON.stringify(user), "EX", 3600);
return Response.json(
{ message: "Authenticated" },
{
headers: {
"Set-Cookie": `session=${sessionId}; HttpOnly; Path=/; Max-Age=3600`,
"Content-Type": "application/json",
return withCors(
Response.json(
{ message: "Authenticated" },
{
headers: {
"Set-Cookie": `session=${sessionId}; HttpOnly; Path=/; Max-Age=3600; SameSite=None; Secure`,
"Content-Type": "application/json",
},
},
},
),
req,
);
}

View file

@ -1,6 +1,7 @@
import { DiscordAuth } from "@/discord";
import { echo } from "@atums/echo";
import { environment, verifyRequiredVariables } from "@config";
import { withCors } from "@lib/cors";
import { serve, sql } from "bun";
verifyRequiredVariables();
@ -39,42 +40,27 @@ echo.info(`Listening on http://${environment.host}:${environment.port}`);
const auth = new DiscordAuth();
function withCors(res: Response): Response {
const headers = new Headers(res.headers);
headers.set("Access-Control-Allow-Origin", "*");
headers.set(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS",
);
headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
headers.set("Access-Control-Allow-Credentials", "true");
headers.set("Access-Control-Max-Age", "86400");
return new Response(res.body, {
status: res.status,
statusText: res.statusText,
headers,
});
}
serve({
port: environment.port,
fetch: async (req) => {
if (req.method === "OPTIONS") {
const origin = req.headers.get("origin") ?? "";
return new Response(null, {
status: 204,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Origin": origin,
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Content-Type, Authorization",
"Access-Control-Max-Age": "86400", // 24 hours
"Access-Control-Allow-Credentials": "true",
"Access-Control-Max-Age": "86400",
Vary: "Origin",
},
});
}
const url = new URL(req.url);
if (url.pathname === "/auth/discord") return auth.startOAuthRedirect();
if (url.pathname === "/auth/discord") return auth.startOAuthRedirect(req);
if (url.pathname === "/auth/discord/callback")
return auth.handleCallback(req);
@ -83,6 +69,7 @@ serve({
if (!user)
return withCors(
Response.json({ error: "Unauthorized" }, { status: 401 }),
req,
);
const tz = url.searchParams.get("timezone");
@ -92,6 +79,7 @@ serve({
{ error: "Timezone parameter is required" },
{ status: 400 },
),
req,
);
try {
@ -99,6 +87,7 @@ serve({
} catch {
return withCors(
Response.json({ error: "Invalid timezone" }, { status: 400 }),
req,
);
}
@ -109,7 +98,7 @@ serve({
SET username = EXCLUDED.username, timezone = EXCLUDED.timezone
`;
return withCors(Response.json({ success: true }));
return withCors(Response.json({ success: true }), req);
}
if (url.pathname === "/get") {
@ -117,6 +106,7 @@ serve({
if (!id)
return withCors(
Response.json({ error: "Missing user ID" }, { status: 400 }),
req,
);
const rows = await sql`
@ -126,6 +116,7 @@ serve({
if (rows.length === 0) {
return withCors(
Response.json({ error: "User not found" }, { status: 404 }),
req,
);
}
@ -134,6 +125,7 @@ serve({
user: { id, username: rows[0].username },
timezone: rows[0].timezone,
}),
req,
);
}
@ -142,10 +134,14 @@ serve({
if (!user)
return withCors(
Response.json({ error: "Unauthorized" }, { status: 401 }),
req,
);
return withCors(Response.json(user));
return withCors(Response.json(user), req);
}
return withCors(Response.json({ error: "Not Found" }, { status: 404 }));
return withCors(
Response.json({ error: "Not Found" }, { status: 404 }),
req,
);
},
});

22
src/lib/cors.ts Normal file
View file

@ -0,0 +1,22 @@
function withCors(response: Response, req: Request): Response {
const origin = req.headers.get("origin");
const headers = new Headers(response.headers);
if (origin && isAllowedOrigin(origin)) {
headers.set("Access-Control-Allow-Origin", origin);
headers.set("Access-Control-Allow-Credentials", "true");
headers.set("Vary", "Origin");
}
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers,
});
}
function isAllowedOrigin(origin: string): boolean {
return origin.endsWith(".discord.com") || origin === "https://discord.com";
}
export { withCors };

View file

@ -3,7 +3,8 @@
"baseUrl": "./",
"paths": {
"@/*": ["src/*"],
"@config": ["config/index.ts"]
"@config": ["config/index.ts"],
"@lib/*": ["src/lib/*"]
},
"typeRoots": ["types", "./node_modules/@types"],
"lib": ["ESNext", "DOM"],