gitrecon
Version:
This CLI tool is designed to retrieve exposed email addresses from a GitHub user’s public activity and to identify a GitHub username associated with a given email address. It is intended strictly for educational use and ethical security research. Please u
270 lines (215 loc) • 10.2 kB
JavaScript
// Rate limiting and API management - rate limit logic
const ColorUtils = require('../utils/colors');
const { getRateLimitInfo, setRateLimitInfo } = require('../config/settings');
class RateLimiter {
constructor() {
this.limits = {
github: {
unauthenticated: { requests: 60, window: 3600000 }, // 60 requests per hour
authenticated: { requests: 5000, window: 3600000 } // 5000 requests per hour
},
gitlab: {
unauthenticated: { requests: 300, window: 60000 }, // 300 requests per minute
authenticated: { requests: 2000, window: 60000 } // 2000 requests per minute
}
};
this.tokens = {
github: null,
gitlab: null
};
this.requestCounts = {
github: { count: 0, resetTime: null },
gitlab: { count: 0, resetTime: null }
};
}
// Set API token - updateHeader logic
setToken(token, platform) {
this.tokens[platform] = token;
console.log(ColorUtils.green(`Rate limiter configured for ${platform} with token`));
}
// Check if we're approaching rate limits - orijinal koddan
checkRateLimit(platform = 'github') {
const rateLimitInfo = getRateLimitInfo();
if (rateLimitInfo.remaining !== null) {
const warningThreshold = 10;
if (rateLimitInfo.remaining < warningThreshold) {
console.warn(ColorUtils.yellow(`Warning: ${platform} API rate limit is getting low (${rateLimitInfo.remaining} remaining)`));
if (rateLimitInfo.remaining === 0) {
const resetTime = new Date(rateLimitInfo.resetTime);
console.error(ColorUtils.red(`Error: ${platform} API rate limit exceeded. Resets at ${resetTime.toLocaleTimeString()}`));
return false;
}
}
// Update display - orijinal koddan
console.log(ColorUtils.dim(`Rate limit: ${rateLimitInfo.remaining}/${rateLimitInfo.limit} (Resets: ${rateLimitInfo.resetTime ? rateLimitInfo.resetTime.toLocaleTimeString() : 'Unknown'})`));
}
return true;
}
// Update rate limit info from API response headers - orijinal koddan
updateFromHeaders(headers, platform = 'github') {
if (platform === 'github' && headers) {
const remaining = headers['x-ratelimit-remaining'];
const limit = headers['x-ratelimit-limit'];
const resetHeader = headers['x-ratelimit-reset'];
if (remaining && limit && resetHeader) {
const resetTime = new Date(parseInt(resetHeader) * 1000);
const rateLimitInfo = {
remaining: parseInt(remaining),
limit: parseInt(limit),
resetTime
};
setRateLimitInfo(rateLimitInfo);
this.requestCounts[platform] = {
count: parseInt(limit) - parseInt(remaining),
resetTime
};
return rateLimitInfo;
}
} else if (platform === 'gitlab' && headers) {
// GitLab uses different headers
const remaining = headers['ratelimit-remaining'];
const limit = headers['ratelimit-limit'];
const resetHeader = headers['ratelimit-reset'];
if (remaining && limit && resetHeader) {
const resetTime = new Date(parseInt(resetHeader) * 1000);
const rateLimitInfo = {
remaining: parseInt(remaining),
limit: parseInt(limit),
resetTime
};
setRateLimitInfo(rateLimitInfo);
this.requestCounts[platform] = {
count: parseInt(limit) - parseInt(remaining),
resetTime
};
return rateLimitInfo;
}
}
return null;
}
// Calculate optimal delay between requests
calculateOptimalDelay(platform = 'github') {
const rateLimitInfo = getRateLimitInfo();
if (!rateLimitInfo.remaining || !rateLimitInfo.resetTime) {
return 1000; // Default 1 second
}
const timeUntilReset = rateLimitInfo.resetTime.getTime() - Date.now();
const requestsRemaining = rateLimitInfo.remaining;
if (requestsRemaining <= 0) {
return timeUntilReset;
}
// Calculate delay to spread remaining requests evenly
const optimalDelay = Math.max(timeUntilReset / requestsRemaining, 1000);
return Math.min(optimalDelay, 5000); // Cap at 5 seconds
}
// Wait for rate limit reset
async waitForReset(platform = 'github') {
const rateLimitInfo = getRateLimitInfo();
if (!rateLimitInfo.resetTime) {
console.log(ColorUtils.yellow('No reset time available, waiting 60 seconds...'));
await this.sleep(60000);
return;
}
const waitTime = rateLimitInfo.resetTime.getTime() - Date.now();
if (waitTime > 0) {
console.log(ColorUtils.yellow(`Rate limit exceeded. Waiting ${Math.round(waitTime / 1000)} seconds for reset...`));
await this.sleep(waitTime + 1000); // Add 1 second buffer
}
}
// Sleep utility
async sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Get current rate limit status
getStatus(platform = 'github') {
const rateLimitInfo = getRateLimitInfo();
const hasToken = !!this.tokens[platform];
const limits = this.limits[platform];
const currentLimit = hasToken ? limits.authenticated : limits.unauthenticated;
return {
platform,
hasToken,
currentLimit,
rateLimitInfo,
requestCounts: this.requestCounts[platform],
canMakeRequest: this.canMakeRequest(platform),
optimalDelay: this.calculateOptimalDelay(platform)
};
}
// Check if we can make a request
canMakeRequest(platform = 'github') {
const rateLimitInfo = getRateLimitInfo();
if (rateLimitInfo.remaining === null) {
return true; // No rate limit info available
}
return rateLimitInfo.remaining > 0;
}
// Adaptive delay based on rate limit status
async adaptiveDelay(platform = 'github') {
const status = this.getStatus(platform);
if (!status.canMakeRequest) {
await this.waitForReset(platform);
return;
}
const delay = status.optimalDelay;
if (delay > 1000) {
console.log(ColorUtils.dim(`Adaptive delay: ${Math.round(delay)}ms`));
}
await this.sleep(delay);
}
// Estimate remaining scanning time
estimateRemainingTime(requestsNeeded, platform = 'github') {
const status = this.getStatus(platform);
if (!status.canMakeRequest) {
const resetTime = status.rateLimitInfo.resetTime;
if (resetTime) {
return resetTime.getTime() - Date.now() + (requestsNeeded * status.optimalDelay);
}
return null;
}
const availableRequests = status.rateLimitInfo.remaining || status.currentLimit.requests;
if (requestsNeeded <= availableRequests) {
return requestsNeeded * status.optimalDelay;
}
// Will need to wait for reset
const timeToReset = status.rateLimitInfo.resetTime ?
status.rateLimitInfo.resetTime.getTime() - Date.now() :
status.currentLimit.window;
const requestsAfterReset = requestsNeeded - availableRequests;
const timeForRemainingRequests = requestsAfterReset * 1000; // 1 second per request after reset
return timeToReset + timeForRemainingRequests;
}
// Display rate limit information
displayStatus(platform = 'github') {
const status = this.getStatus(platform);
console.log(ColorUtils.bright(`\n=== RATE LIMIT STATUS (${platform.toUpperCase()}) ===`));
console.log(`Authentication: ${status.hasToken ? ColorUtils.green('✓ Token provided') : ColorUtils.yellow('⚠ No token (limited)')}`);
console.log(`Current Limit: ${status.currentLimit.requests} requests per ${status.currentLimit.window / 1000} seconds`);
if (status.rateLimitInfo.remaining !== null) {
console.log(`Remaining: ${ColorUtils.cyan(status.rateLimitInfo.remaining)}/${status.rateLimitInfo.limit}`);
if (status.rateLimitInfo.resetTime) {
console.log(`Resets: ${ColorUtils.cyan(status.rateLimitInfo.resetTime.toLocaleTimeString())}`);
}
}
console.log(`Status: ${status.canMakeRequest ? ColorUtils.green('✓ Can make requests') : ColorUtils.red('✗ Rate limited')}`);
console.log(`Optimal Delay: ${ColorUtils.cyan(Math.round(status.optimalDelay))}ms`);
}
// Reset counters (for testing)
reset() {
this.requestCounts = {
github: { count: 0, resetTime: null },
gitlab: { count: 0, resetTime: null }
};
setRateLimitInfo({
remaining: null,
limit: null,
resetTime: null
});
}
// Cleanup resources
cleanup() {
// Clear any pending timeouts
this.tokens = { github: null, gitlab: null };
}
}
module.exports = RateLimiter;