This repository has been archived on 2026-06-07. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
master-ai/vibn-frontend/app/api/integrations/github/connect/route.ts

57 lines
1.8 KiB
TypeScript

/**
* GET /api/integrations/github/connect
*
* Kicks off GitHub OAuth. Generates an unguessable `state`, stores it
* in a short-lived (10 min) httpOnly cookie, and 302s to GitHub's
* authorize endpoint.
*
* The callback route validates the cookie matches the `state` GitHub
* echoes back, defending against login-CSRF.
*/
import { NextResponse } from "next/server";
import { randomBytes } from "crypto";
import { authSession } from "@/lib/auth/session-server";
import {
buildAuthorizeUrl, isGithubOauthConfigured,
} from "@/lib/integrations/github";
const STATE_COOKIE = "gh_oauth_state";
const STATE_TTL_S = 10 * 60;
export async function GET(req: Request) {
if (!isGithubOauthConfigured()) {
return NextResponse.json(
{ error: "GitHub OAuth is not configured on this server." },
{ status: 503 },
);
}
const session = await authSession();
if (!session?.user?.email) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}
const url = new URL(req.url);
// Use NEXTAUTH_URL when available — behind a proxy req.url.origin
// resolves to the internal bind address (0.0.0.0) rather than the
// public hostname, which GitHub then rejects as an unregistered URI.
const appOrigin = (process.env.NEXTAUTH_URL ?? url.origin).replace(/\/$/, "");
const callbackUrl = `${appOrigin}/api/integrations/github/callback`;
const returnTo = url.searchParams.get("returnTo") ?? "/";
const state = randomBytes(16).toString("hex");
const statePayload = `${state}:${returnTo}`;
const authorize = buildAuthorizeUrl(state, callbackUrl);
const res = NextResponse.redirect(authorize);
res.cookies.set(STATE_COOKIE, statePayload, {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: url.protocol === "https:",
maxAge: STATE_TTL_S,
});
return res;
}