Sync from GitHub via hub-sync
Browse files
server.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
| 1 |
import express from 'express';
|
| 2 |
import path from 'path';
|
|
|
|
| 3 |
import { fileURLToPath } from 'url';
|
| 4 |
import dotenv from 'dotenv';
|
| 5 |
|
|
@@ -898,6 +899,93 @@ async function startServer() {
|
|
| 898 |
next();
|
| 899 |
});
|
| 900 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 901 |
// API: Search Odoo + PrestaShop contacts
|
| 902 |
app.get('/api/search', async (req, res) => {
|
| 903 |
const phone = String(req.query.phone || '');
|
|
|
|
| 1 |
import express from 'express';
|
| 2 |
import path from 'path';
|
| 3 |
+
import crypto from 'crypto';
|
| 4 |
import { fileURLToPath } from 'url';
|
| 5 |
import dotenv from 'dotenv';
|
| 6 |
|
|
|
|
| 899 |
next();
|
| 900 |
});
|
| 901 |
|
| 902 |
+
// ==========================================
|
| 903 |
+
// PROTECTION PAR MOT DE PASSE PARTAGE
|
| 904 |
+
// ==========================================
|
| 905 |
+
const APP_PASSWORD = process.env.APP_PASSWORD || '';
|
| 906 |
+
const AUTH_SECRET = crypto
|
| 907 |
+
.createHash('sha256')
|
| 908 |
+
.update('tousergo-crm-' + APP_PASSWORD)
|
| 909 |
+
.digest('hex');
|
| 910 |
+
const AUTH_COOKIE_NAME = 'crm_auth';
|
| 911 |
+
|
| 912 |
+
function parseCookies(req: express.Request): Record<string, string> {
|
| 913 |
+
const header = req.headers.cookie;
|
| 914 |
+
const out: Record<string, string> = {};
|
| 915 |
+
if (!header) return out;
|
| 916 |
+
header.split(';').forEach((part) => {
|
| 917 |
+
const idx = part.indexOf('=');
|
| 918 |
+
if (idx === -1) return;
|
| 919 |
+
const key = part.slice(0, idx).trim();
|
| 920 |
+
const value = part.slice(idx + 1).trim();
|
| 921 |
+
out[key] = decodeURIComponent(value);
|
| 922 |
+
});
|
| 923 |
+
return out;
|
| 924 |
+
}
|
| 925 |
+
|
| 926 |
+
function isAuthenticated(req: express.Request): boolean {
|
| 927 |
+
if (!APP_PASSWORD) return true; // si aucun mot de passe configure, ne bloque rien
|
| 928 |
+
const cookies = parseCookies(req);
|
| 929 |
+
return cookies[AUTH_COOKIE_NAME] === AUTH_SECRET;
|
| 930 |
+
}
|
| 931 |
+
|
| 932 |
+
app.use(express.urlencoded({ extended: true }));
|
| 933 |
+
|
| 934 |
+
app.post('/login', (req, res) => {
|
| 935 |
+
const submitted = String(req.body?.password || '');
|
| 936 |
+
const redirectTo = String(req.body?.redirect || '/');
|
| 937 |
+
if (APP_PASSWORD && submitted === APP_PASSWORD) {
|
| 938 |
+
res.setHeader(
|
| 939 |
+
'Set-Cookie',
|
| 940 |
+
`${AUTH_COOKIE_NAME}=${encodeURIComponent(AUTH_SECRET)}; Path=/; Max-Age=2592000; HttpOnly; SameSite=Lax`
|
| 941 |
+
);
|
| 942 |
+
return res.redirect(302, redirectTo || '/');
|
| 943 |
+
}
|
| 944 |
+
return res.send(renderLoginPage(redirectTo, true));
|
| 945 |
+
});
|
| 946 |
+
|
| 947 |
+
function renderLoginPage(redirectTo: string, showError: boolean): string {
|
| 948 |
+
return `<!DOCTYPE html>
|
| 949 |
+
<html lang="fr">
|
| 950 |
+
<head>
|
| 951 |
+
<meta charset="UTF-8" />
|
| 952 |
+
<title>CRM Levee de Fiche - Connexion</title>
|
| 953 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 954 |
+
<style>
|
| 955 |
+
body { font-family: -apple-system, sans-serif; background: #f1f5f9; display: flex; align-items: center; justify-content: center; height: 100vh; margin: 0; }
|
| 956 |
+
.box { background: white; padding: 32px; border-radius: 16px; box-shadow: 0 4px 16px rgba(0,0,0,0.08); width: 320px; }
|
| 957 |
+
h1 { font-size: 16px; margin: 0 0 16px; color: #26295A; }
|
| 958 |
+
input { width: 100%; padding: 10px; border: 1px solid #cbd5e1; border-radius: 8px; box-sizing: border-box; margin-bottom: 12px; font-size: 14px; }
|
| 959 |
+
button { width: 100%; padding: 10px; background: #B63E19; color: white; border: none; border-radius: 8px; font-weight: bold; cursor: pointer; }
|
| 960 |
+
.error { color: #B63E19; font-size: 12px; margin-bottom: 12px; }
|
| 961 |
+
</style>
|
| 962 |
+
</head>
|
| 963 |
+
<body>
|
| 964 |
+
<div class="box">
|
| 965 |
+
<h1>CRM Service Client - Acces protege</h1>
|
| 966 |
+
${showError ? '<div class="error">Mot de passe incorrect.</div>' : ''}
|
| 967 |
+
<form method="POST" action="/login">
|
| 968 |
+
<input type="hidden" name="redirect" value="${redirectTo.replace(/"/g, '"')}" />
|
| 969 |
+
<input type="password" name="password" placeholder="Mot de passe" autofocus />
|
| 970 |
+
<button type="submit">Se connecter</button>
|
| 971 |
+
</form>
|
| 972 |
+
</div>
|
| 973 |
+
</body>
|
| 974 |
+
</html>`;
|
| 975 |
+
}
|
| 976 |
+
|
| 977 |
+
app.use((req, res, next) => {
|
| 978 |
+
if (req.path === '/login') return next();
|
| 979 |
+
if (isAuthenticated(req)) return next();
|
| 980 |
+
|
| 981 |
+
if (req.path.startsWith('/api/')) {
|
| 982 |
+
return res.status(401).json({ error: 'Non authentifie' });
|
| 983 |
+
}
|
| 984 |
+
|
| 985 |
+
const originalUrl = req.originalUrl || '/';
|
| 986 |
+
return res.send(renderLoginPage(originalUrl, false));
|
| 987 |
+
});
|
| 988 |
+
|
| 989 |
// API: Search Odoo + PrestaShop contacts
|
| 990 |
app.get('/api/search', async (req, res) => {
|
| 991 |
const phone = String(req.query.phone || '');
|