- Reorganized backend logic by moving API, authentication, Docker, status, and WebSocket handling into separate modules (api.js, auth.js, docker.js, status.js, websocket.js) within ./includes/ - Converted codebase to ES modules with import/export syntax for modern JavaScript - Updated index.js to serve as main entry point, importing from ./includes/ - Reduced code duplication and improved readability with modularized functions - Ensured full functionality preservation, including Docker stats and WebSocket communication - Updated README to reflect new folder structure and ES module setup
88 lines
3.2 KiB
JavaScript
88 lines
3.2 KiB
JavaScript
import unirest from 'unirest';
|
|
import { randomBytes } from 'crypto';
|
|
|
|
const temporaryLinks = new Map();
|
|
|
|
setInterval(() => {
|
|
const now = Date.now();
|
|
for (const [linkId, linkData] of temporaryLinks.entries()) {
|
|
if (linkData.expiresAt < now) temporaryLinks.delete(linkId);
|
|
}
|
|
}, parseInt(process.env.TEMP_LINKS_CLEANUP_INTERVAL_MS, 10));
|
|
|
|
export async function generateLoginLink(req, res) {
|
|
try {
|
|
const { secretKey, username } = req.body;
|
|
if (secretKey !== process.env.ADMIN_SECRET_KEY) return res.status(401).json({ error: 'Invalid secret key' });
|
|
if (!username) return res.status(400).json({ error: 'Username is required' });
|
|
|
|
const tokenResponse = await unirest
|
|
.post(process.env.AUTH_ENDPOINT)
|
|
.headers({ 'Accept': 'application/json', 'Content-Type': 'application/json' })
|
|
.send({ username, password: process.env.AUTH_PASSWORD });
|
|
|
|
if (!tokenResponse.body.token) return res.status(500).json({ error: 'Failed to generate API key' });
|
|
|
|
const apiKey = tokenResponse.body.token;
|
|
const linkId = randomBytes(parseInt(process.env.LINK_ID_BYTES, 10)).toString('hex');
|
|
const loginLink = `${process.env.AUTO_LOGIN_LINK_PREFIX}${linkId}`;
|
|
|
|
temporaryLinks.set(linkId, {
|
|
apiKey,
|
|
username,
|
|
expiresAt: Date.now() + parseInt(process.env.LINK_EXPIRY_SECONDS, 10) * 1000
|
|
});
|
|
|
|
setTimeout(() => temporaryLinks.delete(linkId), parseInt(process.env.LINK_EXPIRY_SECONDS, 10) * 1000);
|
|
res.json({ loginLink });
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
}
|
|
|
|
export function handleAutoLogin(req, res) {
|
|
const { linkId } = req.params;
|
|
const linkData = temporaryLinks.get(linkId);
|
|
|
|
if (!linkData || linkData.expiresAt < Date.now()) {
|
|
temporaryLinks.delete(linkId);
|
|
return res.send(`
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="refresh" content="3;url=${process.env.AUTO_LOGIN_REDIRECT_URL}">
|
|
<style>
|
|
body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #111827; font-family: 'Arial', sans-serif; }
|
|
.notification { background-color: #1f2937; color: white; padding: 16px; border-radius: 8px; display: flex; flex-direction: column; align-items: center; gap: 12px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); max-width: 400px; width: 100%; }
|
|
h1 { font-size: 2.25em; color: white; text-align: center; margin: 0; }
|
|
.spinner { border: 4px solid rgba(255, 255, 255, 0.3); border-top: 4px solid #ffffff; border-radius: 50%; width: 24px; height: 24px; animation: spin 1s linear infinite; }
|
|
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="notification">
|
|
<span class="spinner"></span>
|
|
<h1>Login Expired.</h1>
|
|
<h1>Redirecting...</h1>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`);
|
|
}
|
|
|
|
temporaryLinks.delete(linkId);
|
|
res.send(`
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>Auto Login</title>
|
|
<script>
|
|
localStorage.setItem('apiKey', '${linkData.apiKey}');
|
|
window.location.href = '/';
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<p>Logging in...</p>
|
|
</body>
|
|
</html>
|
|
`);
|
|
} |