endpoint-sentinel
Version:
User-friendly security scanner with interactive setup that scales from beginner to expert
234 lines • 7.52 kB
JavaScript
"use strict";
/**
* Configuration Management System
* Handles domain-specific configurations for easy scanning setup
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConfigManager = void 0;
const fs_1 = require("fs");
const path_1 = require("path");
const os_1 = require("os");
const joi_1 = __importDefault(require("joi"));
class ConfigManager {
configDir;
configFile;
store;
constructor() {
this.configDir = (0, path_1.join)((0, os_1.homedir)(), '.endpoint-sentinel');
this.configFile = (0, path_1.join)(this.configDir, 'config.json');
this.store = this.loadOrCreateStore();
}
/**
* Normalizes URL by adding protocol if missing
*/
normalizeUrl(target) {
if (!target || typeof target !== 'string') {
return target;
}
// If URL already has a protocol, return as-is
if (/^https?:\/\//i.test(target)) {
return target;
}
// If it looks like a domain/hostname, prepend https://
if (/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(\/.*)?$/.test(target)) {
return `https://${target}`;
}
// Return as-is for other cases (will likely fail validation)
return target;
}
/**
* Get domain from URL
*/
extractDomain(url) {
try {
const normalizedUrl = this.normalizeUrl(url);
const urlObj = new URL(normalizedUrl);
return urlObj.hostname;
}
catch {
throw new Error(`Invalid URL: ${url}`);
}
}
/**
* Load existing config store or create new one
*/
loadOrCreateStore() {
if (!(0, fs_1.existsSync)(this.configDir)) {
(0, fs_1.mkdirSync)(this.configDir, { recursive: true });
}
if ((0, fs_1.existsSync)(this.configFile)) {
try {
const data = (0, fs_1.readFileSync)(this.configFile, 'utf-8');
const parsed = JSON.parse(data);
// Convert date strings back to Date objects
Object.values(parsed.configs).forEach((config) => {
config.createdAt = new Date(config.createdAt);
config.updatedAt = new Date(config.updatedAt);
});
return parsed;
}
catch (error) {
console.warn('Config file corrupted, creating new one');
}
}
return {
version: '1.0.0',
configs: {},
globalDefaults: {
rateLimit: 2,
timeout: 30000,
maxRedirects: 5,
concurrent: 5
}
};
}
/**
* Save config store to disk
*/
saveStore() {
(0, fs_1.writeFileSync)(this.configFile, JSON.stringify(this.store, null, 2));
}
/**
* Validate domain config
*/
validateDomainConfig(config) {
const schema = joi_1.default.object({
domain: joi_1.default.string().required(),
name: joi_1.default.string().optional(),
authType: joi_1.default.string().valid(...['none', 'basic', 'bearer_token', 'jwt', 'oauth2', 'session_cookie', 'api_key', 'custom']).required(),
token: joi_1.default.string().optional(),
cookie: joi_1.default.string().optional(),
defaultKeywords: joi_1.default.array().items(joi_1.default.string()).optional(),
rateLimit: joi_1.default.number().min(0.1).max(100).optional(),
customHeaders: joi_1.default.object().optional(),
notes: joi_1.default.string().optional(),
createdAt: joi_1.default.date().optional(),
updatedAt: joi_1.default.date().optional()
});
const { error, value } = schema.validate(config);
if (error) {
throw new Error(`Config validation failed: ${error.message}`);
}
const now = new Date();
return {
...value,
createdAt: value.createdAt || now,
updatedAt: now
};
}
/**
* Check if config exists for domain
*/
hasConfig(url) {
const domain = this.extractDomain(url);
return domain in this.store.configs;
}
/**
* Get config for domain
*/
getConfig(url) {
const domain = this.extractDomain(url);
return this.store.configs[domain] || null;
}
/**
* Save config for domain
*/
saveConfig(url, config) {
const domain = this.extractDomain(url);
const validatedConfig = this.validateDomainConfig({
...config,
domain
});
this.store.configs[domain] = validatedConfig;
this.saveStore();
return validatedConfig;
}
/**
* Delete config for domain
*/
deleteConfig(url) {
const domain = this.extractDomain(url);
if (domain in this.store.configs) {
delete this.store.configs[domain];
this.saveStore();
return true;
}
return false;
}
/**
* List all saved configs
*/
listConfigs() {
return Object.values(this.store.configs);
}
/**
* Convert domain config to scan config
*/
buildScanConfig(url, overrides = {}) {
const normalizedUrl = this.normalizeUrl(url);
const domain = this.extractDomain(url);
const domainConfig = this.store.configs[domain];
const baseConfig = {
target: normalizedUrl,
consent: true,
...this.store.globalDefaults
};
if (domainConfig) {
// Apply domain-specific settings
if (domainConfig.defaultKeywords) {
baseConfig.keywords = domainConfig.defaultKeywords;
}
if (domainConfig.rateLimit) {
baseConfig.rateLimit = domainConfig.rateLimit;
}
// Handle authentication
if (domainConfig.authType !== 'none') {
if (domainConfig.cookie) {
baseConfig.cookie = domainConfig.cookie;
}
if (domainConfig.token) {
// For JWT/Bearer tokens, add to cookie or custom header
if (domainConfig.authType === 'jwt' || domainConfig.authType === 'bearer_token') {
baseConfig.cookie = `token=${domainConfig.token}`;
}
}
}
}
// Apply any overrides
return { ...baseConfig, ...overrides };
}
/**
* Update global defaults
*/
updateGlobalDefaults(defaults) {
this.store.globalDefaults = { ...this.store.globalDefaults, ...defaults };
this.saveStore();
}
/**
* Get config file path for manual editing
*/
getConfigPath() {
return this.configFile;
}
/**
* Reset all configurations
*/
reset() {
this.store = {
version: '1.0.0',
configs: {},
globalDefaults: {
rateLimit: 2,
timeout: 30000,
maxRedirects: 5,
concurrent: 5
}
};
this.saveStore();
}
}
exports.ConfigManager = ConfigManager;
//# sourceMappingURL=config-manager.js.map