mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-30 18:56:16 +00:00
104 lines
4.2 KiB
JavaScript
104 lines
4.2 KiB
JavaScript
import 'dotenv/config';
|
|
import { z } from 'zod';
|
|
import { createHash } from 'crypto';
|
|
import { readSync } from 'fs';
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// ADMIN PASSWORD GATE
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// Purpose: If this codebase is released, it is inoperable without the
|
|
// admin password. Even with correct env vars, credentials are gated.
|
|
//
|
|
// To set your own password:
|
|
// node -e "console.log(require('crypto').createHash('sha256').update('YOUR_PASS').digest('hex'))"
|
|
// Then replace ADMIN_PASSWORD_HASH below.
|
|
// ───────────────────────────────────────────────────────────────────────────
|
|
const ADMIN_PASSWORD_HASH = '1f34adb50429071ba3d9bee229b22b1c09c2d82aee19b31ec394218f87e5fd19';
|
|
|
|
function promptPassword(query) {
|
|
process.stdout.write(query);
|
|
const buf = Buffer.alloc(1024);
|
|
const n = readSync(0, buf, 0, 1024);
|
|
return buf.toString('utf8', 0, n).trim();
|
|
}
|
|
|
|
function verifyPassword() {
|
|
// Non-interactive bypass for emergency/automation (not for production release)
|
|
if (process.env.STACK_PASSWORD) {
|
|
const hash = createHash('sha256').update(process.env.STACK_PASSWORD).digest('hex');
|
|
if (hash === ADMIN_PASSWORD_HASH) return true;
|
|
console.error('❌ Invalid STACK_PASSWORD hash');
|
|
return false;
|
|
}
|
|
|
|
// Skip password check in test environments if explicitly requested
|
|
if (process.env.SKIP_PASSWORD_CHECK === '1') {
|
|
console.warn('⚠️ SKIP_PASSWORD_CHECK=1 — bypassing admin password gate');
|
|
return true;
|
|
}
|
|
|
|
const password = promptPassword('🔐 Admin password: ');
|
|
const hash = createHash('sha256').update(password).digest('hex');
|
|
return hash === ADMIN_PASSWORD_HASH;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
// ENV SCHEMA
|
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
const EnvSchema = z.object({
|
|
// Linear API
|
|
LINEAR_API_KEY: z.string().min(1, 'LINEAR_API_KEY is required'),
|
|
LINEAR_TEAM_ID: z.string().optional().default('3c8c51e6-3f24-4999-8fe6-3e097468bf6c'),
|
|
|
|
// Notion API
|
|
NOTION_API_KEY: z.string().min(1, 'NOTION_API_KEY is required'),
|
|
NOTION_DATABASE_ID: z.string().min(1, 'NOTION_DATABASE_ID is required'),
|
|
|
|
// ENE Security
|
|
ENE_SECRET_KEY: z.string().min(1, 'ENE_SECRET_KEY is required'),
|
|
ENE_ENCRYPTION_KEY: z.string().optional(),
|
|
|
|
// Environment
|
|
NODE_ENV: z.enum(['development', 'production', 'test']).optional().default('development'),
|
|
});
|
|
|
|
let env = null;
|
|
|
|
export function loadConfig() {
|
|
if (env) return env;
|
|
|
|
const ok = verifyPassword();
|
|
if (!ok) {
|
|
console.error('❌ Admin password verification failed');
|
|
console.error(' This stack requires a local password to load credentials.');
|
|
console.error(' If you are not the stack operator, this system is inoperable.');
|
|
process.exit(1);
|
|
}
|
|
|
|
try {
|
|
env = EnvSchema.parse(process.env);
|
|
console.log('✅ Configuration loaded successfully');
|
|
return env;
|
|
} catch (error) {
|
|
console.error('❌ Configuration error:');
|
|
if (error.errors) {
|
|
error.errors.forEach((err) => {
|
|
console.error(` - ${err.path.join('.')}: ${err.message}`);
|
|
});
|
|
} else {
|
|
console.error(error);
|
|
}
|
|
console.error('\n💡 Set the required environment variables in your .env file');
|
|
console.error(' Copy .env.example to .env and add your API keys');
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
export function getConfig() {
|
|
if (!env) {
|
|
return loadConfig();
|
|
}
|
|
return env;
|
|
}
|
|
|
|
export default getConfig;
|