first commit
This commit is contained in:
commit
4d332ef662
27586 changed files with 3281783 additions and 0 deletions
18
rus/facematch/.env.example
Normal file
18
rus/facematch/.env.example
Normal 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
2
rus/facematch/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
.env
|
||||
101
rus/facematch/README.md
Normal file
101
rus/facematch/README.md
Normal 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.
|
||||
20
rus/facematch/package.json
Normal file
20
rus/facematch/package.json
Normal 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
115
rus/facematch/server.js
Normal 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})`);
|
||||
});
|
||||
93
rus/facematch/views/index.ejs
Normal file
93
rus/facematch/views/index.ejs
Normal 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">✓ 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()">← Torna indietro</a>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue