119 lines
4.8 KiB
JavaScript
119 lines
4.8 KiB
JavaScript
require('dotenv').config();
|
|
const express = require('express');
|
|
const cors = require('cors');
|
|
|
|
const { requireAuth } = require('./auth');
|
|
const { ensureDefaultAdmin } = require('./users');
|
|
const audit = require('./audit');
|
|
const secrets = require('./secrets');
|
|
const schedule = require('node-schedule');
|
|
|
|
const zonesRouter = require('./routes/zones');
|
|
const secretsRouter = require('./routes/secrets');
|
|
const ipamRouter = require('./routes/ipam');
|
|
const healthRouter = require('./routes/health');
|
|
const recordsRouter = require('./routes/records');
|
|
const settingsRouter = require('./routes/settings');
|
|
const authRouter = require('./routes/auth');
|
|
const usersRouter = require('./routes/users');
|
|
const db = require('./db');
|
|
|
|
// Create default admin user if none exist
|
|
ensureDefaultAdmin();
|
|
|
|
const app = express();
|
|
app.use(cors());
|
|
app.use(express.json());
|
|
|
|
// ─── Public routes (no auth required) ────────────────────────────────────────
|
|
app.get('/api/ping', (req, res) => res.json({ ok: true }));
|
|
app.use('/api/auth', authRouter);
|
|
|
|
// ─── Protected routes (JWT required) ─────────────────────────────────────────
|
|
app.use('/api/zones', requireAuth, zonesRouter);
|
|
app.use('/api/records', requireAuth, recordsRouter);
|
|
app.use('/api/settings', requireAuth, settingsRouter);
|
|
app.use('/api/users', usersRouter);
|
|
app.use('/api/secrets', secretsRouter);
|
|
app.use('/api/ipam', ipamRouter);
|
|
app.use('/api/health', healthRouter); // requireAuth applied inside router
|
|
|
|
app.get('/api/audit', requireAuth, (req, res) => {
|
|
const { limit, offset, user, action, provider, category } = req.query;
|
|
res.json(audit.getEntries({
|
|
limit: Math.min(parseInt(limit) || 100, 500),
|
|
offset: parseInt(offset) || 0,
|
|
user, action, provider, category,
|
|
}));
|
|
});
|
|
|
|
app.get('/api/sync-status/:provider', requireAuth, (req, res) => {
|
|
res.json(db.getProviderSyncStatus(req.params.provider));
|
|
});
|
|
|
|
app.get('/api/domains', requireAuth, (req, res) => {
|
|
res.json(db.getAllZones());
|
|
});
|
|
|
|
app.get('/api/stats', requireAuth, (req, res) => {
|
|
res.json(db.getStats());
|
|
});
|
|
|
|
app.get('/api/providers', requireAuth, (req, res) => {
|
|
const providers = [];
|
|
const disabled = new Set((process.env.DISABLED_PROVIDERS ?? '').split(',').map(s => s.trim().toLowerCase()).filter(Boolean));
|
|
|
|
if (!disabled.has('cloudflare') && process.env.CLOUDFLARE_API_TOKEN) providers.push({ id: 'cloudflare', name: 'Cloudflare' });
|
|
if (!disabled.has('loopia') && process.env.LOOPIA_USER && process.env.LOOPIA_PASSWORD) providers.push({ id: 'loopia', name: 'Loopia' });
|
|
if (!disabled.has('pihole') && process.env.PIHOLE_URL && process.env.PIHOLE_PASSWORD) providers.push({ id: 'pihole', name: 'Pi-hole', url: process.env.PIHOLE_URL });
|
|
if (!disabled.has('azure') && process.env.AZURE_TENANT_ID && process.env.AZURE_CLIENT_ID && process.env.AZURE_CLIENT_SECRET && process.env.AZURE_SUBSCRIPTION_ID) providers.push({ id: 'azure', name: 'Azure DNS' });
|
|
if (!disabled.has('cpanel') && process.env.CPANEL_URL && process.env.CPANEL_USERNAME && process.env.CPANEL_API_TOKEN) providers.push({ id: 'cpanel', name: 'cPanel', url: process.env.CPANEL_URL });
|
|
res.json(providers);
|
|
});
|
|
|
|
// ─── Daily secrets expiry check (runs at 08:00 every day) ────────────────────
|
|
const { notify } = require('./notify');
|
|
|
|
const LAST_CHECK_PATH = require('path').join(__dirname, '..', '.last-secret-check');
|
|
|
|
function getLastCheckDate() {
|
|
try { return require('fs').readFileSync(LAST_CHECK_PATH, 'utf8').trim(); } catch { return null; }
|
|
}
|
|
|
|
function saveLastCheckDate(date) {
|
|
require('fs').writeFileSync(LAST_CHECK_PATH, date, 'utf8');
|
|
}
|
|
|
|
async function checkSecretExpiry() {
|
|
const expiring = secrets.getExpiring();
|
|
if (expiring.length === 0) return;
|
|
|
|
const lines = expiring.map(s =>
|
|
s.status === 'expired'
|
|
? `✕ EXPIRED — ${s.name}`
|
|
: `⚠ ${s.daysLeft}d left — ${s.name}`
|
|
);
|
|
|
|
await notify(
|
|
`🦥 Sloth Manager — Secrets Alert`,
|
|
`${expiring.length} secret${expiring.length !== 1 ? 's' : ''} need attention:\n\n${lines.join('\n')}`
|
|
);
|
|
}
|
|
|
|
async function checkSecretExpiryOnce() {
|
|
const today = new Date().toDateString();
|
|
if (getLastCheckDate() === today) return; // already ran today, persisted to disk
|
|
saveLastCheckDate(today);
|
|
await checkSecretExpiry();
|
|
}
|
|
|
|
// Run at startup only if not already checked today (persisted to disk), then every day at 08:00
|
|
checkSecretExpiryOnce();
|
|
schedule.scheduleJob('0 8 * * *', () => {
|
|
saveLastCheckDate(''); // clear so the scheduled job always fires
|
|
checkSecretExpiry();
|
|
});
|
|
|
|
const PORT = process.env.PORT || 3001;
|
|
app.listen(PORT, () => console.log(`Sloth Manager backend running on port ${PORT}`));
|