app/routes/api.auth.github.connect-pat.tsx (95 lines of code) (raw):
import { ActionFunction, json } from "@remix-run/node";
import { parseCookies } from "~/lib/server/auth";
import serverConfig from "~/lib/server/config";
export const action: ActionFunction = async ({ request }) => {
if (request.method !== "POST") {
return json({ error: "Method not allowed" }, { status: 405 });
}
try {
const { token } = await request.json();
// Validate the token first
const userResponse = await fetch("https://api.github.com/user", {
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
});
if (!userResponse.ok) {
return json({ error: "Invalid token" }, { status: 400 });
}
const userData = await userResponse.json();
// Get user's primary email
let userEmail = userData.email;
if (!userEmail) {
try {
const emailResponse = await fetch(
"https://api.github.com/user/emails",
{
headers: {
Authorization: `Bearer ${token}`,
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
}
);
if (emailResponse.ok) {
const emails = await emailResponse.json();
const primaryEmail = emails.find((email: any) => email.primary);
userEmail = primaryEmail?.email || emails[0]?.email;
}
} catch (emailError) {
console.warn("Could not fetch user email:", emailError);
}
}
// Get existing session cookie
const cookieHeader = request.headers.get("Cookie");
const cookies = parseCookies(cookieHeader || "");
let existingCookieData: any = {};
const existingAuthCookie = cookies[serverConfig.COOKIE_NAME];
if (existingAuthCookie) {
try {
existingCookieData = JSON.parse(atob(existingAuthCookie));
} catch (error) {
console.warn("Could not parse existing auth cookie:", error);
}
}
// Merge existing credentials with GitHub PAT
let mergedCredentials: any = {};
if (existingCookieData.enc) {
try {
mergedCredentials = JSON.parse(atob(existingCookieData.enc));
} catch (error) {
console.warn("Could not parse existing credentials:", error);
}
}
mergedCredentials.gh = token;
// Create GitHub user info
const githubUserInfo = {
username: userData.login,
name: userData.name || userData.login,
email: userEmail,
avatar_url: userData.avatar_url,
};
// Update session cookie
const expiresAt = new Date();
expiresAt.setTime(expiresAt.getTime() + 7 * 24 * 60 * 60 * 1000); // 7 days
const cookieData = {
hasOpenAI: existingCookieData.hasOpenAI || false,
hasHuggingFace: existingCookieData.hasHuggingFace || false,
hasGitHub: true,
expiresAt: existingCookieData.expiresAt || expiresAt.toISOString(),
hfUserInfo: existingCookieData.hfUserInfo,
githubUserInfo: btoa(JSON.stringify(githubUserInfo)),
isOAuth2: existingCookieData.isOAuth2,
isGitHubPAT: true, // Flag to indicate PAT-based authentication
enc: btoa(JSON.stringify(mergedCredentials)),
};
const cookieValue = btoa(JSON.stringify(cookieData));
return json(
{ success: true, user: githubUserInfo },
{
headers: {
"Set-Cookie": `${serverConfig.COOKIE_NAME}=${cookieValue}; Max-Age=${7 * 24 * 60 * 60}; Path=/; HttpOnly=false; SameSite=Lax`,
},
}
);
} catch (error) {
console.error("PAT connection error:", error);
return json({ error: "Failed to connect with token" }, { status: 500 });
}
};