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
JavaScript
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;