mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
82 lines
2.1 KiB
TypeScript
82 lines
2.1 KiB
TypeScript
import { Hono } from "hono";
|
|
import { getOAuthURL, handleOAuthCallback } from "./github-oauth";
|
|
import { removeToken } from "./token-store";
|
|
import type { Env } from "./types";
|
|
|
|
interface PendingState {
|
|
redirectTo: string;
|
|
expiresAt: number;
|
|
}
|
|
|
|
function generateRandomHex(length: number): string {
|
|
const bytes = new Uint8Array(length);
|
|
crypto.getRandomValues(bytes);
|
|
return Array.from(bytes)
|
|
.map((b) => b.toString(16).padStart(2, "0"))
|
|
.join("");
|
|
}
|
|
|
|
export function createOAuthRoutes(): Hono<{ Bindings: Env }> {
|
|
const app = new Hono<{ Bindings: Env }>();
|
|
|
|
app.get("/github", async (c) => {
|
|
const redirectTo = c.req.query("redirect") ?? "/";
|
|
const state = generateRandomHex(16);
|
|
|
|
const pending: PendingState = {
|
|
redirectTo,
|
|
expiresAt: Date.now() + 10 * 60 * 1000,
|
|
};
|
|
await c.env.KV.put(`state:${state}`, JSON.stringify(pending), {
|
|
expirationTtl: 600,
|
|
});
|
|
|
|
const url = getOAuthURL(c.env.GITHUB_CLIENT_ID ?? "", state);
|
|
return c.redirect(url);
|
|
});
|
|
|
|
app.get("/github/callback", async (c) => {
|
|
const code = c.req.query("code");
|
|
const state = c.req.query("state");
|
|
|
|
if (!code || !state) {
|
|
return c.json({ error: "Missing code or state" }, 400);
|
|
}
|
|
|
|
const raw = await c.env.KV.get(`state:${state}`, "json");
|
|
if (!raw) {
|
|
return c.json({ error: "Invalid or expired state" }, 400);
|
|
}
|
|
const pending = raw as PendingState;
|
|
if (Date.now() > pending.expiresAt) {
|
|
await c.env.KV.delete(`state:${state}`);
|
|
return c.json({ error: "Invalid or expired state" }, 400);
|
|
}
|
|
await c.env.KV.delete(`state:${state}`);
|
|
|
|
const result = await handleOAuthCallback(
|
|
c.env.GITHUB_CLIENT_ID ?? "",
|
|
c.env.GITHUB_CLIENT_SECRET ?? "",
|
|
code,
|
|
state,
|
|
c.env.KV,
|
|
);
|
|
|
|
if (!result) {
|
|
return c.json({ error: "OAuth failed" }, 400);
|
|
}
|
|
|
|
return c.json({
|
|
userId: result.userId,
|
|
login: result.login,
|
|
redirectTo: pending.redirectTo,
|
|
});
|
|
});
|
|
|
|
app.delete("/token/:userId", async (c) => {
|
|
await removeToken(c.env.KV, c.req.param("userId"));
|
|
return c.json({ ok: true });
|
|
});
|
|
|
|
return app;
|
|
}
|