Skip to content

Commit 87649f2

Browse files
authored
Merge pull request #25 from RustyRory/dev
chore(deploy): 0.3.0
2 parents af912b4 + e8e6f91 commit 87649f2

7 files changed

Lines changed: 268 additions & 10 deletions

File tree

vps-monitor-app/api/server.js

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,67 @@
11
import 'dotenv/config';
22
import express from 'express';
3+
import session from 'express-session';
34
import { fileURLToPath } from 'url';
45
import { dirname, join } from 'path';
56
import { getContainers, restartContainer, stopContainer, startContainer } from './services/docker.js';
67
import { checkWebsites } from './services/http.js';
78

89
const __dirname = dirname(fileURLToPath(import.meta.url));
910

10-
const PORT = process.env.PORT || 3000;
11-
const BASE_URL = process.env.BASE_URL || `http://localhost:${PORT}`;
11+
const PORT = process.env.PORT || 3000;
12+
const BASE_URL = process.env.BASE_URL || `http://localhost:${PORT}`;
13+
const AUTH_USER = process.env.AUTH_USER || 'admin';
14+
const AUTH_PASS = process.env.AUTH_PASS || 'admin';
15+
const SESSION_SECRET = process.env.SESSION_SECRET || 'change-me';
1216

1317
const app = express();
1418

19+
app.use(express.json());
20+
app.use(session({
21+
secret: SESSION_SECRET,
22+
resave: false,
23+
saveUninitialized: false,
24+
cookie: { httpOnly: true, maxAge: 8 * 60 * 60 * 1000 },
25+
}));
26+
27+
// --- Auth routes (publiques) ---
28+
29+
app.post('/auth/login', (req, res) => {
30+
const { username, password } = req.body;
31+
if (username === AUTH_USER && password === AUTH_PASS) {
32+
req.session.authenticated = true;
33+
return res.json({ ok: true });
34+
}
35+
res.status(401).json({ error: 'Identifiants incorrects' });
36+
});
37+
38+
app.post('/auth/logout', (req, res) => {
39+
req.session.destroy();
40+
res.json({ ok: true });
41+
});
42+
43+
// --- Middleware auth ---
44+
45+
function requireAuth(req, res, next) {
46+
if (req.session.authenticated) return next();
47+
if (req.path.startsWith('/api/')) return res.status(401).json({ error: 'Non authentifié' });
48+
res.redirect('/login.html');
49+
}
50+
51+
// Fichiers statiques publics (login.html, style.css)
1552
app.use(express.static(join(__dirname, '../public')));
1653

17-
app.get('/api/status', async (req, res) => {
54+
// Toutes les routes suivantes sont protégées
55+
app.use(requireAuth);
56+
57+
// Redirige / vers index.html (déjà servi en statique, mais protégé via middleware)
58+
app.get('/', (_req, res) => {
59+
res.sendFile(join(__dirname, '../public/index.html'));
60+
});
61+
62+
// --- API ---
63+
64+
app.get('/api/status', async (_req, res) => {
1865
try {
1966
const [containers, websites] = await Promise.all([
2067
getContainers(),
@@ -34,8 +81,6 @@ app.get('/api/status', async (req, res) => {
3481
}
3582
});
3683

37-
app.use(express.json());
38-
3984
app.post('/api/container/restart', async (req, res) => {
4085
const { name } = req.body;
4186
if (!name) return res.status(400).json({ error: 'name requis' });

vps-monitor-app/api/server.test.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,45 @@ jest.unstable_mockModule('./services/http.js', () => ({
1818
const { default: app } = await import('./server.js');
1919
const { default: request } = await import('supertest');
2020

21+
const CREDENTIALS = { username: 'admin', password: 'admin' };
22+
23+
describe('Auth', () => {
24+
it('refuse sans session', async () => {
25+
const res = await request(app).get('/api/status');
26+
expect(res.statusCode).toBe(401);
27+
});
28+
29+
it('refuse avec mauvais identifiants', async () => {
30+
const res = await request(app)
31+
.post('/auth/login')
32+
.send({ username: 'admin', password: 'wrong' });
33+
expect(res.statusCode).toBe(401);
34+
});
35+
36+
it('accepte avec les bons identifiants', async () => {
37+
const res = await request(app).post('/auth/login').send(CREDENTIALS);
38+
expect(res.statusCode).toBe(200);
39+
});
40+
});
41+
2142
describe('GET /api/status', () => {
43+
let agent;
44+
45+
beforeEach(async () => {
46+
agent = request.agent(app);
47+
await agent.post('/auth/login').send(CREDENTIALS);
48+
});
49+
2250
it('répond 200 avec la structure attendue', async () => {
23-
const res = await request(app).get('/api/status');
51+
const res = await agent.get('/api/status');
2452
expect(res.statusCode).toBe(200);
2553
expect(res.body).toHaveProperty('containers');
2654
expect(res.body).toHaveProperty('websites');
2755
expect(res.body).toHaveProperty('globalStatus');
2856
});
2957

3058
it('globalStatus est OK si tous les containers tournent', async () => {
31-
const res = await request(app).get('/api/status');
59+
const res = await agent.get('/api/status');
3260
expect(res.body.globalStatus).toBe('OK');
3361
});
3462
});

vps-monitor-app/eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export default [
1616
},
1717
},
1818
rules: {
19-
'no-unused-vars': 'warn',
19+
'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
2020
'no-console': 'off',
2121
},
2222
},

vps-monitor-app/package-lock.json

Lines changed: 76 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vps-monitor-app/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
"dependencies": {
2020
"dockerode": "^5.0.0",
2121
"dotenv": "^17.4.2",
22-
"express": "^5.2.1"
22+
"express": "^5.2.1",
23+
"express-session": "^1.19.0"
2324
},
2425
"devDependencies": {
2526
"@eslint/js": "^10.0.1",

vps-monitor-app/public/login.html

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!DOCTYPE html>
2+
<html lang="fr">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>VPS Monitor — Login</title>
7+
<link rel="stylesheet" href="style.css" />
8+
</head>
9+
<body class="login-body">
10+
<div class="login-card">
11+
<h1>VPS Monitor</h1>
12+
<form id="login-form">
13+
<div id="login-error" class="login-error hidden">Identifiants incorrects</div>
14+
<input type="text" id="username" placeholder="Utilisateur" autocomplete="username" required />
15+
<input type="password" id="password" placeholder="Mot de passe" autocomplete="current-password" required />
16+
<button type="submit">Connexion</button>
17+
</form>
18+
</div>
19+
20+
<script>
21+
document.getElementById('login-form').addEventListener('submit', async (e) => {
22+
e.preventDefault();
23+
const username = document.getElementById('username').value;
24+
const password = document.getElementById('password').value;
25+
26+
const res = await fetch('/auth/login', {
27+
method: 'POST',
28+
headers: { 'Content-Type': 'application/json' },
29+
body: JSON.stringify({ username, password }),
30+
});
31+
32+
if (res.ok) {
33+
window.location.href = '/';
34+
} else {
35+
document.getElementById('login-error').classList.remove('hidden');
36+
}
37+
});
38+
</script>
39+
</body>
40+
</html>

vps-monitor-app/public/style.css

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,72 @@ section {
9696
}
9797

9898
.dot.ok { background: #4caf50; }
99+
100+
/* Login */
101+
.login-body {
102+
display: flex;
103+
align-items: center;
104+
justify-content: center;
105+
min-height: 100vh;
106+
padding: 0;
107+
}
108+
109+
.login-card {
110+
background: #1a1a1a;
111+
border: 1px solid #2a2a2a;
112+
border-radius: 8px;
113+
padding: 2rem;
114+
width: 100%;
115+
max-width: 320px;
116+
display: flex;
117+
flex-direction: column;
118+
gap: 1.2rem;
119+
}
120+
121+
.login-card h1 {
122+
text-align: center;
123+
font-size: 1.2rem;
124+
color: #e0e0e0;
125+
}
126+
127+
.login-card input {
128+
width: 100%;
129+
padding: 0.6rem 0.8rem;
130+
background: #0f0f0f;
131+
border: 1px solid #333;
132+
border-radius: 4px;
133+
color: #e0e0e0;
134+
font-family: monospace;
135+
font-size: 0.9rem;
136+
display: block;
137+
margin-bottom: 0.6rem;
138+
}
139+
140+
.login-card input:focus {
141+
outline: none;
142+
border-color: #555;
143+
}
144+
145+
.login-card button {
146+
width: 100%;
147+
padding: 0.6rem;
148+
background: #2a2a2a;
149+
border: 1px solid #444;
150+
border-radius: 4px;
151+
color: #e0e0e0;
152+
font-family: monospace;
153+
font-size: 0.9rem;
154+
cursor: pointer;
155+
}
156+
157+
.login-card button:hover {
158+
background: #333;
159+
}
160+
161+
.login-error {
162+
color: #f44336;
163+
font-size: 0.82rem;
164+
text-align: center;
165+
}
166+
167+
.hidden { display: none; }

0 commit comments

Comments
 (0)