branch: main
github.ts
2033 bytesRaw
import type { Env } from "./types";
const GITHUB_AUTHORIZE_URL = "https://github.com/login/oauth/authorize";
const GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token";
const GITHUB_USER_URL = "https://api.github.com/user";
export interface GitHubUser {
id: number;
login: string;
name: string | null;
email: string | null;
}
/** Build the GitHub OAuth authorization redirect URL. */
export function githubAuthorizeUrl(
clientId: string,
state: string,
callbackUrl: string,
): string {
const url = new URL(GITHUB_AUTHORIZE_URL);
url.searchParams.set("client_id", clientId);
url.searchParams.set("scope", "read:user,user:email");
url.searchParams.set("state", state);
url.searchParams.set("redirect_uri", callbackUrl);
return url.toString();
}
/** Exchange a GitHub authorization code for an access token. */
export async function exchangeCode(
code: string,
env: Env,
callbackUrl: string,
): Promise<string> {
const resp = await fetch(GITHUB_TOKEN_URL, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({
client_id: env.GITHUB_CLIENT_ID,
client_secret: env.GITHUB_CLIENT_SECRET,
code,
redirect_uri: callbackUrl,
}),
});
const data = (await resp.json()) as {
access_token?: string;
error?: string;
error_description?: string;
};
if (!data.access_token) {
throw new Error(
data.error_description ?? data.error ?? "GitHub token exchange failed",
);
}
return data.access_token;
}
/** Fetch the authenticated GitHub user's profile. */
export async function fetchGitHubUser(githubToken: string): Promise<GitHubUser> {
const resp = await fetch(GITHUB_USER_URL, {
headers: {
Authorization: `Bearer ${githubToken}`,
"User-Agent": "ripgit-auth/1.0",
},
});
if (!resp.ok) {
throw new Error(`GitHub API error: ${resp.status} ${resp.statusText}`);
}
return resp.json() as Promise<GitHubUser>;
}