Skip to content

Commit 4709e2c

Browse files
committed
feat: Implement GitHub authentication flow and UI integration
1 parent fa20c2d commit 4709e2c

File tree

2 files changed

+294
-0
lines changed

2 files changed

+294
-0
lines changed

src/providers/uiProvider.ts

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ export class UiProvider implements vscode.WebviewViewProvider {
5454
case "loadConfig":
5555
this.loadConfiguration();
5656
break;
57+
case "loginWithGitHub":
58+
this.loginWithGitHub();
59+
break;
5760
case "testConnection":
5861
this.testGitHubConnection();
5962
break;
@@ -165,6 +168,26 @@ export class UiProvider implements vscode.WebviewViewProvider {
165168
}
166169
this.logger.info("UiProvider", `Using threat model: ${threatModel}`);
167170

171+
// Check GitHub auth status
172+
try {
173+
const authStatus = await this._githubService.checkGitHubAuth();
174+
175+
// Send auth status to the WebUI
176+
this._view?.webview.postMessage({
177+
command: "authStatus",
178+
isAuthenticated: authStatus.isAuthenticated,
179+
displayName: authStatus.displayName
180+
});
181+
182+
if (authStatus.isAuthenticated) {
183+
this.logger.info("UiProvider", `User is authenticated with GitHub as: ${authStatus.displayName}`);
184+
} else {
185+
this.logger.info("UiProvider", "User is not authenticated with GitHub");
186+
}
187+
} catch (error) {
188+
this.logger.warn("UiProvider", "Failed to check GitHub authentication status", error);
189+
}
190+
168191
// Auto-select GitHub repository languages if no manual selection exists
169192
let languages = config.get<string[]>("languages", []);
170193
if (languages.length === 0) {
@@ -593,6 +616,70 @@ export class UiProvider implements vscode.WebviewViewProvider {
593616
}
594617
}
595618

619+
private async loginWithGitHub() {
620+
this.logger.logServiceCall("UiProvider", "loginWithGitHub", "started");
621+
622+
try {
623+
// Update UI to show authentication is in progress
624+
this._view?.webview.postMessage({
625+
command: "authStarted",
626+
message: "GitHub authentication in progress..."
627+
});
628+
629+
// Call GitHub Service to authenticate
630+
const success = await this._githubService.authenticateWithGitHub();
631+
632+
if (success) {
633+
this.logger.logServiceCall("UiProvider", "loginWithGitHub", "completed");
634+
635+
try {
636+
// Try to get repository info with the new token
637+
const repoInfo = await this._githubService.getRepositoryInfo();
638+
const config = vscode.workspace.getConfiguration("codeql-scanner");
639+
const token = config.get<string>("github.token");
640+
641+
// Update UI with authentication status
642+
this._view?.webview.postMessage({
643+
command: "authCompleted",
644+
success: true,
645+
message: "Successfully authenticated with GitHub",
646+
config: {
647+
githubToken: token,
648+
githubOwner: repoInfo.owner,
649+
githubRepo: repoInfo.repo,
650+
}
651+
});
652+
} catch (repoError) {
653+
// Still authenticated but couldn't get repo info
654+
this.logger.warn("UiProvider", "Authenticated but couldn't get repository info", repoError);
655+
656+
this._view?.webview.postMessage({
657+
command: "authCompleted",
658+
success: true,
659+
message: "Successfully authenticated with GitHub, but couldn't retrieve repository information.",
660+
});
661+
}
662+
663+
// Reload the configuration to update the UI
664+
this.loadConfiguration();
665+
} else {
666+
this.logger.logServiceCall("UiProvider", "loginWithGitHub", "failed");
667+
this._view?.webview.postMessage({
668+
command: "authCompleted",
669+
success: false,
670+
message: "GitHub authentication failed or was cancelled.",
671+
});
672+
}
673+
} catch (error) {
674+
this.logger.logServiceCall("UiProvider", "loginWithGitHub", "failed", error);
675+
this._view?.webview.postMessage({
676+
command: "authCompleted",
677+
success: false,
678+
message: `GitHub authentication failed: ${error instanceof Error ? error.message : String(error)}`,
679+
});
680+
}
681+
}
682+
596683
private mapGitHubSeverityToLocal(severity?: string): string {
597684
if (!severity) return "medium";
598685

@@ -927,6 +1014,44 @@ export class UiProvider implements vscode.WebviewViewProvider {
9271014
50% { opacity: 0.7; transform: scale(1.05); }
9281015
}
9291016
1017+
/* GitHub Login Button */
1018+
.github-login-btn {
1019+
background: linear-gradient(135deg, #2F4858 0%, #333 100%);
1020+
color: white;
1021+
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
1022+
position: relative;
1023+
border: 1px solid rgba(255, 255, 255, 0.2);
1024+
}
1025+
1026+
.github-login-btn.authenticated {
1027+
background: linear-gradient(135deg, #28a745 0%, #22863a 100%);
1028+
border: 1px solid rgba(40, 167, 69, 0.5);
1029+
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3);
1030+
}
1031+
1032+
.github-login-btn:hover:not(:disabled) {
1033+
background: linear-gradient(135deg, #24292e 0%, #1b1f23 100%);
1034+
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);
1035+
}
1036+
1037+
.github-login-btn.authenticated:hover:not(:disabled) {
1038+
background: linear-gradient(135deg, #22863a 0%, #1e7e34 100%);
1039+
box-shadow: 0 6px 20px rgba(40, 167, 69, 0.4);
1040+
}
1041+
1042+
.github-login-btn:disabled {
1043+
background: linear-gradient(135deg, #9ca3af 0%, #6b7280 100%);
1044+
}
1045+
1046+
.github-icon {
1047+
font-size: 16px;
1048+
transition: all 0.3s ease;
1049+
}
1050+
1051+
.github-login-btn:hover:not(:disabled) .github-icon {
1052+
transform: scale(1.1);
1053+
}
1054+
9301055
/* Fetch Remote Button */
9311056
#fetchButton {
9321057
background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
@@ -1841,6 +1966,12 @@ export class UiProvider implements vscode.WebviewViewProvider {
18411966
<div class="section scan-section">
18421967
<h3>🚀 Actions</h3>
18431968
<div class="button-group">
1969+
<div class="button-row">
1970+
<button onclick="loginWithGitHub()" id="loginButton" class="action-button github-login-btn">
1971+
<span class="github-icon">G</span>
1972+
<span>Sign in with GitHub</span>
1973+
</button>
1974+
</div>
18441975
<div class="button-row">
18451976
<button onclick="runLocalScan()" id="scanButton" class="action-button">
18461977
<span class="scan-icon">🔍</span>
@@ -2212,6 +2343,55 @@ export class UiProvider implements vscode.WebviewViewProvider {
22122343
vscode.postMessage({ command: 'testConnection' });
22132344
}
22142345
2346+
function updateLoginButtonState(isAuthenticated, displayName) {
2347+
const loginButton = document.getElementById('loginButton');
2348+
if (!loginButton) return;
2349+
2350+
const loginIcon = loginButton.querySelector('.github-icon');
2351+
const loginText = loginButton.querySelector('span:last-child');
2352+
2353+
if (isAuthenticated) {
2354+
loginIcon.textContent = '✓';
2355+
if (displayName) {
2356+
loginText.textContent = 'Signed in as ' + displayName;
2357+
} else {
2358+
loginText.textContent = 'Signed in to GitHub';
2359+
}
2360+
loginButton.classList.add('authenticated');
2361+
2362+
// Change button action to log out or switch accounts
2363+
loginButton.onclick = function() {
2364+
// Just trigger login again - VS Code will handle account selection
2365+
loginWithGitHub();
2366+
};
2367+
} else {
2368+
loginIcon.textContent = 'G';
2369+
loginText.textContent = 'Sign in with GitHub';
2370+
loginButton.classList.remove('authenticated');
2371+
2372+
// Reset button action to login
2373+
loginButton.onclick = function() {
2374+
loginWithGitHub();
2375+
};
2376+
}
2377+
}
2378+
2379+
function loginWithGitHub() {
2380+
const loginButton = document.getElementById('loginButton');
2381+
if (loginButton) {
2382+
loginButton.disabled = true;
2383+
loginButton.classList.add('loading');
2384+
2385+
// Update text and icon
2386+
const loginIcon = loginButton.querySelector('.github-icon');
2387+
const loginText = loginButton.querySelector('span:last-child');
2388+
loginIcon.textContent = '...';
2389+
loginText.textContent = 'Authenticating...';
2390+
}
2391+
2392+
vscode.postMessage({ command: 'loginWithGitHub' });
2393+
}
2394+
22152395
function runLocalScan() {
22162396
const scanButton = document.getElementById('scanButton');
22172397
scanButton.disabled = true;
@@ -2454,6 +2634,53 @@ export class UiProvider implements vscode.WebviewViewProvider {
24542634
showMessage(message.message, !message.success);
24552635
break;
24562636
2637+
case 'authStarted':
2638+
showMessage(message.message, false);
2639+
break;
2640+
2641+
case 'authStatus':
2642+
updateLoginButtonState(message.isAuthenticated, message.displayName);
2643+
break;
2644+
2645+
case 'authCompleted':
2646+
const loginButton = document.getElementById('loginButton');
2647+
if (loginButton) {
2648+
loginButton.disabled = false;
2649+
loginButton.classList.remove('loading');
2650+
2651+
// Add success or error animation
2652+
if (message.success) {
2653+
loginButton.classList.add('success');
2654+
setTimeout(() => loginButton.classList.remove('success'), 600);
2655+
2656+
// Update the button to show logged-in state
2657+
updateLoginButtonState(true, message.config?.githubOwner);
2658+
} else {
2659+
loginButton.classList.add('error');
2660+
setTimeout(() => loginButton.classList.remove('error'), 600);
2661+
2662+
const loginIcon = loginButton.querySelector('.github-icon');
2663+
const loginText = loginButton.querySelector('span:last-child');
2664+
2665+
loginIcon.textContent = 'X';
2666+
loginText.textContent = 'Sign in Failed';
2667+
2668+
// Reset to normal state after 3 seconds
2669+
setTimeout(() => {
2670+
loginIcon.textContent = 'G';
2671+
loginText.textContent = 'Sign in with GitHub';
2672+
}, 3000);
2673+
}
2674+
}
2675+
2676+
showMessage(message.message, !message.success);
2677+
2678+
// If authentication was successful, reload configuration to update UI
2679+
if (message.success && message.config) {
2680+
loadConfig();
2681+
}
2682+
break;
2683+
24572684
case 'scanStarted':
24582685
showMessage(message.message, false);
24592686
break;

src/services/githubService.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -728,4 +728,71 @@ export class GitHubService {
728728
return "github.com";
729729
}
730730
}
731+
732+
/**
733+
* Authenticate with GitHub using VS Code's built-in GitHub authentication provider.
734+
* This method will prompt the user to sign in with GitHub and return the authentication session.
735+
* @param scopes The GitHub OAuth scopes to request
736+
* @returns A promise that resolves to the authentication session, or null if authentication failed
737+
*/
738+
public async authenticateWithGitHub(scopes: string[] = ['repo', 'read:org', 'security_events']): Promise<boolean> {
739+
this.logger.logServiceCall("GitHubService", "authenticateWithGitHub", "started");
740+
741+
try {
742+
// Use VS Code's built-in GitHub authentication provider
743+
const session = await vscode.authentication.getSession('github', scopes, { createIfNone: true });
744+
745+
if (session) {
746+
// Got a valid session, update the token and Octokit instance
747+
this.updateToken(session.accessToken);
748+
749+
this.logger.logServiceCall("GitHubService", "authenticateWithGitHub", "completed", {
750+
scopes: session.scopes,
751+
account: session.account.label
752+
});
753+
754+
// Show confirmation to user
755+
vscode.window.showInformationMessage(`Signed in to GitHub as ${session.account.label}`);
756+
757+
return true;
758+
}
759+
760+
return false;
761+
} catch (error) {
762+
this.logger.logServiceCall("GitHubService", "authenticateWithGitHub", "failed", error);
763+
vscode.window.showErrorMessage(`GitHub authentication failed: ${error instanceof Error ? error.message : String(error)}`);
764+
return false;
765+
}
766+
}
767+
768+
/**
769+
* Check if there's an existing GitHub authentication session through VS Code authentication API
770+
* @returns A promise that resolves to information about the current session or null if none exists
771+
*/
772+
public async checkGitHubAuth(): Promise<{ isAuthenticated: boolean; displayName?: string }> {
773+
this.logger.logServiceCall("GitHubService", "checkGitHubAuth", "started");
774+
775+
try {
776+
// Check for existing sessions without prompting the user
777+
const sessions = await vscode.authentication.getAccounts('github');
778+
779+
if (sessions && sessions.length > 0) {
780+
// We have an existing session, use it
781+
this.logger.logServiceCall("GitHubService", "checkGitHubAuth", "completed", {
782+
isAuthenticated: true,
783+
account: sessions[0].label
784+
});
785+
786+
return {
787+
isAuthenticated: true,
788+
displayName: sessions[0].label
789+
};
790+
}
791+
792+
return { isAuthenticated: false };
793+
} catch (error) {
794+
this.logger.logServiceCall("GitHubService", "checkGitHubAuth", "failed", error);
795+
return { isAuthenticated: false };
796+
}
797+
}
731798
}

0 commit comments

Comments
 (0)