UNPKG

sourcewizard

Version:

SourceWizard - AI-powered setup wizard for dev tools and libraries with MCP integration

134 lines (133 loc) 4.66 kB
import { TokenStorage } from "./token-storage.js"; import { WebAuthServer } from "./server.js"; import open from "open"; export class CLIAuth { auth; tokenStorage; constructor(authInstance, configDir) { this.auth = authInstance; this.tokenStorage = new TokenStorage(configDir, authInstance); } /** * Initialize authentication by restoring session from stored tokens * Now includes automatic token refresh if tokens are expired */ async initialize() { // First attempt to refresh tokens on startup await this.tokenStorage.attemptStartupRefresh(); // Then get tokens (which may have been refreshed) const tokens = await this.tokenStorage.getTokens(); if (tokens) { try { // Set the session with potentially refreshed tokens await this.auth.setSession({ access_token: tokens.accessToken, refresh_token: tokens.refreshToken, }); } catch (error) { // If session restoration fails, log the error but don't clear tokens immediately // The user may still be able to use them for some operations console.error("Failed to restore session:", error instanceof Error ? error.message : String(error)); } } } /** * Login using web-based authentication */ async login(options = {}) { const webAuthServer = new WebAuthServer({ tokenStorage: this.tokenStorage, }); try { // Start the web auth server on a random port const port = await webAuthServer.start(); const callbackUrl = webAuthServer.getCallbackUrl(); if (!callbackUrl) { throw new Error("Failed to get callback URL"); } // Construct login page URL with callback parameter const loginPageUrl = `${options.loginPageUrl}/cli-login?redirect_to=${encodeURIComponent(callbackUrl)}`; await open(loginPageUrl); // Wait for authentication const tokens = await webAuthServer.waitForAuth(); // Stop the server await webAuthServer.stop(); return { isAuthenticated: true, user: tokens.user, }; } catch (error) { // Make sure to stop the server on error try { await webAuthServer.stop(); } catch (stopError) { // Ignore stop errors } throw new Error(error instanceof Error ? error.message : "Web authentication failed with unknown error"); } } /** * Logout the current user */ async logout() { try { await this.auth.signOut(); } catch (error) { // Even if Supabase logout fails, clear local tokens } finally { await this.tokenStorage.clearTokens(); } } /** * Get current authentication status * Now uses the automatic token refresh functionality */ async getStatus() { // Check tokens (this will automatically refresh if needed) const storedUser = await this.tokenStorage.getStoredUser(); if (!storedUser) { return { isAuthenticated: false }; } // Verify with Supabase session try { const { data: { session }, error, } = await this.auth.getSession(); if (error || !session) { // Don't immediately clear tokens - they might be refreshable return { isAuthenticated: false }; } return { isAuthenticated: true, user: storedUser, }; } catch (error) { // If verification fails, return unauthenticated but don't clear tokens // The tokens might still be valid for API operations return { isAuthenticated: false }; } } /** * Ensure user is authenticated, throw error if not */ async requireAuth() { const status = await this.getStatus(); if (!status.isAuthenticated || !status.user) { throw new Error("Authentication required. Please login first using: sourcewizard login"); } return status.user; } /** * Get current user ID for database operations */ async getCurrentUserId() { const user = await this.requireAuth(); return user.id; } }