diff --git a/src/routes/admin.js b/src/routes/admin.js new file mode 100644 index 0000000..c2ee4fc --- /dev/null +++ b/src/routes/admin.js @@ -0,0 +1,44 @@ +// src/routes/admin.js (or wherever your router is) +const express = require("express"); +const { validateToken, cleanupTokens } = require("../utils/adminToken"); +const HttpError = require("../utils/HttpError"); +const router = express.Router(); + +// Middleware to cleanup expired tokens periodically +router.use((req, res, next) => { + // Clean up expired tokens on each request (you might want to do this less frequently) + if (Math.random() < 0.1) { + // 10% chance to cleanup on each request + cleanupTokens(); + } + next(); +}); + +router.get("/:token", (req, res, next) => { + const { token } = req.params; + if (!token) { + return next(); + } + + // Validate the token before proceeding + if (!validateToken(token)) { + const error = new HttpError("Invalid or expired token", 401, { token }); + req.log.warn({ err: error, token }, "Token validation failed"); + return next(); // fail silently + } + + const scheme = req.protocol; + const host = req.get("host"); + const referrer = req.get("Referer") || req.get("Referrer") || ""; + + const rd = referrer.startsWith("http") + ? referrer + : `${scheme}://${host}${referrer}`; + + const adminLoginUrl = `${process.env.AUTH_LOGIN}${rd}`; + res.set("Content-Type", "text/html"); + res.render("pages/redirect", { adminLoginUrl }); + // res.redirect(301, adminLoginUrl); +}); + +module.exports = router; diff --git a/src/utils/adminToken.js b/src/utils/adminToken.js new file mode 100644 index 0000000..b24f19c --- /dev/null +++ b/src/utils/adminToken.js @@ -0,0 +1,75 @@ +// src/utils/adminToken.js +const crypto = require("crypto"); + +const TOKEN_TTL_MS = 10 * 60 * 1000; // 10 minutes +const preAuthTokens = new Map(); // token -> expiry timestamp (ms) + +function generateToken() { + const token = crypto.randomBytes(24).toString("base64url"); + const expiry = Date.now() + TOKEN_TTL_MS; + preAuthTokens.set(token, expiry); + return token; +} + +function validateToken(token) { + const expiry = preAuthTokens.get(token); + if (!expiry || expiry < Date.now()) { + // Remove expired token + preAuthTokens.delete(token); + return false; + } + return true; +} + +function revokeToken(token) { + return preAuthTokens.delete(token); +} + +function cleanupTokens() { + const now = Date.now(); + for (const [token, expiry] of preAuthTokens.entries()) { + if (expiry < now) { + preAuthTokens.delete(token); + } + } +} + +// Optional: Get token info for debugging +function getTokenInfo(token) { + const expiry = preAuthTokens.get(token); + if (!expiry) return null; + + return { + token, + expiresAt: new Date(expiry), + isExpired: expiry < Date.now(), + ttlMs: expiry - Date.now(), + }; +} + +// Optional: Get all active tokens (for admin/debugging) +function getAllTokens() { + const now = Date.now(); + const tokens = []; + + for (const [token, expiry] of preAuthTokens.entries()) { + tokens.push({ + token, + expiresAt: new Date(expiry), + isExpired: expiry < now, + ttlMs: expiry - now, + }); + } + + return tokens; +} + +module.exports = { + generateToken, + validateToken, + revokeToken, + cleanupTokens, + getTokenInfo, + getAllTokens, + TOKEN_TTL_MS, +}; diff --git a/src/utils/tokenCleanup.js b/src/utils/tokenCleanup.js new file mode 100644 index 0000000..8bdd5a4 --- /dev/null +++ b/src/utils/tokenCleanup.js @@ -0,0 +1,14 @@ +// src/utils/tokenCleanup.js +const { cleanupTokens } = require("./adminToken"); + +// Set up periodic cleanup (run every 5 minutes) +const CLEANUP_INTERVAL = 5 * 60 * 1000; // 5 minutes + +function startTokenCleanup() { + setInterval(() => { + cleanupTokens(); + console.log("Cleaned up expired pre-auth tokens"); + }, CLEANUP_INTERVAL); +} + +module.exports = { startTokenCleanup }; diff --git a/src/views/pages/redirect.handlebars b/src/views/pages/redirect.handlebars new file mode 100644 index 0000000..93a078a --- /dev/null +++ b/src/views/pages/redirect.handlebars @@ -0,0 +1,24 @@ + + + +
+ + +Please wait while we redirect you to the authentication service.
+If you are not redirected automatically, click here.
+