first commit

This commit is contained in:
MaddoScientisto 2026-03-14 20:04:39 +01:00
commit 4d332ef662
27586 changed files with 3281783 additions and 0 deletions

View file

@ -0,0 +1,18 @@
# ─────────────────────────────────────────────────────────────────────────────
# Copy this file to .env and fill in the values for your environment.
# ─────────────────────────────────────────────────────────────────────────────
# Port this Node app listens on
PORT=3001
# Public URL prefix under which the app is mounted in the reverse proxy
# Must match the Nginx location block (e.g. location /face_match { ... })
PUBLIC_BASE=/face_match
# Internal (loopback) URL of the main Tomcat/Java application.
# Used for the back-channel session validation call.
# Never expose checkSession.jsp directly to the internet.
JAVA_APP_INTERNAL_URL=http://localhost:8080
# Public URL of the main app login page unauthenticated users are redirected here
LOGIN_URL=https://www.regalamiunsorriso.it/admin/menu/Menu4.abl

2
rus/facematch/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules/
.env

101
rus/facematch/README.md Normal file
View file

@ -0,0 +1,101 @@
# facematch
Node.js/Express micro-app for the **Ricerca Facciale** feature on [regalamiunsorriso.it](https://www.regalamiunsorriso.it).
It shares authentication with the main Java/Tomcat app by forwarding the browser's `JSESSIONID` cookie to a lightweight JSP validation endpoint (`/admin/pg/checkSession.jsp`).
---
## How authentication works
```
Browser Nginx Node app (this) Tomcat (Java app)
│ │ │ │
│── GET /face_match ──────►│ │ │
│ Cookie: JSESSIONID=X │── proxy_pass ──────────►│ │
│ │ │── GET /admin/pg/ │
│ │ │ checkSession.jsp ──►│
│ │ │ Cookie: JSESSIONID=X│
│ │ │◄── 200 {userId:42} ───│
│◄── 200 face-match page ─┤◄────────────────────────│ │
```
- The Java app stores sessions server-side; the browser carries only the `JSESSIONID` cookie.
- Because both apps are under the same public domain, the browser sends `JSESSIONID` to the Node app too.
- The Node app validates the cookie via a back-channel HTTP call (Tomcat ↔ Node, same host, loopback).
- If the session is invalid/absent, the user is redirected to the main app login page.
---
## Project structure
```
facematch/
├── server.js Express entry point
├── views/
│ └── index.ejs Protected face-match page
├── .env.example Environment variable template
├── package.json
└── README.md
```
The complementary JSP file lives in the main app:
```
admin/pg/checkSession.jsp Returns {"authenticated":true,"userId":N} or 401
```
---
## Setup
```bash
cd facematch
npm install
cp .env.example .env
# edit .env as needed
npm start
```
---
## Environment variables
| Variable | Default | Description |
|---|---|---|
| `PORT` | `3001` | Port this app listens on |
| `PUBLIC_BASE` | `/face_match` | URL prefix in the reverse proxy |
| `JAVA_APP_INTERNAL_URL` | `http://localhost:8080` | Internal URL of the Tomcat app |
| `LOGIN_URL` | `https://www.regalamiunsorriso.it/admin/menu/Menu4.abl` | Login redirect target |
---
## Nginx configuration snippet
Add inside your existing `server {}` block:
```nginx
# Node facematch app
location /face_match {
proxy_pass http://127.0.0.1:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
# Protect the session validation endpoint block external access
location = /admin/pg/checkSession.jsp {
allow 127.0.0.1;
deny all;
# continue to Tomcat proxy as normal
proxy_pass http://127.0.0.1:8080;
}
```
> **Important**: the `deny all` rule for `checkSession.jsp` ensures only the Node app
> (running on the same host) can call the session validation endpoint.

View file

@ -0,0 +1,20 @@
{
"name": "facematch",
"version": "1.0.0",
"description": "Face Match module for Regalami un Sorriso shares auth session with the main Java app",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"dependencies": {
"cookie-parser": "^1.4.6",
"dotenv": "^16.4.5",
"ejs": "^3.1.10",
"express": "^4.19.2",
"node-fetch": "^2.7.0"
},
"devDependencies": {
"nodemon": "^3.1.4"
}
}

115
rus/facematch/server.js Normal file
View file

@ -0,0 +1,115 @@
'use strict';
require('dotenv').config();
const express = require('express');
const cookieParser = require('cookie-parser');
const fetch = require('node-fetch');
const path = require('path');
const app = express();
// ─── Configuration ────────────────────────────────────────────────────────────
// PUBLIC_BASE the public-facing prefix behind which this app is mounted
// e.g. if Nginx proxies /face_match → this app, set PUBLIC_BASE=/face_match
// JAVA_APP_INTERNAL_URL internal (loopback) URL to reach the Tomcat app
// e.g. http://localhost:8080
// LOGIN_URL where to redirect unauthenticated users (public URL of the login)
const PUBLIC_BASE = (process.env.PUBLIC_BASE || '/face_match').replace(/\/$/, '');
const JAVA_INTERNAL_URL = (process.env.JAVA_APP_INTERNAL_URL || 'http://localhost:8080').replace(/\/$/, '');
const LOGIN_URL = process.env.LOGIN_URL || 'https://www.regalamiunsorriso.it/admin/menu/Menu4.abl';
const PORT = process.env.PORT || 3001;
// ─── Middleware ───────────────────────────────────────────────────────────────
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));
app.use(cookieParser());
// Strip the public base prefix so routes work both mounted and standalone
app.use((req, _res, next) => {
if (PUBLIC_BASE && req.path.startsWith(PUBLIC_BASE)) {
req.url = req.url.slice(PUBLIC_BASE.length) || '/';
}
next();
});
// ─── Session validation helper ────────────────────────────────────────────────
/**
* Calls the lightweight JSP endpoint on the main Java app, forwarding the
* browser's JSESSIONID cookie. Returns { authenticated, userId } or throws.
*
* The Java app stores the session server-side; both apps are on the same host
* so the JSESSIONID issued by Tomcat is forwarded here by the browser.
*/
async function checkJavaSession(jsessionId) {
if (!jsessionId) return { authenticated: false };
const url = `${JAVA_INTERNAL_URL}/admin/pg/checkSession.jsp`;
const response = await fetch(url, {
method: 'GET',
headers: {
Cookie: `JSESSIONID=${jsessionId}`,
},
// Don't follow redirects a redirect usually means the session expired
redirect: 'manual',
});
if (response.status === 401 || response.status >= 300) {
return { authenticated: false };
}
try {
return await response.json(); // { authenticated, userId }
} catch {
return { authenticated: false };
}
}
// ─── Auth middleware ──────────────────────────────────────────────────────────
async function requireAuth(req, res, next) {
const jsessionId = req.cookies['JSESSIONID'];
let sessionInfo;
try {
sessionInfo = await checkJavaSession(jsessionId);
} catch (err) {
console.error('Session validation request failed:', err.message);
sessionInfo = { authenticated: false };
}
if (!sessionInfo.authenticated) {
// Redirect to the main app login, passing the desired return URL
const returnUrl = encodeURIComponent(`https://www.regalamiunsorriso.it${PUBLIC_BASE}${req.path}`);
return res.redirect(`${LOGIN_URL}?returnUrl=${returnUrl}`);
}
// Attach session info for use in route handlers / views
req.sessionInfo = sessionInfo;
next();
}
// ─── Routes ───────────────────────────────────────────────────────────────────
// Main entry point protected
app.get('/', requireAuth, (req, res) => {
res.render('index', {
userId: req.sessionInfo.userId,
publicBase: PUBLIC_BASE,
});
});
// Health-check unprotected (used by reverse proxy / monitoring)
app.get('/health', (_req, res) => {
res.json({ status: 'ok' });
});
// Catch-all 404
app.use((_req, res) => {
res.status(404).send('Not found');
});
// ─── Start ────────────────────────────────────────────────────────────────────
app.listen(PORT, () => {
console.log(`facematch app listening on port ${PORT} (base: ${PUBLIC_BASE})`);
});

View file

@ -0,0 +1,93 @@
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ricerca Facciale Regalami un Sorriso</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Helvetica Neue", Arial, sans-serif;
background: #f4f6f8;
color: #333;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.card {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,.12);
padding: 2.5rem 3rem;
max-width: 480px;
width: 90%;
text-align: center;
}
.badge {
display: inline-block;
background: #28a745;
color: #fff;
font-size: .75rem;
font-weight: 700;
letter-spacing: .05em;
text-transform: uppercase;
border-radius: 50px;
padding: .25rem .75rem;
margin-bottom: 1.25rem;
}
h1 {
font-size: 1.6rem;
margin-bottom: .75rem;
color: #222;
}
p {
font-size: 1rem;
color: #555;
line-height: 1.55;
margin-bottom: 1rem;
}
.user-info {
background: #eaf4ea;
border: 1px solid #b8dcb8;
border-radius: 6px;
padding: .75rem 1rem;
font-size: .9rem;
color: #2d6a2d;
margin-top: 1.5rem;
}
.back-link {
display: inline-block;
margin-top: 1.75rem;
font-size: .9rem;
color: #0069d9;
text-decoration: none;
}
.back-link:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="card">
<span class="badge">&#10003; Autenticato</span>
<h1>Ricerca Facciale</h1>
<p>
Sessione verificata correttamente con l'applicazione principale.
Il modulo di ricerca facciale è in fase di sviluppo.
</p>
<div class="user-info">
ID utente in sessione: <strong><%= userId %></strong>
</div>
<a class="back-link" href="javascript:history.back()">&#8592; Torna indietro</a>
</div>
</body>
</html>