Compare commits

...

2 commits

Author SHA1 Message Date
bb60201ad4 Enhance FaceAI integration with live checks and metadata handling
All checks were successful
Publish FaceAI Container / publish (push) Successful in 3m22s
- Added optional live FaceAI checks in README.md
- Implemented relative storage segment parsing in race-storage.js
- Updated server.js to include relative directory in race storage
- Refactored legacyAssets.js to resolve asset base URL dynamically
- Expanded live race tests to validate FaceAI app launch and metadata
- Introduced portrait image handling for live upload flow
- Updated faceai_handoff.php to process race storage relative directory
2026-04-19 10:38:32 +02:00
c88b373c73 Add Playwright tests for live site authentication and race page loading
- Introduced `auth.setup.js` to handle authentication against the live site and store the session state.
- Created `live-race.spec.js` to test loading a live race page with an authenticated session, including cookie validation.
- Added utility functions in `live-site-test-utils.js` for managing authentication, dismissing cookie banners, and checking UI states.
- Included a temporary JSON file for live state inspection.
- Updated deployment manifest to reflect new and modified files.
- Implemented `_inc_faceai_identity.jsp` for managing FaceAI identity cookies and included it in relevant JSP files.
- Added language management JavaScript in `lang.js`.
- Adjusted `fotoCR-en.jsp` and `fotoCR.jsp` to include the FaceAI identity logic.
- Created a tarball for staging deployment.
2026-04-19 10:26:34 +02:00
29 changed files with 4554 additions and 18 deletions

View file

@ -102,6 +102,7 @@ code example here
- **Include Examples**: Real code snippets are more effective than descriptions
- **Stay Current**: Reference current versions and best practices
- **Link Resources**: Include official documentation and authoritative sources
- **Capture validated shell quirks**: For environment-specific instruction files, record proven terminal behaviors, quoting pitfalls, failing command patterns, and the known-good command form that replaced them
### Instruction Altitude (Goldilocks Zone)

View file

@ -35,6 +35,12 @@ If you need a single elevated command:
ssh -tt -i C:\Users\Maddo\.ssh\id_rsa -p 410 marco@83.149.164.4 "sudo tcsh -c 'command here'"
```
From PowerShell on Windows, prefer invoking the SSH binary directly instead of wrapping it in `cmd /c`:
```powershell
& 'C:\Windows\System32\OpenSSH\ssh.exe' -tt -i 'C:\Users\Maddo\.ssh\id_rsa' -p 410 'marco@83.149.164.4'
```
## Shell Behavior On This Host
- The remote login shell behaves as `tcsh`.
@ -42,11 +48,15 @@ ssh -tt -i C:\Users\Maddo\.ssh\id_rsa -p 410 marco@83.149.164.4 "sudo tcsh -c 'c
- The server `sh` does not support `-l`, so use `sh -c`, not `sh -lc`.
- `tcsh` treats redirection and pipelines differently from POSIX shells; commands like `find ... 2>/dev/null | head` can fail with `Ambiguous output redirect` unless the whole payload runs under `sh -c`.
- Prefer one remote command per SSH invocation when doing reconnaissance. Complex commands with pipes, grouped expressions, or escaped parentheses are much more likely to break under PowerShell-to-SSH-to-`tcsh` quoting.
- On Windows PowerShell, avoid `cmd /c "ssh ..."` and `cmd /c "scp ..."` wrappers for anything nontrivial. Nested quoting can collapse before SSH runs and spill later tokens into the local PowerShell session, which leads to misleading local errors such as `sudo: The term 'sudo' is not recognized` or local attempts to run `cksum`.
- Prefer the PowerShell call operator form `& 'C:\Windows\System32\OpenSSH\ssh.exe' ...` and pass the remote command as a single argument when you must stay non-interactive.
- If PowerShell shows the continuation prompt `? >`, the command was malformed locally before SSH executed it. Cancel it and rerun a simpler command instead of trying to answer the prompt.
- If `sudo` reports that a terminal is required, reconnect with `-tt`.
- When running remote commands from PowerShell, quoting can break if the command contains both nested quotes and file paths with spaces.
- For read-only verification commands from PowerShell, prefer `ssh ... --% <remote command>` so the remote command is passed verbatim.
- For `promote-file.sh` calls that target paths with spaces, prefer a local PowerShell loop that passes the full remote command as a single SSH argument instead of building one long nested quoted command.
- For multi-step privileged work, prefer opening one interactive SSH session, then running `sudo tcsh`, then issuing commands sequentially inside that shell. This is more reliable than trying to encode several `sudo tcsh -c 'a ; b ; c'` operations through PowerShell quoting.
- In an interactive `tcsh` root shell, do not re-send a password or any other text starting with `!` after the password prompt has already succeeded. `tcsh` interprets `!` as history expansion and will emit `Event not found`.
- If repeated SSH commands start cancelling or interleaving poorly in the same terminal, rerun them sequentially instead of in parallel.
## Mail Template Runtime Notes
@ -88,6 +98,14 @@ ssh -tt -i C:\Users\Maddo\.ssh\id_rsa -p 410 marco@83.149.164.4 "sudo tcsh -c 'c
- Incoming staging root: `/home/marco/regalamiunsorriso/incoming/www`
- Live site root: `/home/sites/regalamiunsorriso/www`
## Tomcat Logs And Runtime Clues
- The active Tomcat installation on this host is under `/usr/local/apache-tomcat-9.0`.
- The most useful live runtime log is `/usr/local/apache-tomcat-9.0/logs/catalina.out`.
- Rotated Tomcat logs are under `/usr/local/apache-tomcat-9.0/logs/`, including files such as `catalina.YYYY-MM-DD.log` and `localhost.YYYY-MM-DD.log`.
- Access to generated JSP work files under `/usr/local/apache-tomcat-9.0/work` may require root.
- A broken JSP on this host can still return `HTTP 200` with a visibly truncated HTML body instead of a clean 500 response; when that happens, fetch part of the page body with `curl -L <url> | head -n ...` and compare the cutoff point with recent `catalina.out` output.
## Staging Workflow
When `www/**` files need deployment:
@ -103,6 +121,9 @@ Example staging command pattern:
tar -cf - -C K:\various\regalamiunsorriso <file-list-under-www> | ssh -i C:\Users\Maddo\.ssh\id_rsa -p 410 marco@83.149.164.4 "tar -xf - -C /home/marco/regalamiunsorriso/incoming"
```
- The streamed tar extraction into `/home/marco/regalamiunsorriso/incoming` works as the unprivileged `marco` user and avoids the permission problems seen when uploading an archive and trying to unpack it with `sudo tar`.
- Do not rely on `sudo tar` for staging on this host. `marco` is not permitted to run that extraction as root.
## Promotion Rules
- Promotion to the live site must happen through `sudo tcsh`.
@ -136,6 +157,13 @@ $remote = "sudo tcsh -c \"/home/marco/promote-file.sh '<staged-path>' '<live-pat
& ssh -tt -i 'C:\Users\Maddo\.ssh\id_rsa' -p 410 'marco@83.149.164.4' $remote
```
If the deployment needs more than one privileged action or may prompt for a password, prefer this sequence instead of packing everything into one quoted SSH command:
1. Open an interactive SSH session with `-tt`.
2. Run `sudo tcsh`.
3. Run `/home/marco/promote-file.sh ...` commands one at a time.
4. Run `ls -l`, `stat -f`, and `cksum` in that same root shell.
Behavior of `promote-file.sh`:
- If the destination already exists, it copies the file and restores that destination file's original owner, group, and mode.

View file

@ -14,3 +14,8 @@ FACEAI_UPLOAD_ROOT=/data/runtime/uploads
FACEAI_LOG_ROOT=/data/logs
FACEAI_PKL_ROOT=/data/pkl
FACEAI_MATCHER_BINARY=/opt/face-recognition/face_matcher
LIVE_SITE_BASE_URL=https://www.regalamiunsorriso.it
LIVE_SITE_LOGIN_URL=https://www.regalamiunsorriso.it/login_clienti-it.html
LIVE_SITE_RACE_URL=https://www.regalamiunsorriso.it/42%20HALF%20MARATHON%20FIRENZE_gara-1018545---96-1.html
LIVE_SITE_USERNAME=
LIVE_SITE_PASSWORD=

1
faceai/.gitignore vendored
View file

@ -3,3 +3,4 @@ apps/frontend/dist/
.env
playwright-report/
test-results/
tests/live-site/.auth/

View file

@ -172,6 +172,50 @@ If you want to keep the local containers running after the test for manual inspe
FACEAI_E2E_KEEP_STACK=1
```
## Live Site Playwright Checks
The `faceai/` workspace now also includes a separate Playwright project for the live site. It is isolated from the Docker-backed simulator suite and is intended to verify that production login still works and that a real race page loads correctly after authentication.
Set these environment variables before running it:
```bash
LIVE_SITE_BASE_URL=https://www.regalamiunsorriso.it
LIVE_SITE_LOGIN_URL=https://www.regalamiunsorriso.it/login_clienti-it.html
LIVE_SITE_RACE_URL=https://www.regalamiunsorriso.it/42%20HALF%20MARATHON%20FIRENZE_gara-1018545---96-1.html
LIVE_SITE_USERNAME=your-login
LIVE_SITE_PASSWORD=your-password
```
Then run:
```bash
npm run test:live:install
npm run test:live
```
What it does:
- opens the live login page
- signs in with the supplied credentials
- persists authenticated Playwright storage state under `tests/live-site/.auth/user.json`
- opens the configured live race URL
- verifies the account UI is present and the race search form renders correctly
Optional live FaceAI checks can also be enabled with:
```bash
LIVE_FACEAI_BASE_URL=https://ai.regalamiunsorriso.it
LIVE_SITE_PORTRAIT_PATH=../test_pkl/live/test_portrait_1.png
LIVE_SITE_RUN_UPLOAD_FLOW=1
```
When enabled, the live suite also:
- validates that the legacy Face ID handoff URL includes the race storage metadata expected by FaceAI
- opens the real FaceAI app and asserts that the legacy header stylesheets load from the live legacy site
- confirms the app does not emit the `MISSING_RACE_STORAGE` invalid-race error on launch
- uploads the supplied portrait image and verifies that search creation succeeds
## Optional Backend And Frontend Dev Loop
If you only want to iterate on the app without the PHP simulator, you can still run the public site and the processor separately. The queue-backed flow now requires Redis and the processor, so `npm run dev` alone is no longer the full stack.

1116
faceai/anonymous_output.html Normal file

File diff suppressed because it is too large Load diff

View file

@ -30,6 +30,20 @@ function sanitizePathSegment(value) {
return normalized;
}
function parseRelativeStorageSegments(value) {
const normalized = String(value || '').trim().replace(/\\/g, '/');
if (!normalized) {
return [];
}
return normalized
.split('/')
.map((segment) => segment.trim())
.filter(Boolean)
.map((segment) => sanitizePathSegment(segment));
}
export function normalizeRaceFolderName(value) {
return String(value || '')
.trim()
@ -49,9 +63,16 @@ export function buildMonthFolder(year, monthIndex) {
}
export function buildRaceStorage(storageInput = {}) {
const year = sanitizePathSegment(storageInput.year);
const monthFolder = sanitizePathSegment(storageInput.monthFolder);
const raceFolder = sanitizePathSegment(normalizeRaceFolderName(storageInput.raceFolder));
const relativeSegments = parseRelativeStorageSegments(storageInput.relativeDir);
const relativeYear = relativeSegments[0] || '';
const relativeMonthFolder = relativeSegments.length >= 3 ? relativeSegments[1] : '';
const relativeRaceFolder = relativeSegments.length >= 3
? relativeSegments[2]
: (relativeSegments.length >= 2 ? relativeSegments[1] : '');
const year = sanitizePathSegment(storageInput.year || relativeYear);
const monthFolder = sanitizePathSegment(storageInput.monthFolder || relativeMonthFolder);
const raceFolder = sanitizePathSegment(normalizeRaceFolderName(storageInput.raceFolder || relativeRaceFolder));
if (!year || !monthFolder || !raceFolder) {
return null;

View file

@ -262,7 +262,8 @@ app.get('/dev/legacy/launch', (req, res) => {
raceStorage: {
year: String(req.query.raceYear || mockCatalog[raceId]?.storage?.year || ''),
monthFolder: String(req.query.raceMonthFolder || mockCatalog[raceId]?.storage?.monthFolder || ''),
raceFolder: String(req.query.raceFolder || mockCatalog[raceId]?.storage?.raceFolder || '')
raceFolder: String(req.query.raceFolder || mockCatalog[raceId]?.storage?.raceFolder || ''),
relativeDir: String(req.query.raceStorageRelativeDir || '')
},
lang,
returnUrl

View file

@ -1,4 +1,33 @@
const legacyAssetBaseUrl = (import.meta.env.VITE_LEGACY_ASSET_BASE_URL || '/legacy-static').replace(/\/$/, '');
import { getLegacyBaseUrl } from './legacyUrls.js';
const localHostnames = new Set(['localhost', '127.0.0.1', '::1']);
function trimTrailingSlash(value) {
return String(value || '').replace(/\/$/, '');
}
function currentHostname() {
if (typeof window === 'undefined' || !window.location || !window.location.hostname) {
return '';
}
return window.location.hostname.toLowerCase();
}
function resolveLegacyAssetBaseUrl() {
const configuredAssetBaseUrl = trimTrailingSlash(import.meta.env.VITE_LEGACY_ASSET_BASE_URL || '');
if (configuredAssetBaseUrl) {
return configuredAssetBaseUrl;
}
if (localHostnames.has(currentHostname())) {
return '/legacy-static';
}
return getLegacyBaseUrl();
}
const legacyAssetBaseUrl = resolveLegacyAssetBaseUrl();
export function legacyAsset(path) {
return `${legacyAssetBaseUrl}${path.startsWith('/') ? path : `/${path}`}`;

File diff suppressed because it is too large Load diff

0
faceai/headers.txt Normal file
View file

359
faceai/login_response.html Normal file
View file

@ -0,0 +1,359 @@
<!DOCTYPE html>
<html lang="en"><!-- InstanceBegin template="/Templates/rus.dwt" codeOutsideHTMLIsLocked="false" -->
<head>
<!-- _inc_lang.jsp -->
<script>var webApp='';</script>
<script src="_js/lang.js"></script>
<input type="hidden" name="lang" id="lang" value="en" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<!-- [favicon] begin -->
<link rel="shortcut icon" type="image/x-icon" href="images/favicon.ico"/>
<link rel="icon" type="image/x-icon" href="images/favicon.ico" />
<!-- [favicon] end -->
<!-- JSP -->
<!-- InstanceBeginEditable name="Bean" -->
<!-- InstanceEndEditable -->
<!-- InstanceBeginEditable name="doctitle" -->
<title>Regalami Un Sorriso ETS - User Area</title>
<!-- InstanceEndEditable -->
<!-- Bootstrap core CSS -->
<link href="vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<!-- FontAwesome Icons -->
<link rel="stylesheet" href="css/font-awesome.min.css">
<!-- Roboto Font -->
<link href="https://fonts.googleapis.com/css?family=Roboto:300,300i,400,400i,700,700i" rel="stylesheet">
<!-- Custom styles for this template -->
<link href="css/custom-style.css" rel="stylesheet">
<!-- Css Datepicker -->
<link href="addons/datepicker/css/bootstrap-datepicker.standalone.min.css" rel="stylesheet">
<link rel="stylesheet" href='admin/_V4/_css/ajaxLoading.css'>
<!-- InstanceBeginEditable name="head" -->
<!-- InstanceEndEditable -->
<!-- _inc_cookie.jsp -->
<!-- Begin Cookie Consent plugin by Silktide - http://silktide.com/cookieconsent -->
<script type="text/javascript">
window.cookieconsent_options = {
"message": "This website or the third-party tools used here make use of cookies necessary for its operation and useful for the purposes described in the cookie policy. To learn more or refuse consent to all or some cookies, please refer to the cookie policy. By closing this banner, scrolling this page, clicking on a link, or continuing to browse otherwise, you consent to the use of cookies.",
"dismiss": "Accept",
"learnMore": "Cookie Policy",
"link": "https://www.regalamiunsorriso.it/privacy-en.html",
"theme": "dark-bottom"
};
</script>
<script type="text/javascript" src="js/cookieconsent.min.js"></script>
<!-- End Cookie Consent plugin -->
<!-- _inc_head.jsp -->
<!-- Matomo Tag Manager -->
<script>
var _mtm = window._mtm = window._mtm || [];
_mtm.push({'mtm.startTime': (new Date().getTime()), 'event': 'mtm.Start'});
(function() {
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.async=true; g.src='https://matomo.acxent.it/js/container_TKh2j1eF.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Matomo Tag Manager -->
</head>
<body>
<!-- Page Content -->
<!-- InstanceBeginEditable name="main" -->
<input name="id_tipoSel" type="hidden" id="id_tipoSel" value="0">
<!-- facebook init script-->
<div id="fb-root"></div>
<script>
window.fbAsyncInit = function() {
FB.init({
appId : '5858950737524161',
autoLogAppEvents : true,
xfbml : true,
version : 'v15.0',
});
};
</script>
<script async defer crossorigin="anonymous" src="https://connect.facebook.net/it_IT/sdk.js"></script>
<!-- Bootstrap core JavaScript -->
<script src="vendor/jquery/jquery.min.js"></script>
<script src="vendor/popper/popper.min.js"></script>
<script src="admin/_V4/_js/_acxent.js"></script>
<script src="_js/rus-ecom-240621.js"></script>
<script src="addons/datepicker/js/bootstrap-datepicker.min.js"></script>
<script src="addons/datepicker/locales/bootstrap-datepicker.it.min.js"></script>
<script src="vendor/bootstrap/js/bootstrap.min.js"></script>
<!-- google sign in -->
<script src="https://accounts.google.com/gsi/client" async defer></script>
<!-- -->
<!-- owl -->
<link rel="stylesheet" href="vendor/owl-carousel/owl.carousel.min.css">
<link rel="stylesheet" href="vendor/owl-carousel/owl.theme.default.min.css">
<!-- sweet alert -->
<link rel="stylesheet" type="text/css" href="js/sweetalert/sweetalert.css">
<script src="js/sweetalert/sweetalert.min.js"></script>
<a id="top"></a>
<!-- Navigation -->
<nav class="navbar fixed-top navbar-expand-lg navbar-light bg-white fixed-top">
<div class="container"> <a class="navbar-brand" href="index.html"><img src="images/layout/regalami-un-sorriso-ets-640.png" alt="Regalami_Un_Sorriso_Ets" width="100"></a>
<button class="navbar-toggler navbar-toggler-right" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav">
<li class="nav-item"> <a class="nav-link " href="index.html">Home</a> </li>
<li class="nav-item"> <a class="nav-link " href="associazione-en.html">Association</a> </li>
<li class="nav-item"> <a class="nav-link " href=" rivista+atleticaImmagine-en.html"><img src="pics/atletica2.jpg" class="img-fluid" style="max-width: 110px"></a> </li>
<li class="nav-item dropdown"> <a href="#" class="dropdown-toggle nav-link " data-toggle="dropdown">Photo <b class="caret"></b></a>
<ul class="dropdown-menu multi-column columns-2" style="overflow-y:scroll">
<div class="row">
<div class="col-sm-6 vertical-divider">
<h2>EVENT PHOTOS</h2>
<ul class="multi-column-dropdown">
<li><a href="Carnevale-elenco_eventi-14---1-en.html">Carnival</a></li>
<li><a href="Defibrillatori-elenco_eventi-20---1-en.html">Defibrillators</a></li>
<li><a href="Feste-elenco_eventi-12---1-en.html">Parties</a></li>
<li><a href="Lions-elenco_eventi-4---1-en.html">Lions</a></li>
<li><a href="Manifestazioni-elenco_eventi-13---1-en.html">Demonstrations</a></li>
<li><a href="Panathlon-elenco_eventi-3---1-en.html">Panathlon</a></li>
<li><a href="Viaggi-elenco_eventi-18---1-en.html">Trips</a></li>
<li><a href="Volantini storici-elenco_eventi-21---1-en.html">Historical flyers</a></li>
</ul>
</div>
<div class="col-sm-6 ">
<h2>SPORTS PHOTOS</h2>
<ul class="multi-column-dropdown">
<li><a href="Basket-elenco_eventi-17---1-en.html">Basketball</a></li>
<li><a href="Ciclismo-elenco_eventi-6---1-en.html">Bike</a></li>
<li><a href="Golf-elenco_eventi-15---1-en.html">Golf</a></li>
<li><a href="Multidiscipline-elenco_eventi-11---1-en.html">Multidiscipline</a></li>
<li><a href="Podismo-elenco_eventi-5---1-en.html">Running</a></li>
</ul>
</div>
</div>
</ul>
</li>
<li class="nav-item dropdown show"> <a class="nav-link btn btn-sm btn-warning dropdown-toggle" data-toggle="dropdown" href="#" aria-expanded="true">Archive <b class="caret"></b></a>
<ul class="dropdown-menu ">
<li>
<h2 style="margin: 0px;font-size: 12px;"><a style="padding-left: 0px;" href="gallery1.php">.Fino al 2011.</a></h2>
</li>
<li>
<h2 style="margin: 0px;font-size: 12px;"><a style="padding-left: 0px;" href="gallery2.php">2012-2017</a></h2>
</li>
</ul>
</li>
<a href="dettaglio_clienti-en.html"><img src="https://www.regalamiunsorriso.it/images/btn_donateCC_LG.gif" border="0" name="submit" alt="PayPal - The easiest and safest online payment system!"></a>
<!--
<form action="https://www.paypal.com/cgi-bin/webscr" method="post">
<input type="hidden" name="cmd" value="_s-xclick">
<input type="hidden" name="hosted_button_id" value="4089564">
<input type="image" src="https://www.regalamiunsorriso.it/images/btn_donateCC_LG.gif" border="0" name="submit" alt="PayPal - Il sistema di pagamento online piu' facile e sicuro!">
</form>-->
</ul>
<ul class="navbar-nav ml-auto">
<li class="nav-item dropdown"> <a class="nav-link dropdown-toggle active" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <i class="fa fa-user" aria-hidden="true"></i> My account </a>
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink"> <a class="dropdown-item" href="dettaglio_clienti-en.html"><i class="fa fa-user" aria-hidden="true"></i> Account Detail</a> <a class="dropdown-item" href="586291449-user_logout-en.html"><i class="fa fa-sign-out" aria-hidden="true"></i> Logout</a> </div>
</li>
<li class="nav-item"> <a class="nav-link" href="https://it-it.facebook.com/pg/Regalami-un-sorriso-ETS-189377806523/community/"><img src="images/FB-f-Logo__blue_29.png" class="img-fluid" alt="Facebook"></a></li>
</ul>
<ul class="navbar-nav ml-auto" data="langMenu">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="langDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <img src="images/flags/en.png" alt="English" title="English" class="flag"> </a>
<div class="dropdown-menu" aria-labelledby="langDropdownMenuLink">
<a class="dropdown-item" href="index-it.html"> <img src="images/flags/it.png" class="flag" alt="Italiano" title="Italiano"> Italiano </a>
</div>
</ul>
</div>
</div>
</nav>
<div class="container my-3">
<div class="row">
<div class="col-lg-6 my-4">
<h1 class="my-3">User Area</h1>
<div class="col-lg-12 my-12"> <a href="index.html" class="btn btn-primary btn-block"><br>
View photos<br>
<br>
</a> </div>
<p class="mt-5 mb-4">HI <strong>Giacomelli Piero</strong><br>
Address: <strong>Fosso del Masi n. 24, Prato 59100 (PO)</strong><br>
Company belonging to: <strong>REGALAMI UN SORRISO ONLUS </strong><br>
Telephone: <strong>3487258208</strong><br>
E-mail: <strong><a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="5828313d2a371828313d2a373f31393b37353d343431763b3735">[email&#160;protected]</a></strong><br>
Tax ID code: <strong>GCMPRI58H25G999U</strong><br>
Expiration Date: <strong>15/06/2030</strong><br>
No. of photos displayed/No. of photos max: <strong>37045/0</strong><br>
Number of photos viewed today: <strong>0</strong></p>
<p class="">Please verify that the information is correct. For changes, please write to <a href="/cdn-cgi/l/email-protection#74121b001b34041d11061b131d15171b191118181d5a171b19"><span class="__cf_email__" data-cfemail="95f3fae1fad5e5fcf0e7faf2fcf4f6faf8f0f9f9fcbbf6faf8">[email&#160;protected]</span></a> or go to <a href="registrazione_utente.html">Edit Data</a> <br>
Please note that tacit consent to the accuracy of the member personal data is required.</p>
<p class="">.Mancano. <strong>1518 </strong> .giorni alla scadenza..<br>
</p>
</div>
<div class="col-lg-6 my-4" ">
</div>
<div class="col-md-12 text-danger font-weight-bold"> Login ok </div>
</div>
</div>
<!-- /.container --> <!-- InstanceEndEditable -->
<!-- Footer -->
<!-- inc_footer.jsp -->
<!-- sweet alert -->
<link rel="stylesheet" type="text/css" href="js/sweetalert/sweetalert.css">
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js" defer></script><script src="js/sweetalert/sweetalert.min.js"></script>
<footer>
<div class="container py-3">
<div class="row">
<div class="col-lg-3 col-md-6">
<h4>Contacts</h4>
<p>ETS Regalami un sorriso<br>
Via Torquato Tasso, 23/C<br>
59100 Prato PO<br>
CF 92076170486<br>
<a href="/cdn-cgi/l/email-protection#e98f869d86a999808c9b868e80888a86848c858580c78a8684"><span class="__cf_email__" data-cfemail="b8ded7ccd7f8c8d1ddcad7dfd1d9dbd7d5ddd4d4d196dbd7d5">[email&#160;protected]</span></a><br>
<a href="/cdn-cgi/l/email-protection#36445351575a575b5f4358455944445f45597646535518465f534459515f5755595b535a5a5f1855595b"><span class="__cf_email__" data-cfemail="7d0f181a1c111c101408130e120f0f140e123d0d181e530d14180f121a141c1e121018111114531e1210">[email&#160;protected]</span></a> <br>
Alone <i class="fa fa-whatsapp fa-2x" aria-hidden="true" title="WhatsApp" style="color:#25e47b"></i>: 348 7258209
</p>
</div>
<div class="col-lg-3 col-md-6">
<h4>Information</h4>
<ul class="list-unstyled">
<li><a href="associazione-en.html">Association</a></li>
<li><a href="privacy-en.html">Privacy</a></li>
<li><a href="cookies-en.html">Cookies</a></li>
</ul>
</div>
</div>
</div>
<hr>
<p class="text-center text-secondary"><small>Copyright</small></p>
<p class="triangle"><a href="#top"><i class="fa fa-arrow-up" aria-hidden="true"></i></a></p>
</footer>
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js" defer></script><script src="_js/noTastoDx.js"></script>
<script>
$('#datepicker-sport').datepicker({
language: "it"
});
$('#datepicker-eventi').datepicker({
language: "it"
});
</script>
<!-- InstanceBeginEditable name="lastStuff" --> <!-- InstanceEndEditable -->
<div class="modal-loading"></div>
</body>
<!-- InstanceEnd --></html>

View file

@ -16,7 +16,10 @@
"start:processor": "npm run start --workspace @regalami/faceai-processor",
"test:e2e": "playwright test",
"test:e2e:headed": "playwright test --headed",
"test:e2e:install": "playwright install chromium"
"test:e2e:install": "playwright install chromium",
"test:live": "playwright test -c playwright.live.config.js",
"test:live:headed": "playwright test -c playwright.live.config.js --headed",
"test:live:install": "playwright install chromium"
},
"devDependencies": {
"@playwright/test": "^1.59.1",

View file

@ -0,0 +1,38 @@
const path = require('path');
const { defineConfig } = require('@playwright/test');
const authFile = path.join(__dirname, 'tests/live-site/.auth/user.json');
module.exports = defineConfig({
testDir: './tests/live-site',
timeout: 2 * 60 * 1000,
expect: {
timeout: 20 * 1000
},
fullyParallel: false,
workers: 1,
reporter: [['list'], ['html', { open: 'never', outputFolder: 'playwright-report/live-site' }]],
outputDir: 'test-results/live-site',
use: {
baseURL: process.env.LIVE_SITE_BASE_URL || 'https://www.regalamiunsorriso.it',
browserName: 'chromium',
headless: true,
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure'
},
projects: [
{
name: 'setup',
testMatch: /.*\.setup\.js/
},
{
name: 'live-chromium',
dependencies: ['setup'],
testIgnore: /.*\.setup\.js/,
use: {
storageState: authFile
}
}
]
});

47
faceai/probe.js Normal file
View file

@ -0,0 +1,47 @@
const { chromium } = require('playwright');
const fs = require('fs');
async function run() {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
try {
const loginUrl = process.env.LIVE_SITE_LOGIN_URL;
const raceUrl = process.env.LIVE_SITE_RACE_URL;
console.log('--- Step 1: Navigating to Login ---');
await page.goto(loginUrl, { waitUntil: 'load' });
console.log('--- Step 2: Filling credentials ---');
await page.type('#login', process.env.LIVE_SITE_USERNAME);
await page.type('#pwd', process.env.LIVE_SITE_PASSWORD);
console.log('--- Step 3: Clicking Accedi ---');
await page.click('button:has-text("Accedi")');
await page.waitForTimeout(5000); // Give it time to process and redirect
console.log('--- Step 4: Navigating to Race Page ---');
const response = await page.goto(raceUrl, { waitUntil: 'load' });
console.log('Race Page URL:', page.url());
console.log('HTTP Status:', response.status());
const cookies = await context.cookies();
const cookieNames = cookies.map(c => c.name);
console.log('Cookies:', cookieNames.join(', '));
console.log('rus_faceai_identity:', cookieNames.includes('rus_faceai_identity'));
console.log('JSESSIONID:', cookieNames.includes('JSESSIONID'));
const logoutCount = await page.locator('a[href*="logout"]').count();
const loginCount = await page.locator('a[href*="login"]').count();
console.log('Logout Link Count:', logoutCount);
console.log('Login Link Count:', loginCount);
} catch (err) {
console.error('Error occurred:', err.message);
} finally {
await browser.close();
}
}
run();

1100
faceai/response.html Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,12 @@
const { test } = require('@playwright/test');
const {
AUTH_FILE,
ensureAuthDirectory,
performLiveLogin
} = require('./live-site-test-utils');
test('authenticate against the live site', async ({ page }) => {
ensureAuthDirectory();
await performLiveLogin(page);
await page.context().storageState({ path: AUTH_FILE });
});

View file

@ -0,0 +1,121 @@
const { test, expect } = require('@playwright/test');
const {
LIVE_FACEAI_BASE_URL,
LIVE_SITE_BASE_URL,
LIVE_SITE_PORTRAIT_PATH,
LIVE_SITE_RACE_URL,
LIVE_SITE_RUN_UPLOAD_FLOW,
ensureLiveAuthenticatedRacePage,
requirePortraitFixture
} = require('./live-site-test-utils');
function escapeRegExp(value) {
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
async function openLiveFaceAi(page) {
const consoleErrors = [];
page.on('console', (message) => {
if (message.type() === 'error') {
consoleErrors.push(message.text());
}
});
await ensureLiveAuthenticatedRacePage(page);
await expect(page.locator('h1')).toContainText(/HALF MARATHON FIRENZE|Competitions|Gare/i);
const launchUrl = await page.evaluate(() => {
return typeof buildFaceAiLaunchUrl === 'function' ? buildFaceAiLaunchUrl() : '';
});
expect(launchUrl, 'Expected the legacy race page script to expose a FaceAI handoff URL builder.').toBeTruthy();
const parsedLaunchUrl = new URL(launchUrl, LIVE_SITE_BASE_URL);
expect(parsedLaunchUrl.searchParams.get('raceYear')).toBeTruthy();
expect(parsedLaunchUrl.searchParams.get('raceMonthFolder')).toBeTruthy();
expect(parsedLaunchUrl.searchParams.get('raceFolder')).toBeTruthy();
expect(parsedLaunchUrl.searchParams.get('raceStorageRelativeDir')).toBeTruthy();
await expect(page.locator('#faceaiLaunchButton')).toBeVisible();
await page.locator('#faceaiLaunchButton').click();
await page.waitForURL(new RegExp(`^${escapeRegExp(LIVE_FACEAI_BASE_URL)}/`), {
timeout: 60 * 1000
});
await expect(page.getByRole('heading', { name: /Trova le tue foto con un selfie|Find your photos with a selfie/i })).toBeVisible();
return {
consoleErrors,
launchUrl: parsedLaunchUrl
};
}
test('loads a live race page with an authenticated session', async ({ page }) => {
await ensureLiveAuthenticatedRacePage(page);
const cookies = await page.context().cookies(LIVE_SITE_RACE_URL);
const faceAiIdentityCookie = cookies.find((cookie) => cookie.name === 'rus_faceai_identity');
expect(faceAiIdentityCookie, 'Expected the race page to mint the FaceAI identity cookie for the authenticated session.').toBeTruthy();
expect(faceAiIdentityCookie.httpOnly).toBe(true);
expect(faceAiIdentityCookie.secure).toBe(true);
expect(faceAiIdentityCookie.value).toMatch(/\./);
});
test('launches the live FaceAI app with race storage metadata and a styled header', async ({ page }) => {
const { consoleErrors, launchUrl } = await openLiveFaceAi(page);
expect(launchUrl.searchParams.get('raceStorageRelativeDir')).toContain('/');
await expect(page.locator('nav.navbar')).toBeVisible();
await expect(page.locator('link[data-legacy-href*="bootstrap.min.css"]')).toHaveCount(1);
await expect(page.locator('link[data-legacy-href*="custom-style.css"]')).toHaveCount(1);
const legacyStylesheetHrefs = await page.locator('link[data-legacy-href]').evaluateAll((elements) => {
return elements.map((element) => element.getAttribute('href') || '');
});
expect(legacyStylesheetHrefs.some((href) => href.startsWith(`${LIVE_SITE_BASE_URL}/`))).toBe(true);
const navComputedStyles = await page.locator('nav.navbar').evaluate((element) => {
const styles = window.getComputedStyle(element);
return {
position: styles.position,
display: styles.display
};
});
expect(navComputedStyles.position).toBe('fixed');
expect(navComputedStyles.display).not.toBe('block');
expect(consoleErrors.find((entry) => entry.includes('MISSING_RACE_STORAGE')) || '').toBe('');
expect(consoleErrors.find((entry) => entry.includes('[FaceAI] Invalid race data:')) || '').toBe('');
});
test.skip(!LIVE_SITE_RUN_UPLOAD_FLOW, 'Set LIVE_SITE_RUN_UPLOAD_FLOW=1 to exercise the live upload flow.');
test('accepts the supplied portrait image for the live upload flow', async ({ page }) => {
requirePortraitFixture();
await openLiveFaceAi(page);
const fileInput = page.locator('input[type="file"]');
await expect(fileInput).toBeEnabled();
await fileInput.setInputFiles(LIVE_SITE_PORTRAIT_PATH);
await expect(page.locator('.faceai-file-name')).toContainText('test_portrait_1.png');
const searchResponsePromise = page.waitForResponse((response) => {
return response.request().method() === 'POST' && response.url().includes('/api/searches');
}, {
timeout: 60 * 1000
});
await page.getByRole('button', { name: /Avvia ricerca Face ID|Start Face ID search/i }).click();
const searchResponse = await searchResponsePromise;
expect(searchResponse.ok(), 'Expected the live upload flow to create a FaceAI search successfully.').toBe(true);
const searchPayload = await searchResponse.json();
expect(searchPayload.id || searchPayload.searchId, 'Expected the search creation response to include a search identifier.').toBeTruthy();
await expect(page.locator('.faceai-feedback')).not.toContainText(/Impossibile|Unable|Errore|Error/i);
});

View file

@ -0,0 +1,123 @@
const fs = require('fs');
const path = require('path');
const { expect } = require('@playwright/test');
const WORKSPACE_ROOT = path.resolve(__dirname, '..', '..', '..');
const LIVE_SITE_BASE_URL = process.env.LIVE_SITE_BASE_URL || 'https://www.regalamiunsorriso.it';
const LIVE_SITE_LOGIN_URL = process.env.LIVE_SITE_LOGIN_URL || `${LIVE_SITE_BASE_URL}/login_clienti-it.html`;
const LIVE_SITE_RACE_URL = process.env.LIVE_SITE_RACE_URL || `${LIVE_SITE_BASE_URL}/42%20HALF%20MARATHON%20FIRENZE_gara-1018545---96-1.html`;
const LIVE_FACEAI_BASE_URL = process.env.LIVE_FACEAI_BASE_URL || 'https://ai.regalamiunsorriso.it';
const LIVE_SITE_USERNAME = process.env.LIVE_SITE_USERNAME || '';
const LIVE_SITE_PASSWORD = process.env.LIVE_SITE_PASSWORD || '';
const LIVE_SITE_PORTRAIT_PATH = process.env.LIVE_SITE_PORTRAIT_PATH || path.join(WORKSPACE_ROOT, 'test_pkl', 'live', 'test_portrait_1.png');
const LIVE_SITE_RUN_UPLOAD_FLOW = process.env.LIVE_SITE_RUN_UPLOAD_FLOW === '1';
const AUTH_FILE = path.join(__dirname, '.auth', 'user.json');
function ensureAuthDirectory() {
fs.mkdirSync(path.dirname(AUTH_FILE), { recursive: true });
}
function requireCredentials() {
if (!LIVE_SITE_USERNAME || !LIVE_SITE_PASSWORD) {
throw new Error('LIVE_SITE_USERNAME and LIVE_SITE_PASSWORD must be set before running the live-site Playwright suite.');
}
}
async function dismissCookieBanner(page) {
const cookieButton = page.getByRole('button', { name: /^(Accetto|Accept)$/i });
if (await cookieButton.count()) {
await cookieButton.first().click({ timeout: 5000 }).catch(() => {});
return;
}
const fallbackButton = page.locator('.cc-btn, .cc-dismiss, .cc-allow').filter({ hasText: /Accetto|Accept/i });
if (await fallbackButton.count()) {
await fallbackButton.first().click({ timeout: 5000 }).catch(() => {});
}
}
function loginSubmitLocator(page) {
return page.locator('a.btn').filter({ hasText: /Accedi|Sign in/i }).first();
}
async function performLiveLogin(page) {
requireCredentials();
await page.goto(LIVE_SITE_LOGIN_URL, { waitUntil: 'domcontentloaded' });
await dismissCookieBanner(page);
await page.locator('#login').fill(LIVE_SITE_USERNAME);
await page.locator('#pwd').fill(LIVE_SITE_PASSWORD);
await loginSubmitLocator(page).click();
await waitForLoggedInUi(page);
}
async function waitForLoggedInUi(page) {
const accountMenu = page.locator('#navbarDropdownMenuLink');
const accountLink = page.locator('a[href*="dettaglio_clienti"]');
const logoutLink = page.locator('a[href*="user_logout"]');
await expect.poll(async () => {
if (await accountMenu.count() && await accountMenu.first().isVisible().catch(() => false)) {
return 'account-menu';
}
if (await accountLink.count() && await accountLink.first().isVisible().catch(() => false)) {
return 'account-link';
}
if (await logoutLink.count() && await logoutLink.first().isVisible().catch(() => false)) {
return 'logout-link';
}
return '';
}, {
timeout: 30 * 1000,
message: 'Expected the logged-in account UI to appear after authenticating.'
}).not.toBe('');
}
async function expectRacePageLoaded(page) {
await expect(page.locator('form[onsubmit="return searching()"]')).toBeVisible();
await expect(page.locator('#id_gara')).toHaveValue(/\d+/);
await expect(page.locator('script[src*="_js/rus-ecom-240621.js"]')).toHaveCount(1);
}
async function ensureLiveAuthenticatedRacePage(page) {
await page.goto(LIVE_SITE_RACE_URL, { waitUntil: 'domcontentloaded' });
await dismissCookieBanner(page);
try {
await waitForLoggedInUi(page);
} catch (error) {
await performLiveLogin(page);
await page.goto(LIVE_SITE_RACE_URL, { waitUntil: 'domcontentloaded' });
await dismissCookieBanner(page);
await waitForLoggedInUi(page);
}
await expectRacePageLoaded(page);
}
function requirePortraitFixture() {
if (!fs.existsSync(LIVE_SITE_PORTRAIT_PATH)) {
throw new Error(`LIVE_SITE_PORTRAIT_PATH does not exist: ${LIVE_SITE_PORTRAIT_PATH}`);
}
}
module.exports = {
AUTH_FILE,
LIVE_FACEAI_BASE_URL,
LIVE_SITE_BASE_URL,
LIVE_SITE_LOGIN_URL,
LIVE_SITE_PASSWORD,
LIVE_SITE_PORTRAIT_PATH,
LIVE_SITE_RACE_URL,
LIVE_SITE_RUN_UPLOAD_FLOW,
LIVE_SITE_USERNAME,
dismissCookieBanner,
ensureLiveAuthenticatedRacePage,
ensureAuthDirectory,
expectRacePageLoaded,
loginSubmitLocator,
performLiveLogin,
requirePortraitFixture,
requireCredentials,
waitForLoggedInUi
};

View file

@ -0,0 +1,45 @@
{
"cookies": [
{
"name": "JSESSIONID",
"value": "C78CCBD1771ACB52852817531958E96D",
"domain": "www.regalamiunsorriso.it",
"path": "/",
"expires": -1,
"httpOnly": true,
"secure": false,
"sameSite": "Lax"
},
{
"name": "_pk_id.6.42c1",
"value": "5710c1018f76b02c.1776585670.",
"domain": "www.regalamiunsorriso.it",
"path": "/",
"expires": 1810540870,
"httpOnly": false,
"secure": false,
"sameSite": "Lax"
},
{
"name": "_pk_ses.6.42c1",
"value": "1",
"domain": "www.regalamiunsorriso.it",
"path": "/",
"expires": 1776587471,
"httpOnly": false,
"secure": false,
"sameSite": "Lax"
},
{
"name": "g_state",
"value": "{\"i_l\":0,\"i_ll\":1776585670175,\"i_b\":\"TwramoDGp33YsAyHyO2OJCLtRobG3bzu5wR3/nYmZ0E\",\"i_e\":{\"enable_itp_optimization\":0},\"i_et\":1776585670056}",
"domain": "www.regalamiunsorriso.it",
"path": "/",
"expires": 1792137670,
"httpOnly": false,
"secure": false,
"sameSite": "Lax"
}
],
"origins": []
}

View file

@ -10,20 +10,17 @@ All files in this rollout are deployed from the current working tree.
## New Files
- None in this rollout.
- `www/_inc_faceai_identity.jsp`
- `www/_js/lang.js`
## Updated Files
- `www/faceai_config.php`
- `www/faceai_handoff.php`
- `www/faceai_return.php`
- `www/fotoCR.jsp`
- `www/fotoCR-en.jsp`
- `www/fotoCR.jsp`
## Excluded Files
- `www/faceai_simulator.php`
- `www/faceai_simulator_view.php`
- None in this rollout.
## Remote Copy Target
@ -31,7 +28,7 @@ All files in this rollout are deployed from the current working tree.
- Remote host: `marco@83.149.164.4:410`
- Remote staging path: `/home/marco/regalamiunsorriso/incoming/www`
- Remote live path: `/home/sites/regalamiunsorriso/www`
- Total files in this manifest: `5`
- Total files in this manifest: `4`
## Transfer Method

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

View file

@ -0,0 +1,260 @@
<%@ page language="java" import="java.nio.charset.StandardCharsets" %>
<%@ page language="java" import="java.util.Base64" %>
<%@ page language="java" import="java.lang.reflect.Method" %>
<%@ page language="java" import="javax.crypto.Mac" %>
<%@ page language="java" import="javax.crypto.spec.SecretKeySpec" %>
<%!
private String faceAiCookieEnv(String key, String defaultValue) {
String value = System.getenv(key);
if (value == null || value.trim().length() == 0) {
value = System.getProperty(key, defaultValue);
}
if (value == null) {
return defaultValue;
}
value = value.trim();
return value.length() == 0 ? defaultValue : value;
}
private String faceAiBase64Url(byte[] value) {
return Base64.getUrlEncoder().withoutPadding().encodeToString(value);
}
private String faceAiJsonEscape(String value) {
if (value == null) {
return "";
}
StringBuilder builder = new StringBuilder(value.length() + 16);
for (int index = 0; index < value.length(); index++) {
char current = value.charAt(index);
switch (current) {
case '\\':
builder.append("\\\\");
break;
case '"':
builder.append("\\\"");
break;
case '\b':
builder.append("\\b");
break;
case '\f':
builder.append("\\f");
break;
case '\n':
builder.append("\\n");
break;
case '\r':
builder.append("\\r");
break;
case '\t':
builder.append("\\t");
break;
default:
if (current < 0x20) {
String hex = Integer.toHexString(current);
builder.append("\\u");
for (int padding = hex.length(); padding < 4; padding++) {
builder.append('0');
}
builder.append(hex);
} else {
builder.append(current);
}
break;
}
}
return builder.toString();
}
private Object faceAiInvoke(Object bean, String methodName) {
if (bean == null || methodName == null || methodName.length() == 0) {
return null;
}
try {
Method method = bean.getClass().getMethod(methodName, new Class[0]);
return method.invoke(bean, new Object[0]);
} catch (Exception ignored) {
return null;
}
}
private long faceAiUserId(Object user) {
Object value = faceAiInvoke(user, "getId_users");
if (value instanceof Number) {
return ((Number) value).longValue();
}
if (value != null) {
try {
return Long.parseLong(String.valueOf(value));
} catch (NumberFormatException ignored) {
return 0L;
}
}
return 0L;
}
private String faceAiUserString(Object user, String methodName) {
Object value = faceAiInvoke(user, methodName);
return value == null ? "" : String.valueOf(value).trim();
}
private boolean faceAiUserDaRinnovare(Object user) {
Object value = faceAiInvoke(user, "isDaRinnovare");
return value instanceof Boolean ? ((Boolean) value).booleanValue() : false;
}
private String faceAiCookieDisplayName(Object user) {
String nome = faceAiUserString(user, "getNome");
String cognome = faceAiUserString(user, "getCognome");
String displayName = (nome + " " + cognome).trim();
if (displayName.length() > 0) {
return displayName;
}
String email = faceAiUserString(user, "getEMail");
if (email.length() > 0) {
return email;
}
return String.valueOf(faceAiUserId(user));
}
private String faceAiIdentityToken(Object user, String secret, long expiresAt) throws Exception {
String email = faceAiUserString(user, "getEMail");
String membershipStatus = faceAiUserDaRinnovare(user) ? "inactive" : "active";
String payload = "{"
+ "\"type\":\"legacy-identity\","
+ "\"userId\":\"" + faceAiJsonEscape(String.valueOf(faceAiUserId(user))) + "\","
+ "\"displayName\":\"" + faceAiJsonEscape(faceAiCookieDisplayName(user)) + "\","
+ "\"email\":\"" + faceAiJsonEscape(email) + "\","
+ "\"membershipStatus\":\"" + membershipStatus + "\","
+ "\"expiresAt\":" + expiresAt
+ "}";
String body = faceAiBase64Url(payload.getBytes(StandardCharsets.UTF_8));
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String signature = faceAiBase64Url(mac.doFinal(body.getBytes(StandardCharsets.UTF_8)));
return body + "." + signature;
}
private boolean faceAiRequestIsSecure(javax.servlet.http.HttpServletRequest request) {
if (request == null) {
return false;
}
if (request.isSecure()) {
return true;
}
String forwardedProto = request.getHeader("X-Forwarded-Proto");
if (forwardedProto != null && "https".equalsIgnoreCase(forwardedProto.trim())) {
return true;
}
String frontEndHttps = request.getHeader("Front-End-Https");
return frontEndHttps != null && "on".equalsIgnoreCase(frontEndHttps.trim());
}
private void faceAiWriteCookieHeader(javax.servlet.http.HttpServletResponse response, String cookieName, String cookieValue, int maxAgeSeconds, boolean secureCookie) {
StringBuilder headerValue = new StringBuilder();
headerValue.append(cookieName).append('=').append(cookieValue == null ? "" : cookieValue);
headerValue.append("; Max-Age=").append(maxAgeSeconds);
headerValue.append("; Path=/; HttpOnly; SameSite=Lax");
if (secureCookie) {
headerValue.append("; Secure");
}
response.addHeader("Set-Cookie", headerValue.toString());
}
private Object faceAiResolveUser(javax.servlet.jsp.PageContext pageContext) {
if (pageContext == null) {
return null;
}
Object[] candidates = new Object[] {
pageContext.findAttribute("user"),
pageContext.findAttribute("utenteLogon")
};
for (int index = 0; index < candidates.length; index++) {
Object candidate = candidates[index];
if (candidate != null && faceAiUserId(candidate) > 0L) {
return candidate;
}
}
return null;
}
private Object faceAiResolveUserFromSession(javax.servlet.jsp.PageContext pageContext) {
if (pageContext == null || pageContext.getSession() == null) {
return null;
}
Object loginUserId = pageContext.getSession().getAttribute("loginUser_id");
long resolvedLoginUserId = 0L;
if (loginUserId instanceof Number) {
resolvedLoginUserId = ((Number) loginUserId).longValue();
} else if (loginUserId != null) {
try {
resolvedLoginUserId = Long.parseLong(String.valueOf(loginUserId));
} catch (NumberFormatException ignored) {
resolvedLoginUserId = 0L;
}
}
if (resolvedLoginUserId <= 0L) {
return null;
}
Object sessionUser = pageContext.findAttribute("utenteLogon");
if (sessionUser == null) {
return null;
}
if (faceAiUserId(sessionUser) == resolvedLoginUserId) {
return sessionUser;
}
try {
Method findByPrimaryKey = sessionUser.getClass().getMethod("findByPrimaryKey", long.class);
findByPrimaryKey.invoke(sessionUser, Long.valueOf(resolvedLoginUserId));
} catch (NoSuchMethodException missingPrimitiveOverload) {
try {
Method findByPrimaryKey = sessionUser.getClass().getMethod("findByPrimaryKey", Long.class);
findByPrimaryKey.invoke(sessionUser, new Long(resolvedLoginUserId));
} catch (Exception ignored) {
return null;
}
} catch (Exception ignored) {
return null;
}
return faceAiUserId(sessionUser) > 0L ? sessionUser : null;
}
%>
<%
String faceAiCookieName = faceAiCookieEnv("FACEAI_IDENTITY_COOKIE", "rus_faceai_identity");
String faceAiCookieSecret = faceAiCookieEnv("FACEAI_SHARED_SECRET", "disagio-spaghetti-science-lol-boh");
Object faceAiRequestUser = pageContext.findAttribute("user");
Object faceAiSessionUser = pageContext.findAttribute("utenteLogon");
long faceAiRequestUserId = faceAiUserId(faceAiRequestUser);
long faceAiSessionUserId = faceAiUserId(faceAiSessionUser);
Object faceAiCookieUser = faceAiRequestUserId > 0L ? faceAiRequestUser : (faceAiSessionUserId > 0L ? faceAiSessionUser : faceAiResolveUserFromSession(pageContext));
boolean faceAiSecureCookie = faceAiRequestIsSecure(request);
if (faceAiFeatureEnabled && faceAiCookieUser != null && faceAiUserId(faceAiCookieUser) > 0L) {
long faceAiExpiresAt = System.currentTimeMillis() + (30L * 60L * 1000L);
try {
String faceAiToken = faceAiIdentityToken(faceAiCookieUser, faceAiCookieSecret, faceAiExpiresAt);
faceAiWriteCookieHeader(response, faceAiCookieName, faceAiToken, 30 * 60, faceAiSecureCookie);
} catch (Exception faceAiIdentityError) {
faceAiWriteCookieHeader(response, faceAiCookieName, "", 0, faceAiSecureCookie);
log("Unable to mint FaceAI identity cookie", faceAiIdentityError);
}
} else {
faceAiWriteCookieHeader(response, faceAiCookieName, "", 0, faceAiSecureCookie);
}
%>

55
www/_js/lang.js Normal file
View file

@ -0,0 +1,55 @@
// JavaScript Document
////////////////////////////////////////////
// gestione cambio lingua
////////////////////////////////////////////
var userLang = navigator.language || navigator.userLanguage;
console.log(userLang);
function changeLang(lang) {
var page = window.location.href;
var idx = page.lastIndexOf(".");
if (idx > 0) {
var ext = page.substring(idx, page.length);
if (ext.indexOf("eu") >= 0 || ext.indexOf("com") >= 0 || ext.indexOf("net") >= 0 || ext.indexOf("it") >= 0) {
ext = "";
var page1 = page;
}
else
var page1 = page.substring(0, idx);
}
else {
// caso di pagina root www.xxxx.com/
var ext = "";
var page1 = page;
}
// 1 se ho pagine del tipo xxx.html --> aggiungo -lang.html
// 2 se ho pagine del tipo xxx_xxx-xxx-xxx-.html --> sostituisco l'ultima parte con xxx-lang.html
// 3 se ho pagine del tipo xxx_xxx-xx-xx-lang.html --> sostituisco l'ultima parte
// 4 se non ho estensione sono nella root e finisce con /(www.xxxx.com/) --> vado su index-lang
// 5 altrimenti vado su index-lang.jsp
// 6 caso Ordine.abl --> ritorno alla home
if (ext == ".abl") {
theSvlt = "index-" + lang + ".html";
}
else if (ext == "") {
theSvlt = page1 + "index-" + lang + ".html";
}
else if (page1.lastIndexOf("-") == (page1.length - 1)) {
theSvlt = page1 + lang + ext;
}
else if (page1.lastIndexOf("-") == (page1.length - 3)) {
theSvlt = page1.substring(0, page1.length - 2) + lang + ext;
}
else if (page1.substring(page1.length - 1, page1.length) != "-") {
theSvlt = page1 + "-" + lang + ext;
}
else {
theSvlt = "index-" + lang + ".jsp";
}
location.href = theSvlt;
}

View file

@ -258,6 +258,7 @@ function buildFaceAiLaunchUrl() {
var raceYear = getFaceAiStorageValue("faceAiRaceYear", "year");
var raceMonthFolder = getFaceAiStorageValue("faceAiRaceMonthFolder", "monthFolder");
var raceFolder = getFaceAiStorageValue("faceAiRaceFolder", "raceFolder");
var raceStorageRelativeDir = $("#faceAiRaceStorageRelativeDir").val() || [raceYear, raceMonthFolder, raceFolder].filter(Boolean).join("/");
var lang = getCurrentLangValue();
var handoffUrl = (window.faceAiSimulator && window.faceAiSimulator.handoffUrl) || "faceai_handoff.php";
var returnUrl = (window.faceAiSimulator && window.faceAiSimulator.returnUrl) || window.location.href;
@ -268,6 +269,7 @@ function buildFaceAiLaunchUrl() {
"raceYear=" + encodeURIComponent(raceYear),
"raceMonthFolder=" + encodeURIComponent(raceMonthFolder),
"raceFolder=" + encodeURIComponent(raceFolder),
"raceStorageRelativeDir=" + encodeURIComponent(raceStorageRelativeDir),
"lang=" + encodeURIComponent(lang),
"returnUrl=" + encodeURIComponent(returnUrl)
];

View file

@ -11,6 +11,7 @@ try {
$raceYear = faceai_request_value('raceYear');
$raceMonthFolder = faceai_request_value('raceMonthFolder');
$raceFolder = faceai_request_value('raceFolder');
$raceStorageRelativeDir = faceai_request_value('raceStorageRelativeDir');
$lang = faceai_request_value('lang', 'it');
$returnUrl = faceai_request_value('returnUrl');
@ -45,11 +46,12 @@ try {
'name' => $raceName !== '' ? $raceName : $raceId
);
if ($raceYear !== '' && $raceMonthFolder !== '' && $raceFolder !== '') {
if ($raceYear !== '' || $raceMonthFolder !== '' || $raceFolder !== '' || $raceStorageRelativeDir !== '') {
$racePayload['storage'] = array(
'year' => $raceYear,
'monthFolder' => $raceMonthFolder,
'raceFolder' => strtoupper(trim($raceFolder))
'raceFolder' => strtoupper(trim($raceFolder)),
'relativeDir' => $raceStorageRelativeDir
);
}

View file

@ -8,7 +8,6 @@
<%@ taglib uri="/WEB-INF/cc.tld" prefix="cc" %>
<html lang="<%=lang%>"><!-- InstanceBegin template="/Templates/rus.dwt" codeOutsideHTMLIsLocked="false" -->
<head>
<jsp:include page="_inc_lang.jsp" flush="true" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
@ -57,6 +56,10 @@ if (faceAiFeatureEnabledValue == null || faceAiFeatureEnabledValue.trim().length
}
String faceAiFeatureEnabledNormalized = faceAiFeatureEnabledValue != null ? faceAiFeatureEnabledValue.trim() : "";
boolean faceAiFeatureEnabled = !("0".equals(faceAiFeatureEnabledNormalized) || "false".equalsIgnoreCase(faceAiFeatureEnabledNormalized) || "no".equalsIgnoreCase(faceAiFeatureEnabledNormalized) || "off".equalsIgnoreCase(faceAiFeatureEnabledNormalized));
%>
<%@ include file="_inc_faceai_identity.jsp" %>
<jsp:include page="_inc_lang.jsp" flush="true" />
<%
java.util.Date faceAiRaceDate = CR.getGara().getDataGaraInizio();
String faceAiRacePathBase = CR.getGara().getPathBase() != null ? CR.getGara().getPathBase().trim() : "";
String faceAiRaceYear = "";

View file

@ -8,7 +8,6 @@
<%@ taglib uri="/WEB-INF/cc.tld" prefix="cc" %>
<html lang="<%=lang%>"><!-- InstanceBegin template="/Templates/rus.dwt" codeOutsideHTMLIsLocked="false" -->
<head>
<jsp:include page="_inc_lang.jsp" flush="true" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
@ -57,6 +56,10 @@ if (faceAiFeatureEnabledValue == null || faceAiFeatureEnabledValue.trim().length
}
String faceAiFeatureEnabledNormalized = faceAiFeatureEnabledValue != null ? faceAiFeatureEnabledValue.trim() : "";
boolean faceAiFeatureEnabled = !("0".equals(faceAiFeatureEnabledNormalized) || "false".equalsIgnoreCase(faceAiFeatureEnabledNormalized) || "no".equalsIgnoreCase(faceAiFeatureEnabledNormalized) || "off".equalsIgnoreCase(faceAiFeatureEnabledNormalized));
%>
<%@ include file="_inc_faceai_identity.jsp" %>
<jsp:include page="_inc_lang.jsp" flush="true" />
<%
java.util.Date faceAiRaceDate = CR.getGara().getDataGaraInizio();
String faceAiRacePathBase = CR.getGara().getPathBase() != null ? CR.getGara().getPathBase().trim() : "";
String faceAiRaceYear = "";

BIN
www_staged.tar Normal file

Binary file not shown.