auth.jsā¢5.41 kB
import { getUserByAccessToken, upsertOAuthUser } from './database.js';
import dotenv from 'dotenv';
dotenv.config();
/**
* Middleware to authenticate requests using Bearer token (OAuth)
*/
export async function authenticateRequest(req, res, next) {
try {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({
error: 'Authorization header required',
message: 'Please provide a valid OAuth access token in the Authorization header',
});
}
// Extract Bearer token
const parts = authHeader.split(' ');
if (parts.length !== 2 || parts[0] !== 'Bearer') {
return res.status(401).json({
error: 'Invalid authorization header format',
message: 'Authorization header must be in format: Bearer <token>',
});
}
const accessToken = parts[1];
// Verify token and get user from database
const user = getUserByAccessToken(accessToken);
if (!user) {
return res.status(401).json({
error: 'Invalid access token',
message: 'The provided access token is not valid or has expired',
});
}
// Check if token is expired
if (user.token_expires_at && new Date(user.token_expires_at) < new Date()) {
return res.status(401).json({
error: 'Token expired',
message: 'The access token has expired. Please re-authenticate.',
});
}
// Attach user to request object
req.user = {
id: user.id,
github_id: user.github_id,
username: user.github_username,
email: user.github_email,
name: user.name,
avatar_url: user.avatar_url,
};
next();
} catch (error) {
console.error('[AUTH] Error during authentication:', error);
return res.status(500).json({
error: 'Authentication error',
message: 'An error occurred during authentication',
});
}
}
/**
* GitHub OAuth callback handler
* Exchanges authorization code for access token and creates/updates user
*/
export async function handleGitHubCallback(code) {
try {
const clientId = process.env.GITHUB_CLIENT_ID;
const clientSecret = process.env.GITHUB_CLIENT_SECRET;
const redirectUri = process.env.GITHUB_REDIRECT_URI || 'http://localhost:3000/oauth/callback';
if (!clientId || !clientSecret) {
throw new Error('GitHub OAuth credentials not configured');
}
// Exchange code for access token
const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
client_id: clientId,
client_secret: clientSecret,
code,
redirect_uri: redirectUri,
}),
});
const tokenData = await tokenResponse.json();
if (tokenData.error) {
throw new Error(`GitHub OAuth error: ${tokenData.error_description || tokenData.error}`);
}
const accessToken = tokenData.access_token;
const refreshToken = tokenData.refresh_token;
const expiresIn = tokenData.expires_in;
// Get user information from GitHub
const userResponse = await fetch('https://api.github.com/user', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Accept': 'application/vnd.github.v3+json',
},
});
const githubUser = await userResponse.json();
if (!userResponse.ok) {
throw new Error(`Failed to fetch GitHub user: ${githubUser.message}`);
}
// Get user email if not public
let email = githubUser.email;
if (!email) {
const emailResponse = await fetch('https://api.github.com/user/emails', {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Accept': 'application/vnd.github.v3+json',
},
});
const emails = await emailResponse.json();
const primaryEmail = emails.find(e => e.primary) || emails[0];
email = primaryEmail?.email;
}
// Calculate token expiration
const tokenExpiresAt = expiresIn
? new Date(Date.now() + expiresIn * 1000).toISOString()
: null;
// Create or update user in database
const user = upsertOAuthUser(githubUser.id.toString(), {
username: githubUser.login,
email,
name: githubUser.name || githubUser.login,
avatar_url: githubUser.avatar_url,
access_token: accessToken,
refresh_token: refreshToken,
token_expires_at: tokenExpiresAt,
});
return {
success: true,
user: {
id: user.id,
github_id: user.github_id,
username: user.github_username,
email: user.github_email,
name: user.name,
avatar_url: user.avatar_url,
},
access_token: accessToken,
};
} catch (error) {
console.error('[AUTH] GitHub OAuth error:', error);
throw error;
}
}
/**
* Generate GitHub OAuth authorization URL
*/
export function getGitHubAuthUrl(state) {
const clientId = process.env.GITHUB_CLIENT_ID;
const redirectUri = process.env.GITHUB_REDIRECT_URI || 'http://localhost:3000/oauth/callback';
const scope = 'read:user user:email';
const params = new URLSearchParams({
client_id: clientId,
redirect_uri: redirectUri,
scope,
state,
});
return `https://github.com/login/oauth/authorize?${params.toString()}`;
}