UNPKG

secure-scan-js

Version:

A JavaScript implementation of Yelp's detect-secrets tool - no Python required

272 lines (246 loc) 8.55 kB
// Web-based CLI Authentication Flow const express = require('express'); const open = require('open'); const fs = require('fs'); const path = require('path'); const http = require('http'); const crypto = require('crypto'); const chalk = require('chalk'); // Configuration const AUTH_CONFIG = { // Default to 3 minutes (180 seconds) expiration tokenExpirationSeconds: 300 * 60 * 24, callbackPort: 3005, tokenFile: path.join(process.env.HOME || process.env.USERPROFILE, '.detect-secrets-token.json'), // Replace this with your actual web app auth URL authUrl: 'https://app.securesecretsai.com/dashboard/cli' || 'https://mywebapp.com/auth/cli' }; /** * CLI Authentication class for web-based login flow */ class WebAuth { constructor(config = {}) { this.config = { ...AUTH_CONFIG, ...config }; this.app = express(); this.server = null; // Generate state parameter for CSRF protection this.state = crypto.randomBytes(16).toString('hex'); } /** * Initialize the authentication server * @returns {Promise} Promise that resolves when auth is complete */ async login() { console.log(chalk.cyan('Starting web authentication flow...')); return new Promise((resolve, reject) => { // Set up the callback endpoint this.app.get('/callback', (req, res) => { try { // Verify state parameter to prevent CSRF if (req.query.state !== this.state) { res.status(400).send('Invalid state parameter. Authentication failed.'); reject(new Error('Invalid state parameter')); return; } // Get token from query parameters const token = req.query.token || req.query.code; if (!token) { res.status(400).send('No token or code provided'); reject(new Error('No token or code provided')); return; } // Calculate expiration time (3 minutes from now) const now = Math.floor(Date.now() / 1000); const expiresAt = now + this.config.tokenExpirationSeconds; // Store token with expiration const tokenData = { token, expiresAt, createdAt: now }; // Save token to file this.saveToken(tokenData); // Send success page res.send(` <!DOCTYPE html> <html> <head> <title>Authentication Successful</title> <style> body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background-color: #f5f5f5; } .success-box { background-color: white; border-radius: 8px; padding: 30px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); max-width: 500px; margin: 0 auto; } h1 { color: #4CAF50; } p { color: #555; } .cli-message { background-color: #f0f0f0; padding: 10px; border-radius: 4px; font-family: monospace; margin: 20px 0; } </style> </head> <body> <div class="success-box"> <h1>Authentication Successful</h1> <p>You have successfully authenticated the CLI.</p> <p>You can close this window and return to the terminal.</p> <div class="cli-message"> Token valid for ${this.config.tokenExpirationSeconds} seconds (expires at ${new Date(expiresAt * 1000).toLocaleTimeString()}) </div> </div> <script> // Close the window automatically after 5 seconds setTimeout(() => window.close(), 5000); </script> </body> </html> `); // Close server and resolve promise this.closeServer(); resolve(tokenData); } catch (error) { res.status(500).send(`Authentication error: ${error.message}`); reject(error); } }); // Start server this.server = this.app.listen(this.config.callbackPort, () => { console.log(chalk.blue(`Authentication server started on port ${this.config.callbackPort}`)); // Build auth URL with redirect to our local server const callbackUrl = `http://localhost:${this.config.callbackPort}/callback`; const authUrl = `${this.config.authUrl}?redirect_uri=${encodeURIComponent(callbackUrl)}&state=${this.state}`; // Open browser console.log(chalk.yellow('Opening browser for authentication...')); console.log(chalk.yellow(authUrl)); open(authUrl).catch(err => { console.error('Failed to open browser:', err); console.log(chalk.yellow(`Please open this URL manually: ${authUrl}`)); }); }); // Handle server errors this.server.on('error', (err) => { if (err.code === 'EADDRINUSE') { console.error(chalk.red(`Port ${this.config.callbackPort} is already in use.`)); console.error(chalk.yellow('You may have another authentication process running.')); console.error(chalk.yellow('Please close it or use a different port.')); } else { console.error(chalk.red('Server error:'), err); } reject(err); }); // Set a timeout for authentication (2 minutes) setTimeout(() => { if (this.server) { this.closeServer(); reject(new Error('Authentication timed out. Please try again.')); } }, 120000); // 2 minutes }); } /** * Close the authentication server */ closeServer() { if (this.server) { this.server.close(); this.server = null; console.log(chalk.blue('Authentication server closed')); } } /** * Save token to file * @param {Object} tokenData Token data object */ saveToken(tokenData) { try { fs.writeFileSync(this.config.tokenFile, JSON.stringify(tokenData, null, 2)); console.log(chalk.green('Token saved successfully')); } catch (error) { console.error(chalk.red('Failed to save token:'), error); throw error; } } /** * Get token status * @returns {Object} Token status */ getTokenStatus() { try { // Check if token file exists if (!fs.existsSync(this.config.tokenFile)) { return { valid: false, message: 'Token missing. Please run "yarn custom:login".' }; } // Read token const tokenData = JSON.parse(fs.readFileSync(this.config.tokenFile, 'utf8')); // console.log(tokenData, "__tokenData") // Check if token is expired const now = Math.floor(Date.now() / 1000); if (now > tokenData.expiresAt) { return { valid: false, message: 'Token expired. Please run "yarn custom:login".', expiredAt: new Date(tokenData.expiresAt * 1000).toLocaleString() }; } // Calculate remaining time const remainingSeconds = tokenData.expiresAt - now; // Token is valid return { valid: true, message: 'Token is valid.', expiresIn: `${remainingSeconds} seconds`, expiresAt: new Date(tokenData.expiresAt * 1000).toLocaleString() }; } catch (error) { console.error(chalk.red('Error checking token status:'), error); return { valid: false, message: `Error checking token: ${error.message}` }; } } /** * Check if token is valid * @returns {boolean} Whether token is valid */ isTokenValid() { const status = this.getTokenStatus(); return status.valid; } /** * Sign out - remove the token */ logout() { try { if (fs.existsSync(this.config.tokenFile)) { fs.unlinkSync(this.config.tokenFile); console.log(chalk.green('Token removed. You are now logged out.')); return true; } else { console.log(chalk.yellow('No token found. Already logged out.')); return false; } } catch (error) { console.error(chalk.red('Error during logout:'), error); return false; } } } // Create and export singleton instance module.exports = new WebAuth();