VIBN Frontend for Coolify deployment
This commit is contained in:
177
lib/github/oauth.ts
Normal file
177
lib/github/oauth.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
/**
|
||||
* GitHub OAuth integration for VIBN
|
||||
* Allows users to connect their GitHub account for repo analysis and tracking
|
||||
*/
|
||||
|
||||
export interface GitHubUser {
|
||||
id: number;
|
||||
login: string;
|
||||
name: string | null;
|
||||
email: string | null;
|
||||
avatar_url: string;
|
||||
}
|
||||
|
||||
export interface GitHubRepo {
|
||||
id: number;
|
||||
name: string;
|
||||
full_name: string;
|
||||
description: string | null;
|
||||
html_url: string;
|
||||
language: string | null;
|
||||
default_branch: string;
|
||||
private: boolean;
|
||||
topics: string[];
|
||||
}
|
||||
|
||||
export interface GitHubConnection {
|
||||
userId: string;
|
||||
githubUserId: number;
|
||||
githubUsername: string;
|
||||
accessToken: string; // Encrypted
|
||||
refreshToken?: string; // Encrypted
|
||||
tokenExpiresAt?: Date;
|
||||
scopes: string[];
|
||||
connectedAt: Date;
|
||||
lastSyncedAt?: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates GitHub OAuth flow
|
||||
* Redirects user to GitHub authorization page
|
||||
*/
|
||||
export function initiateGitHubOAuth(redirectUri: string) {
|
||||
const clientId = process.env.NEXT_PUBLIC_GITHUB_CLIENT_ID;
|
||||
|
||||
if (!clientId) {
|
||||
throw new Error('GitHub OAuth not configured');
|
||||
}
|
||||
|
||||
// Scopes we need:
|
||||
// - repo: Access repositories (read commits, PRs, issues)
|
||||
// - read:user: Get user profile
|
||||
const scopes = ['repo', 'read:user'];
|
||||
|
||||
// Generate state for CSRF protection
|
||||
const state = generateRandomString(32);
|
||||
sessionStorage.setItem('github_oauth_state', state);
|
||||
|
||||
const params = new URLSearchParams({
|
||||
client_id: clientId,
|
||||
redirect_uri: redirectUri,
|
||||
scope: scopes.join(' '),
|
||||
state,
|
||||
});
|
||||
|
||||
window.location.href = `https://github.com/login/oauth/authorize?${params}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchanges authorization code for access token
|
||||
*/
|
||||
export async function exchangeCodeForToken(code: string): Promise<{
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
scope: string;
|
||||
}> {
|
||||
const response = await fetch('/api/github/oauth/token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ code }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to exchange code for token');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches GitHub user profile
|
||||
*/
|
||||
export async function getGitHubUser(accessToken: string): Promise<GitHubUser> {
|
||||
const response = await fetch('https://api.github.com/user', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch GitHub user');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches user's repositories
|
||||
*/
|
||||
export async function getGitHubRepos(
|
||||
accessToken: string,
|
||||
options?: {
|
||||
sort?: 'created' | 'updated' | 'pushed' | 'full_name';
|
||||
direction?: 'asc' | 'desc';
|
||||
per_page?: number;
|
||||
}
|
||||
): Promise<GitHubRepo[]> {
|
||||
const params = new URLSearchParams({
|
||||
sort: options?.sort || 'updated',
|
||||
direction: options?.direction || 'desc',
|
||||
per_page: String(options?.per_page || 100),
|
||||
});
|
||||
|
||||
const response = await fetch(`https://api.github.com/user/repos?${params}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch repositories');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a specific repository
|
||||
*/
|
||||
export async function getGitHubRepo(
|
||||
accessToken: string,
|
||||
owner: string,
|
||||
repo: string
|
||||
): Promise<GitHubRepo> {
|
||||
const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/vnd.github.v3+json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch repository');
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility: Generate random string for state parameter
|
||||
*/
|
||||
function generateRandomString(length: number): string {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
const randomValues = new Uint8Array(length);
|
||||
crypto.getRandomValues(randomValues);
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars[randomValues[i] % chars.length];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user