UNPKG

api-scout

Version:

🔍 Automatically scout, discover and generate beautiful interactive API documentation from your codebase. Supports Express.js, NestJS, FastAPI, Spring Boot with interactive testing and security analysis.

551 lines (492 loc) 16.6 kB
const fs = require('fs-extra'); const path = require('path'); class AuthenticationAnalyzer { constructor() { this.authPatterns = { jwt: { patterns: [ /jwt/i, /jsonwebtoken/i, /bearer\s+token/i, /@nestjs\/jwt/i, /jwt\.sign/i, /jwt\.verify/i, /Authorization:\s*Bearer/i ], indicators: [ 'JWT_SECRET', 'JWT_EXPIRES_IN', 'access_token', 'refresh_token', 'Bearer', 'jsonwebtoken' ] }, oauth: { patterns: [ /oauth/i, /passport-oauth/i, /passport-google/i, /passport-github/i, /passport-facebook/i, /client_id/i, /client_secret/i, /authorization_code/i, /access_token/i ], indicators: [ 'GOOGLE_CLIENT_ID', 'GITHUB_CLIENT_ID', 'FACEBOOK_CLIENT_ID', 'OAuth2Strategy', 'GoogleStrategy', 'GitHubStrategy' ] }, apiKey: { patterns: [ /api[-_]?key/i, /x-api-key/i, /api[-_]?token/i, /x-auth-token/i, /authorization.*api/i ], indicators: [ 'API_KEY', 'X-API-KEY', 'X-AUTH-TOKEN', 'api_key', 'apikey' ] }, basic: { patterns: [ /basic\s+auth/i, /Authorization:\s*Basic/i, /btoa\(/i, /atob\(/i, /username.*password/i ], indicators: [ 'BasicAuth', 'Basic ', 'username', 'password', 'credentials' ] }, session: { patterns: [ /express-session/i, /session/i, /cookie-session/i, /req\.session/i, /session\.save/i, /session\.destroy/i ], indicators: [ 'SESSION_SECRET', 'express-session', 'cookie-session', 'session', 'sessionID' ] }, passport: { patterns: [ /passport/i, /passport-local/i, /passport\.use/i, /passport\.authenticate/i, /LocalStrategy/i ], indicators: [ 'passport', 'LocalStrategy', 'passport.authenticate', 'passport.use' ] } }; this.middlewarePatterns = { express: [ 'authenticateToken', 'authenticate', 'authMiddleware', 'verifyToken', 'checkAuth', 'requireAuth', 'isAuthenticated', 'ensureAuthenticated' ], nestjs: [ 'AuthGuard', 'JwtAuthGuard', 'LocalAuthGuard', 'RolesGuard', 'PermissionsGuard' ] }; } async analyzeProject(projectPath) { const authInfo = { schemes: [], middleware: [], securityRequirements: [], endpoints: [], configuration: {}, recommendations: [] }; try { // Analyze package.json for auth dependencies await this.analyzePackageJson(projectPath, authInfo); // Analyze source files for auth patterns await this.analyzeSourceFiles(projectPath, authInfo); // Analyze environment variables await this.analyzeEnvironmentFiles(projectPath, authInfo); // Generate security recommendations this.generateRecommendations(authInfo); return authInfo; } catch (error) { console.warn('⚠️ Authentication analysis failed:', error.message); return authInfo; } } async analyzePackageJson(projectPath, authInfo) { const packageJsonPath = path.join(projectPath, 'package.json'); if (await fs.pathExists(packageJsonPath)) { const packageJson = await fs.readJson(packageJsonPath); const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies }; // Check for authentication libraries const authLibraries = { 'jsonwebtoken': 'JWT', 'passport': 'Passport', 'passport-jwt': 'JWT (Passport)', 'passport-local': 'Local (Passport)', 'passport-oauth2': 'OAuth2 (Passport)', 'passport-google-oauth20': 'Google OAuth', 'passport-github2': 'GitHub OAuth', 'passport-facebook': 'Facebook OAuth', '@nestjs/jwt': 'JWT (NestJS)', '@nestjs/passport': 'Passport (NestJS)', 'express-session': 'Session', 'cookie-session': 'Cookie Session', 'bcrypt': 'Password Hashing', 'bcryptjs': 'Password Hashing', 'argon2': 'Password Hashing', 'auth0': 'Auth0', 'firebase-admin': 'Firebase Auth', 'aws-cognito-identity-js': 'AWS Cognito' }; Object.entries(authLibraries).forEach(([dep, type]) => { if (allDeps[dep]) { authInfo.schemes.push({ type, library: dep, version: allDeps[dep], source: 'package.json' }); } }); } } async analyzeSourceFiles(projectPath, authInfo) { const { glob } = require('glob'); const patterns = [ '**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx' ]; const excludePatterns = [ 'node_modules/**', 'dist/**', 'build/**', '.git/**' ]; for (const pattern of patterns) { const files = await glob(pattern, { cwd: projectPath, absolute: true, ignore: excludePatterns }); for (const file of files.slice(0, 50)) { // Limit for performance await this.analyzeFile(file, authInfo); } } } async analyzeFile(filePath, authInfo) { try { const content = await fs.readFile(filePath, 'utf8'); const relativePath = path.basename(filePath); // Check for authentication patterns Object.entries(this.authPatterns).forEach(([authType, config]) => { const hasPattern = config.patterns.some(pattern => pattern.test(content)); const hasIndicator = config.indicators.some(indicator => content.includes(indicator) ); if (hasPattern || hasIndicator) { const existing = authInfo.schemes.find(s => s.type === authType); if (!existing) { authInfo.schemes.push({ type: authType, source: 'source_analysis', files: [relativePath], patterns: config.patterns.filter(p => p.test(content)).map(p => p.toString()), indicators: config.indicators.filter(i => content.includes(i)) }); } else { if (!existing.files) existing.files = []; if (!existing.files.includes(relativePath)) { existing.files.push(relativePath); } } } }); // Extract security middleware this.extractSecurityMiddleware(content, filePath, authInfo); // Extract security headers this.extractSecurityHeaders(content, filePath, authInfo); // Extract authentication endpoints this.extractAuthEndpoints(content, filePath, authInfo); } catch (error) { // Skip files that can't be read } } extractSecurityMiddleware(content, filePath, authInfo) { const fileName = path.basename(filePath); // Express middleware patterns this.middlewarePatterns.express.forEach(middleware => { if (content.includes(middleware)) { authInfo.middleware.push({ name: middleware, type: 'express', file: fileName, framework: 'Express' }); } }); // NestJS guard patterns this.middlewarePatterns.nestjs.forEach(guard => { if (content.includes(guard)) { authInfo.middleware.push({ name: guard, type: 'guard', file: fileName, framework: 'NestJS' }); } }); // Custom middleware detection const customMiddlewarePatterns = [ /function\s+(\w*auth\w*)/gi, /const\s+(\w*auth\w*)\s*=/gi, /class\s+(\w*Auth\w*Guard)/gi, /class\s+(\w*Auth\w*Middleware)/gi ]; customMiddlewarePatterns.forEach(pattern => { let match; while ((match = pattern.exec(content)) !== null) { authInfo.middleware.push({ name: match[1], type: 'custom', file: fileName, framework: 'custom' }); } }); } extractSecurityHeaders(content, filePath, authInfo) { const securityHeaders = [ 'Authorization', 'X-API-Key', 'X-Auth-Token', 'X-Access-Token', 'X-CSRF-Token', 'X-Requested-With' ]; securityHeaders.forEach(header => { if (content.includes(header)) { const existing = authInfo.securityRequirements.find(s => s.header === header); if (!existing) { authInfo.securityRequirements.push({ header, files: [path.basename(filePath)], type: this.getHeaderType(header) }); } else { if (!existing.files.includes(path.basename(filePath))) { existing.files.push(path.basename(filePath)); } } } }); } getHeaderType(header) { const headerTypes = { 'Authorization': 'Bearer/Basic/Custom', 'X-API-Key': 'API Key', 'X-Auth-Token': 'Auth Token', 'X-Access-Token': 'Access Token', 'X-CSRF-Token': 'CSRF Protection', 'X-Requested-With': 'AJAX Protection' }; return headerTypes[header] || 'Custom'; } extractAuthEndpoints(content, filePath, authInfo) { const authEndpointPatterns = [ { pattern: /app\.(post|get)\s*\(\s*['"`]([^'"`]*(?:login|auth|signin)[^'"`]*)['"`]/gi, framework: 'Express' }, { pattern: /app\.(post|get)\s*\(\s*['"`]([^'"`]*(?:logout|signout)[^'"`]*)['"`]/gi, framework: 'Express' }, { pattern: /app\.(post|get)\s*\(\s*['"`]([^'"`]*(?:register|signup)[^'"`]*)['"`]/gi, framework: 'Express' }, { pattern: /app\.(post|get)\s*\(\s*['"`]([^'"`]*(?:refresh|token)[^'"`]*)['"`]/gi, framework: 'Express' }, { pattern: /@(Post|Get)\s*\(\s*['"`]([^'"`]*(?:login|auth|signin)[^'"`]*)['"`]/gi, framework: 'NestJS' }, { pattern: /@(Post|Get)\s*\(\s*['"`]([^'"`]*(?:logout|signout)[^'"`]*)['"`]/gi, framework: 'NestJS' }, { pattern: /@(Post|Get)\s*\(\s*['"`]([^'"`]*(?:register|signup)[^'"`]*)['"`]/gi, framework: 'NestJS' } ]; authEndpointPatterns.forEach(({ pattern, framework }) => { let match; while ((match = pattern.exec(content)) !== null) { authInfo.endpoints.push({ method: match[1].toUpperCase(), path: match[2], type: this.classifyAuthEndpoint(match[2]), framework, file: path.basename(filePath) }); } }); } classifyAuthEndpoint(path) { if (/login|signin|auth/i.test(path)) return 'login'; if (/logout|signout/i.test(path)) return 'logout'; if (/register|signup/i.test(path)) return 'register'; if (/refresh|token/i.test(path)) return 'token_refresh'; if (/forgot|reset|password/i.test(path)) return 'password_reset'; return 'authentication'; } async analyzeEnvironmentFiles(projectPath, authInfo) { const envFiles = ['.env', '.env.local', '.env.development', '.env.production']; for (const envFile of envFiles) { const envPath = path.join(projectPath, envFile); if (await fs.pathExists(envPath)) { try { const envContent = await fs.readFile(envPath, 'utf8'); // Extract auth-related environment variables const authEnvPatterns = [ /JWT_SECRET/i, /JWT_EXPIRES/i, /API_KEY/i, /CLIENT_ID/i, /CLIENT_SECRET/i, /SESSION_SECRET/i, /AUTH_SECRET/i, /GOOGLE_CLIENT/i, /GITHUB_CLIENT/i, /FACEBOOK_CLIENT/i, /AUTH0_/i, /FIREBASE_/i ]; authEnvPatterns.forEach(pattern => { const matches = envContent.match(new RegExp(`^${pattern.source}.*$`, 'gim')); if (matches) { matches.forEach(match => { const [key] = match.split('='); authInfo.configuration[key.trim()] = { source: envFile, type: this.classifyEnvVar(key.trim()) }; }); } }); } catch (error) { // Skip unreadable env files } } } } classifyEnvVar(key) { if (/JWT/i.test(key)) return 'JWT'; if (/CLIENT/i.test(key)) return 'OAuth'; if (/API.*KEY/i.test(key)) return 'API Key'; if (/SESSION/i.test(key)) return 'Session'; if (/AUTH0/i.test(key)) return 'Auth0'; if (/FIREBASE/i.test(key)) return 'Firebase'; if (/GOOGLE/i.test(key)) return 'Google OAuth'; if (/GITHUB/i.test(key)) return 'GitHub OAuth'; if (/FACEBOOK/i.test(key)) return 'Facebook OAuth'; return 'Authentication'; } generateRecommendations(authInfo) { // Security recommendations based on analysis const recommendations = []; // Check for JWT without proper expiration const hasJWT = authInfo.schemes.some(s => s.type.includes('JWT') || s.type === 'jwt'); const hasJWTExpiry = Object.keys(authInfo.configuration).some(key => /JWT.*EXPIR/i.test(key) ); if (hasJWT && !hasJWTExpiry) { recommendations.push({ type: 'security', priority: 'high', title: 'JWT Token Expiration', description: 'JWT tokens should have expiration times configured', suggestion: 'Add JWT_EXPIRES_IN environment variable and configure token expiration' }); } // Check for password hashing const hasPasswordHashing = authInfo.schemes.some(s => s.library && ['bcrypt', 'bcryptjs', 'argon2'].includes(s.library) ); const hasAuthEndpoints = authInfo.endpoints.some(e => ['login', 'register'].includes(e.type) ); if (hasAuthEndpoints && !hasPasswordHashing) { recommendations.push({ type: 'security', priority: 'critical', title: 'Password Hashing', description: 'Passwords should be properly hashed using bcrypt or argon2', suggestion: 'Install and implement bcrypt or argon2 for password hashing' }); } // Check for HTTPS enforcement recommendations.push({ type: 'security', priority: 'high', title: 'HTTPS Enforcement', description: 'Authentication should always happen over HTTPS', suggestion: 'Implement HTTPS redirection and secure cookie settings' }); // Check for rate limiting on auth endpoints if (authInfo.endpoints.length > 0) { recommendations.push({ type: 'security', priority: 'medium', title: 'Rate Limiting', description: 'Authentication endpoints should have rate limiting', suggestion: 'Implement rate limiting on login, register, and password reset endpoints' }); } authInfo.recommendations = recommendations; } generateSecurityReport(authInfo) { return { summary: { authSchemesCount: authInfo.schemes.length, middlewareCount: authInfo.middleware.length, authEndpointsCount: authInfo.endpoints.length, securityIssues: authInfo.recommendations.filter(r => r.priority === 'critical').length, securityWarnings: authInfo.recommendations.filter(r => r.priority === 'high').length }, schemes: authInfo.schemes, middleware: authInfo.middleware, endpoints: authInfo.endpoints, configuration: authInfo.configuration, recommendations: authInfo.recommendations }; } } module.exports = AuthenticationAnalyzer;