fix some issue s with cors and discord
This commit is contained in:
parent
81749d24d3
commit
bf6fed2684
4 changed files with 67 additions and 37 deletions
|
@ -1,4 +1,5 @@
|
||||||
import { discordConfig } from "@config";
|
import { discordConfig } from "@config";
|
||||||
|
import { withCors } from "@lib/cors";
|
||||||
import { randomUUIDv7, redis } from "bun";
|
import { randomUUIDv7, redis } from "bun";
|
||||||
|
|
||||||
export class DiscordAuth {
|
export class DiscordAuth {
|
||||||
|
@ -6,23 +7,27 @@ export class DiscordAuth {
|
||||||
#clientSecret = discordConfig.clientSecret;
|
#clientSecret = discordConfig.clientSecret;
|
||||||
#redirectUri = discordConfig.redirectUri;
|
#redirectUri = discordConfig.redirectUri;
|
||||||
|
|
||||||
startOAuthRedirect(): Response {
|
startOAuthRedirect(req: Request): Response {
|
||||||
const query = new URLSearchParams({
|
const query = new URLSearchParams({
|
||||||
client_id: this.#clientId,
|
client_id: this.#clientId,
|
||||||
redirect_uri: this.#redirectUri,
|
redirect_uri: this.#redirectUri,
|
||||||
response_type: "code",
|
response_type: "code",
|
||||||
scope: "identify",
|
scope: "identify",
|
||||||
});
|
});
|
||||||
return Response.redirect(
|
return withCors(
|
||||||
`https://discord.com/oauth2/authorize?${query}`,
|
Response.redirect(`https://discord.com/oauth2/authorize?${query}`, 302),
|
||||||
302,
|
req,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCallback(req: Request): Promise<Response> {
|
async handleCallback(req: Request): Promise<Response> {
|
||||||
const url = new URL(req.url);
|
const url = new URL(req.url);
|
||||||
const code = url.searchParams.get("code");
|
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", {
|
const tokenRes = await fetch("https://discord.com/api/oauth2/token", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
@ -38,7 +43,10 @@ export class DiscordAuth {
|
||||||
|
|
||||||
const tokenData: { access_token?: string } = await tokenRes.json();
|
const tokenData: { access_token?: string } = await tokenRes.json();
|
||||||
if (!tokenData.access_token)
|
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", {
|
const userRes = await fetch("https://discord.com/api/users/@me", {
|
||||||
headers: { Authorization: `Bearer ${tokenData.access_token}` },
|
headers: { Authorization: `Bearer ${tokenData.access_token}` },
|
||||||
|
@ -48,14 +56,17 @@ export class DiscordAuth {
|
||||||
const sessionId = randomUUIDv7();
|
const sessionId = randomUUIDv7();
|
||||||
await redis.set(sessionId, JSON.stringify(user), "EX", 3600);
|
await redis.set(sessionId, JSON.stringify(user), "EX", 3600);
|
||||||
|
|
||||||
return Response.json(
|
return withCors(
|
||||||
{ message: "Authenticated" },
|
Response.json(
|
||||||
{
|
{ message: "Authenticated" },
|
||||||
headers: {
|
{
|
||||||
"Set-Cookie": `session=${sessionId}; HttpOnly; Path=/; Max-Age=3600`,
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Set-Cookie": `session=${sessionId}; HttpOnly; Path=/; Max-Age=3600; SameSite=None; Secure`,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
),
|
||||||
|
req,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
42
src/index.ts
42
src/index.ts
|
@ -1,6 +1,7 @@
|
||||||
import { DiscordAuth } from "@/discord";
|
import { DiscordAuth } from "@/discord";
|
||||||
import { echo } from "@atums/echo";
|
import { echo } from "@atums/echo";
|
||||||
import { environment, verifyRequiredVariables } from "@config";
|
import { environment, verifyRequiredVariables } from "@config";
|
||||||
|
import { withCors } from "@lib/cors";
|
||||||
import { serve, sql } from "bun";
|
import { serve, sql } from "bun";
|
||||||
|
|
||||||
verifyRequiredVariables();
|
verifyRequiredVariables();
|
||||||
|
@ -39,42 +40,27 @@ echo.info(`Listening on http://${environment.host}:${environment.port}`);
|
||||||
|
|
||||||
const auth = new DiscordAuth();
|
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({
|
serve({
|
||||||
port: environment.port,
|
port: environment.port,
|
||||||
fetch: async (req) => {
|
fetch: async (req) => {
|
||||||
if (req.method === "OPTIONS") {
|
if (req.method === "OPTIONS") {
|
||||||
|
const origin = req.headers.get("origin") ?? "";
|
||||||
return new Response(null, {
|
return new Response(null, {
|
||||||
status: 204,
|
status: 204,
|
||||||
headers: {
|
headers: {
|
||||||
"Access-Control-Allow-Origin": "*",
|
"Access-Control-Allow-Origin": origin,
|
||||||
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
||||||
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
||||||
"Access-Control-Max-Age": "86400", // 24 hours
|
|
||||||
"Access-Control-Allow-Credentials": "true",
|
"Access-Control-Allow-Credentials": "true",
|
||||||
|
"Access-Control-Max-Age": "86400",
|
||||||
|
Vary: "Origin",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = new URL(req.url);
|
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")
|
if (url.pathname === "/auth/discord/callback")
|
||||||
return auth.handleCallback(req);
|
return auth.handleCallback(req);
|
||||||
|
|
||||||
|
@ -83,6 +69,7 @@ serve({
|
||||||
if (!user)
|
if (!user)
|
||||||
return withCors(
|
return withCors(
|
||||||
Response.json({ error: "Unauthorized" }, { status: 401 }),
|
Response.json({ error: "Unauthorized" }, { status: 401 }),
|
||||||
|
req,
|
||||||
);
|
);
|
||||||
|
|
||||||
const tz = url.searchParams.get("timezone");
|
const tz = url.searchParams.get("timezone");
|
||||||
|
@ -92,6 +79,7 @@ serve({
|
||||||
{ error: "Timezone parameter is required" },
|
{ error: "Timezone parameter is required" },
|
||||||
{ status: 400 },
|
{ status: 400 },
|
||||||
),
|
),
|
||||||
|
req,
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -99,6 +87,7 @@ serve({
|
||||||
} catch {
|
} catch {
|
||||||
return withCors(
|
return withCors(
|
||||||
Response.json({ error: "Invalid timezone" }, { status: 400 }),
|
Response.json({ error: "Invalid timezone" }, { status: 400 }),
|
||||||
|
req,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +98,7 @@ serve({
|
||||||
SET username = EXCLUDED.username, timezone = EXCLUDED.timezone
|
SET username = EXCLUDED.username, timezone = EXCLUDED.timezone
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return withCors(Response.json({ success: true }));
|
return withCors(Response.json({ success: true }), req);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.pathname === "/get") {
|
if (url.pathname === "/get") {
|
||||||
|
@ -117,6 +106,7 @@ serve({
|
||||||
if (!id)
|
if (!id)
|
||||||
return withCors(
|
return withCors(
|
||||||
Response.json({ error: "Missing user ID" }, { status: 400 }),
|
Response.json({ error: "Missing user ID" }, { status: 400 }),
|
||||||
|
req,
|
||||||
);
|
);
|
||||||
|
|
||||||
const rows = await sql`
|
const rows = await sql`
|
||||||
|
@ -126,6 +116,7 @@ serve({
|
||||||
if (rows.length === 0) {
|
if (rows.length === 0) {
|
||||||
return withCors(
|
return withCors(
|
||||||
Response.json({ error: "User not found" }, { status: 404 }),
|
Response.json({ error: "User not found" }, { status: 404 }),
|
||||||
|
req,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,6 +125,7 @@ serve({
|
||||||
user: { id, username: rows[0].username },
|
user: { id, username: rows[0].username },
|
||||||
timezone: rows[0].timezone,
|
timezone: rows[0].timezone,
|
||||||
}),
|
}),
|
||||||
|
req,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,10 +134,14 @@ serve({
|
||||||
if (!user)
|
if (!user)
|
||||||
return withCors(
|
return withCors(
|
||||||
Response.json({ error: "Unauthorized" }, { status: 401 }),
|
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
22
src/lib/cors.ts
Normal 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 };
|
|
@ -3,7 +3,8 @@
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["src/*"],
|
"@/*": ["src/*"],
|
||||||
"@config": ["config/index.ts"]
|
"@config": ["config/index.ts"],
|
||||||
|
"@lib/*": ["src/lib/*"]
|
||||||
},
|
},
|
||||||
"typeRoots": ["types", "./node_modules/@types"],
|
"typeRoots": ["types", "./node_modules/@types"],
|
||||||
"lib": ["ESNext", "DOM"],
|
"lib": ["ESNext", "DOM"],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue