initial commit
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
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.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}`));
|
||||
Reference in New Issue
Block a user