cronjob de manejo de invitados listo

This commit is contained in:
2025-09-26 19:28:58 -06:00
parent 0d4b0cbf67
commit 7d7a845a75
6 changed files with 102 additions and 9 deletions

View File

@@ -1,5 +1,5 @@
import { createApp } from './src/app.js';
import { ensureSchema } from './src/services/db.js';
import { ensureSchema, disableGuestsFromYesterday } from './src/services/db.js';
const app = createApp();
const port = process.env.PORT || 3000;
@@ -13,3 +13,24 @@ try {
app.listen(port, () => {
console.log(`Node RADIUS REST API listening on :${port}`);
});
// Schedule daily guest disable at 4:00 AM America/Tegucigalpa (UTC-6 -> 10:00 UTC)
function scheduleGuestJob() {
const now = new Date();
const next = new Date(now);
next.setUTCHours(10, 0, 0, 0); // 10:00 UTC == 4:00 local (UTC-6)
if (next <= now) next.setUTCDate(next.getUTCDate() + 1);
const delay = next - now;
setTimeout(async function run() {
try {
const { count } = await disableGuestsFromYesterday();
if (count) console.log(`[guest-cron] Disabled ${count} invitado users from yesterday`);
} catch (e) {
console.error('[guest-cron] Error:', e?.message || e);
} finally {
scheduleGuestJob();
}
}, delay);
}
scheduleGuestJob();

View File

@@ -1,7 +1,7 @@
import { Router } from 'express';
import { VLAN_ID } from '../config/env.js';
import { clearRequests, getRecentRequests, registerSse } from '../sse.js';
import { deleteUserFromDb, readUsersFromDb, upsertUserToDb, pool } from '../services/db.js';
import { deleteUserFromDb, readUsersFromDb, upsertUserToDb, pool, disableGuestsFromYesterday } from '../services/db.js';
import { disconnectUserSessions } from '../services/radius.js';
const router = Router();
@@ -17,17 +17,31 @@ router.get('/users', async (_req, res) => {
}
});
router.get('/users/:username', async (req, res) => {
try {
const uname = String(req.params.username);
const items = await readUsersFromDb();
const found = items.find(u => u.username === uname);
if (!found) return res.status(404).json({ ok: false, error: 'not_found' });
res.json({ ok: true, item: found });
} catch (e) {
console.error('GET /api/users/:username error:', e?.message || e);
res.status(500).json({ ok: false, error: 'db_error' });
}
});
router.post('/users', async (req, res) => {
const { username, password, vlan, disabled } = req.body || {};
const { username, password, vlan, disabled, etiquetas } = req.body || {};
if (!username || !password) return res.status(400).json({ ok: false, error: 'username and password required' });
const user = { username: String(username), password: String(password), vlan: String(vlan || VLAN_ID), disabled: !!disabled };
if (Array.isArray(etiquetas)) user.etiquetas = etiquetas.map(String).slice(0, 100);
await upsertUserToDb(user);
res.json({ ok: true });
});
router.patch('/users/:username', async (req, res) => {
const uname = String(req.params.username);
const { password, vlan, disabled } = req.body || {};
const { password, vlan, disabled, etiquetas } = req.body || {};
const current = (await readUsersFromDb()).find(u => u.username === uname);
if (!current) return res.status(404).json({ ok: false, error: 'not_found' });
const next = {
@@ -36,6 +50,7 @@ router.patch('/users/:username', async (req, res) => {
vlan: vlan !== undefined ? String(vlan) : current.vlan,
disabled: disabled !== undefined ? !!disabled : current.disabled,
};
if (etiquetas !== undefined) next.etiquetas = Array.isArray(etiquetas) ? etiquetas.map(String).slice(0, 100) : current.etiquetas;
await upsertUserToDb(next);
if (disabled === true) {
disconnectUserSessions(uname).catch(err => console.error('CoA disconnect error:', err));
@@ -202,4 +217,15 @@ router.post('/devices/:id/disconnect', async (req, res) => {
}
});
// Manual trigger: disable invited users from yesterday (America/Tegucigalpa day)
router.post('/guests/disable-yesterday', async (_req, res) => {
try {
const { count } = await disableGuestsFromYesterday();
res.json({ ok: true, count });
} catch (e) {
console.error('POST /api/guests/disable-yesterday error:', e?.message || e);
res.status(500).json({ ok: false, error: 'db_error' });
}
});
export default router;

View File

@@ -176,7 +176,7 @@ export async function readUsersFromDb() {
}
export async function upsertUserToDb(user) {
const { username, password, vlan, disabled } = user;
const { username, password, vlan, disabled, etiquetas } = user;
const client = await pool.connect();
try {
await client.query('BEGIN');
@@ -185,6 +185,9 @@ export async function upsertUserToDb(user) {
'INSERT INTO users (username) VALUES ($1) ON CONFLICT (username) DO NOTHING',
[username]
);
if (Array.isArray(etiquetas)) {
await client.query('UPDATE users SET etiquetas = $2, updated_at = NOW() WHERE username = $1', [username, etiquetas.map(String).slice(0, 100)]);
}
await client.query("DELETE FROM radcheck WHERE username = $1 AND attribute = 'Cleartext-Password'", [username]);
await client.query(
"INSERT INTO radcheck (username, attribute, op, value) VALUES ($1,'Cleartext-Password',':=',$2)",
@@ -234,6 +237,37 @@ export async function upsertUserToDb(user) {
}
}
export async function disableGuestsFromYesterday() {
const client = await pool.connect();
try {
await client.query('BEGIN');
const { rows } = await client.query(`
WITH tz AS (
SELECT (NOW() AT TIME ZONE 'America/Tegucigalpa')::date AS today
)
SELECT u.username
FROM users u, tz
WHERE 'invitado' = ANY(u.etiquetas)
AND u.habilitado_since IS NOT NULL
AND (u.habilitado_since AT TIME ZONE 'America/Tegucigalpa')::date = (tz.today - INTERVAL '1 day')::date
`);
const usernames = rows.map(r => r.username);
if (usernames.length === 0) { await client.query('COMMIT'); return { count: 0 }; }
await client.query("DELETE FROM radcheck WHERE attribute = 'Auth-Type' AND username = ANY($1)", [usernames]);
const values = usernames.map(u => `('${u}','Auth-Type',':=','Reject')`).join(',');
await client.query(`INSERT INTO radcheck (username, attribute, op, value) VALUES ${values}`);
await client.query('UPDATE users SET updated_at = NOW() WHERE username = ANY($1)', [usernames]);
await client.query('COMMIT');
return { count: usernames.length };
} catch (e) {
await client.query('ROLLBACK');
console.error('disableGuestsFromYesterday error:', e?.message || e);
throw e;
} finally {
client.release();
}
}
export async function deleteUserFromDb(username) {
const client = await pool.connect();
try {