diff --git a/.github/instructions/instructions.instructions.md b/.github/instructions/instructions.instructions.md index c72cb2c4..7a744482 100644 --- a/.github/instructions/instructions.instructions.md +++ b/.github/instructions/instructions.instructions.md @@ -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) diff --git a/.github/instructions/regalamiunsorriso-83-149-164-4.instructions.md b/.github/instructions/regalamiunsorriso-83-149-164-4.instructions.md index 1259fa34..7b153171 100644 --- a/.github/instructions/regalamiunsorriso-83-149-164-4.instructions.md +++ b/.github/instructions/regalamiunsorriso-83-149-164-4.instructions.md @@ -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 ... --% ` 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 | 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 | 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 '' ' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Regalami Un Sorriso ETS - Competitions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+

Running // Running // 42 HALF MARATHON FIRENZE

+
+
+ + +
+
+ +
+
+ +
+ + + + + + + + +
+ + + +
+

Search your photos

+
+
+
+ + +
+
+
+
+ + +
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+

They have been found 35389 photo + + +

+

+
+
+ +
+
+
+ + + go to page + + +
+
+ + +
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ +
+
+
+ +

ATTENTION! GOOGLE ADVERTISING + + + +

+ + + + + +
+ + + + + + + + + + + + + + +
+ +
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/faceai/apps/backend/src/race-storage.js b/faceai/apps/backend/src/race-storage.js index 0a8ee4f7..0b150995 100644 --- a/faceai/apps/backend/src/race-storage.js +++ b/faceai/apps/backend/src/race-storage.js @@ -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; diff --git a/faceai/apps/backend/src/server.js b/faceai/apps/backend/src/server.js index da9f901f..e503def3 100644 --- a/faceai/apps/backend/src/server.js +++ b/faceai/apps/backend/src/server.js @@ -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 diff --git a/faceai/apps/frontend/src/legacyAssets.js b/faceai/apps/frontend/src/legacyAssets.js index 07e8a100..4c6e46ba 100644 --- a/faceai/apps/frontend/src/legacyAssets.js +++ b/faceai/apps/frontend/src/legacyAssets.js @@ -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}`}`; diff --git a/faceai/authenticated_output.html b/faceai/authenticated_output.html new file mode 100644 index 00000000..e14e1696 --- /dev/null +++ b/faceai/authenticated_output.html @@ -0,0 +1,1120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Regalami Un Sorriso ETS - Competitions + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+

Running // Running // 42 HALF MARATHON FIRENZE

+
+
+ + +
+
+ +
+
+ +
+ + + + + + + + +
+ + + +
+

Search your photos

+
+
+
+ + +
+
+
+
+ + +
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+

They have been found 35389 photo + + +

+

+
+
+ +
+
+
+ + + go to page + + +
+
+ + +
+
+ + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + +
+
+
+ +

ATTENTION! GOOGLE ADVERTISING + + + +

+ + + + + +
+ + + + + + + + + + + + + + +
+ +
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/faceai/headers.txt b/faceai/headers.txt new file mode 100644 index 00000000..e69de29b diff --git a/faceai/login_response.html b/faceai/login_response.html new file mode 100644 index 00000000..ebb9936e --- /dev/null +++ b/faceai/login_response.html @@ -0,0 +1,359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Regalami Un Sorriso ETS - User Area + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+

User Area

+ +

HI Giacomelli Piero
+ Address: Fosso del Masi n. 24, Prato 59100 (PO)
+ Company belonging to: REGALAMI UN SORRISO ONLUS
+ Telephone: 3487258208
+ E-mail: [email protected]
+ Tax ID code: GCMPRI58H25G999U
+ Expiration Date: 15/06/2030
+ No. of photos displayed/No. of photos max: 37045/0
+ Number of photos viewed today: 0

+

Please verify that the information is correct. For changes, please write to [email protected] or go to Edit Data
+ Please note that tacit consent to the accuracy of the member personal data is required.

+ +

.Mancano. 1518 .giorni alla scadenza..
+

+ + +
+
+ +
+
Login ok
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/faceai/package.json b/faceai/package.json index 4a363e79..473af682 100644 --- a/faceai/package.json +++ b/faceai/package.json @@ -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", diff --git a/faceai/playwright.live.config.js b/faceai/playwright.live.config.js new file mode 100644 index 00000000..4795474a --- /dev/null +++ b/faceai/playwright.live.config.js @@ -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 + } + } + ] +}); \ No newline at end of file diff --git a/faceai/probe.js b/faceai/probe.js new file mode 100644 index 00000000..e20ca7bb --- /dev/null +++ b/faceai/probe.js @@ -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(); diff --git a/faceai/response.html b/faceai/response.html new file mode 100644 index 00000000..0d0514e7 --- /dev/null +++ b/faceai/response.html @@ -0,0 +1,1100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Regalami Un Sorriso ETS - Gare + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+
+

Podismo // Podismo // 42 HALF MARATHON FIRENZE

+
+
+ + +
+
+ +
+
+ +
+ + + + + + + + +
+ + + +
+

Cerca le tue foto

+
+
+
+ + +
+
+
+
+ + +
+
+ +
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+

Sono state trovate 35389 foto + + +

+

+
+
+ +
+
+
+ + + vai a pag. + + +
+
+ + +
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ + + +
Hits: 0 -
+
+ +
+
+
+ +

ATTENZIONE ! PUBBLICITA' GOOGLE + + + +

+ + + + + +
+ + + + + + + + + + + + + + +
+ +
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/faceai/tests/live-site/auth.setup.js b/faceai/tests/live-site/auth.setup.js new file mode 100644 index 00000000..c0d47555 --- /dev/null +++ b/faceai/tests/live-site/auth.setup.js @@ -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 }); +}); \ No newline at end of file diff --git a/faceai/tests/live-site/live-race.spec.js b/faceai/tests/live-site/live-race.spec.js new file mode 100644 index 00000000..c98f9ecb --- /dev/null +++ b/faceai/tests/live-site/live-race.spec.js @@ -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); +}); \ No newline at end of file diff --git a/faceai/tests/live-site/live-site-test-utils.js b/faceai/tests/live-site/live-site-test-utils.js new file mode 100644 index 00000000..c98819d7 --- /dev/null +++ b/faceai/tests/live-site/live-site-test-utils.js @@ -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 +}; \ No newline at end of file diff --git a/faceai/tmp-live-state-inspection.json b/faceai/tmp-live-state-inspection.json new file mode 100644 index 00000000..31a25038 --- /dev/null +++ b/faceai/tmp-live-state-inspection.json @@ -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": [] +} \ No newline at end of file diff --git a/sync/www-deploy-manifest.md b/sync/www-deploy-manifest.md index b1e6ae06..dc174365 100644 --- a/sync/www-deploy-manifest.md +++ b/sync/www-deploy-manifest.md @@ -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 diff --git a/test_pkl/live/test_portrait_1.png b/test_pkl/live/test_portrait_1.png new file mode 100644 index 00000000..48e5c58c Binary files /dev/null and b/test_pkl/live/test_portrait_1.png differ diff --git a/www/_inc_faceai_identity.jsp b/www/_inc_faceai_identity.jsp new file mode 100644 index 00000000..7d3cb783 --- /dev/null +++ b/www/_inc_faceai_identity.jsp @@ -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); +} +%> \ No newline at end of file diff --git a/www/_js/lang.js b/www/_js/lang.js new file mode 100644 index 00000000..f6cb59e0 --- /dev/null +++ b/www/_js/lang.js @@ -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; +} \ No newline at end of file diff --git a/www/_js/rus-ecom-240621.js b/www/_js/rus-ecom-240621.js index d6f5b561..c83fbbe6 100644 --- a/www/_js/rus-ecom-240621.js +++ b/www/_js/rus-ecom-240621.js @@ -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) ]; diff --git a/www/faceai_handoff.php b/www/faceai_handoff.php index 466af2d2..08767ccb 100644 --- a/www/faceai_handoff.php +++ b/www/faceai_handoff.php @@ -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 ); } diff --git a/www/fotoCR-en.jsp b/www/fotoCR-en.jsp index 17ad6e47..f275b270 100644 --- a/www/fotoCR-en.jsp +++ b/www/fotoCR-en.jsp @@ -8,7 +8,6 @@ <%@ taglib uri="/WEB-INF/cc.tld" prefix="cc" %> - @@ -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" %> + +<% java.util.Date faceAiRaceDate = CR.getGara().getDataGaraInizio(); String faceAiRacePathBase = CR.getGara().getPathBase() != null ? CR.getGara().getPathBase().trim() : ""; String faceAiRaceYear = ""; diff --git a/www/fotoCR.jsp b/www/fotoCR.jsp index 76b6aaf7..596c240f 100644 --- a/www/fotoCR.jsp +++ b/www/fotoCR.jsp @@ -8,7 +8,6 @@ <%@ taglib uri="/WEB-INF/cc.tld" prefix="cc" %> - @@ -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" %> + +<% java.util.Date faceAiRaceDate = CR.getGara().getDataGaraInizio(); String faceAiRacePathBase = CR.getGara().getPathBase() != null ? CR.getGara().getPathBase().trim() : ""; String faceAiRaceYear = ""; diff --git a/www_staged.tar b/www_staged.tar new file mode 100644 index 00000000..4e6853ed Binary files /dev/null and b/www_staged.tar differ