All checks were successful
Publish FaceAI Container / publish (push) Successful in 5m41s
265 lines
7.8 KiB
PHP
265 lines
7.8 KiB
PHP
<?php
|
|
|
|
function faceai_env($key, $default = null)
|
|
{
|
|
$value = getenv($key);
|
|
return $value === false ? $default : $value;
|
|
}
|
|
|
|
function faceai_env_flag($key, $default = false)
|
|
{
|
|
$value = strtolower(trim((string) faceai_env($key, $default ? '1' : '0')));
|
|
return in_array($value, array('1', 'true', 'yes', 'on'), true);
|
|
}
|
|
|
|
function faceai_request_host()
|
|
{
|
|
if (empty($_SERVER['HTTP_HOST'])) {
|
|
return '';
|
|
}
|
|
|
|
return strtolower(trim((string) $_SERVER['HTTP_HOST']));
|
|
}
|
|
|
|
function faceai_is_local_host($host)
|
|
{
|
|
$normalized = strtolower(trim((string) $host));
|
|
if ($normalized === '') {
|
|
return false;
|
|
}
|
|
|
|
$withoutPort = preg_replace('/:\d+$/', '', $normalized);
|
|
return in_array($withoutPort, array('localhost', '127.0.0.1', '::1'), true);
|
|
}
|
|
|
|
function faceai_request_targets_local_frontend()
|
|
{
|
|
if (faceai_is_local_host(faceai_request_host())) {
|
|
return true;
|
|
}
|
|
|
|
$returnUrl = faceai_request_value('returnUrl');
|
|
if ($returnUrl === '') {
|
|
return false;
|
|
}
|
|
|
|
$host = parse_url($returnUrl, PHP_URL_HOST);
|
|
if (!is_string($host) || $host === '') {
|
|
return false;
|
|
}
|
|
|
|
return faceai_is_local_host($host);
|
|
}
|
|
|
|
function faceai_default_frontend_url()
|
|
{
|
|
if (faceai_request_targets_local_frontend()) {
|
|
return 'http://localhost:3001';
|
|
}
|
|
|
|
return 'https://ai.regalamiunsorriso.it';
|
|
}
|
|
|
|
function faceai_default_backend_internal_url()
|
|
{
|
|
if (faceai_is_local_host(faceai_request_host())) {
|
|
return 'http://localhost:3001';
|
|
}
|
|
|
|
return 'https://ai.regalamiunsorriso.it';
|
|
}
|
|
|
|
function faceai_config()
|
|
{
|
|
static $config = null;
|
|
|
|
if ($config !== null) {
|
|
return $config;
|
|
}
|
|
|
|
$config = array(
|
|
'feature_enabled' => faceai_env_flag('FACEAI_FEATURE_ENABLED', true),
|
|
'frontend_url' => rtrim(faceai_env('FACEAI_FRONTEND_URL', faceai_default_frontend_url()), '/'),
|
|
'backend_internal_url' => rtrim(faceai_env('FACEAI_BACKEND_INTERNAL_URL', faceai_default_backend_internal_url()), '/'),
|
|
'shared_secret' => (string) faceai_env('FACEAI_SHARED_SECRET', 'disagio-spaghetti-science-lol-boh'),
|
|
'allow_dev_handoff' => faceai_env_flag('FACEAI_ALLOW_DEV_HANDOFF', true),
|
|
'identity_cookie' => (string) faceai_env('FACEAI_IDENTITY_COOKIE', 'rus_faceai_identity'),
|
|
'return_forward_url' => rtrim((string) faceai_env('FACEAI_RETURN_FORWARD_URL', ''), '/')
|
|
);
|
|
|
|
return $config;
|
|
}
|
|
|
|
function faceai_base64url_encode($value)
|
|
{
|
|
return rtrim(strtr(base64_encode($value), '+/', '-_'), '=');
|
|
}
|
|
|
|
function faceai_base64url_decode($value)
|
|
{
|
|
$padding = strlen($value) % 4;
|
|
if ($padding > 0) {
|
|
$value .= str_repeat('=', 4 - $padding);
|
|
}
|
|
|
|
return base64_decode(strtr($value, '-_', '+/'));
|
|
}
|
|
|
|
function faceai_sign_payload(array $payload, $secret)
|
|
{
|
|
$body = faceai_base64url_encode(json_encode($payload));
|
|
$signature = hash_hmac('sha256', $body, $secret, true);
|
|
return $body . '.' . faceai_base64url_encode($signature);
|
|
}
|
|
|
|
function faceai_verify_payload($token, $secret)
|
|
{
|
|
if (!is_string($token) || strpos($token, '.') === false) {
|
|
throw new RuntimeException('Invalid token format.');
|
|
}
|
|
|
|
list($body, $signature) = explode('.', $token, 2);
|
|
$expected = faceai_base64url_encode(hash_hmac('sha256', $body, $secret, true));
|
|
|
|
if (!hash_equals($expected, $signature)) {
|
|
throw new RuntimeException('Invalid token signature.');
|
|
}
|
|
|
|
$decoded = faceai_base64url_decode($body);
|
|
$payload = json_decode($decoded, true);
|
|
|
|
if (!is_array($payload)) {
|
|
throw new RuntimeException('Invalid token payload.');
|
|
}
|
|
|
|
if (isset($payload['expiresAt']) && (int) $payload['expiresAt'] < (int) round(microtime(true) * 1000)) {
|
|
throw new RuntimeException('Token expired.');
|
|
}
|
|
|
|
return $payload;
|
|
}
|
|
|
|
function faceai_build_url($baseUrl, array $params)
|
|
{
|
|
return $baseUrl . (strpos($baseUrl, '?') === false ? '?' : '&') . http_build_query($params);
|
|
}
|
|
|
|
function faceai_redirect_with_error($returnUrl, $message, $title = 'Face ID non disponibile')
|
|
{
|
|
if (is_string($returnUrl) && trim($returnUrl) !== '') {
|
|
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
|
|
header('Pragma: no-cache');
|
|
header('Location: ' . faceai_build_url($returnUrl, array(
|
|
'faceaiError' => '1',
|
|
'faceaiErrorTitle' => $title,
|
|
'faceaiErrorMessage' => $message
|
|
)), true, 302);
|
|
exit;
|
|
}
|
|
|
|
faceai_render_message_page($title, $message, array(), 503);
|
|
}
|
|
|
|
function faceai_request_value($key, $default = '')
|
|
{
|
|
if (!isset($_GET[$key])) {
|
|
return $default;
|
|
}
|
|
|
|
if (is_array($_GET[$key])) {
|
|
return $default;
|
|
}
|
|
|
|
return trim((string) $_GET[$key]);
|
|
}
|
|
|
|
function faceai_html($value)
|
|
{
|
|
return htmlspecialchars((string) $value, ENT_QUOTES, 'UTF-8');
|
|
}
|
|
|
|
function faceai_resolve_identity(array $config)
|
|
{
|
|
if (!empty($_COOKIE[$config['identity_cookie']])) {
|
|
$payload = faceai_verify_payload($_COOKIE[$config['identity_cookie']], $config['shared_secret']);
|
|
if (($payload['type'] ?? '') !== 'legacy-identity') {
|
|
throw new RuntimeException('Unexpected identity cookie payload.');
|
|
}
|
|
|
|
return array(
|
|
'id' => (string) ($payload['userId'] ?? ''),
|
|
'displayName' => (string) ($payload['displayName'] ?? ''),
|
|
'email' => (string) ($payload['email'] ?? ''),
|
|
'membershipStatus' => (string) ($payload['membershipStatus'] ?? 'inactive')
|
|
);
|
|
}
|
|
|
|
if ($config['allow_dev_handoff']) {
|
|
$userId = faceai_request_value('devUserId');
|
|
if ($userId !== '') {
|
|
return array(
|
|
'id' => $userId,
|
|
'displayName' => faceai_request_value('devDisplayName', 'Local Test User'),
|
|
'email' => faceai_request_value('devEmail', 'local.test@example.invalid'),
|
|
'membershipStatus' => faceai_request_value('devMembershipStatus', 'active')
|
|
);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function faceai_render_message_page($title, $message, array $details = array(), $statusCode = 400)
|
|
{
|
|
http_response_code($statusCode);
|
|
header('Content-Type: text/html; charset=UTF-8');
|
|
|
|
echo '<!doctype html><html lang="it"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">';
|
|
echo '<title>' . faceai_html($title) . '</title>';
|
|
echo '<style>body{font-family:Georgia,serif;background:#f7f1e8;color:#2a231b;margin:0;padding:32px}main{max-width:900px;margin:0 auto;background:#fff;border:1px solid #ddcbb5;padding:24px}h1{margin-top:0}code{background:#f2ece2;padding:2px 5px}ul{padding-left:20px}li{margin:8px 0}</style>';
|
|
echo '</head><body><main>';
|
|
echo '<h1>' . faceai_html($title) . '</h1>';
|
|
echo '<p>' . faceai_html($message) . '</p>';
|
|
|
|
if (!empty($details)) {
|
|
echo '<ul>';
|
|
foreach ($details as $detail) {
|
|
echo '<li>' . faceai_html($detail) . '</li>';
|
|
}
|
|
echo '</ul>';
|
|
}
|
|
|
|
echo '</main></body></html>';
|
|
exit;
|
|
}
|
|
|
|
function faceai_fetch_json($url)
|
|
{
|
|
$context = stream_context_create(array(
|
|
'http' => array(
|
|
'ignore_errors' => true,
|
|
'timeout' => 10
|
|
)
|
|
));
|
|
|
|
$response = @file_get_contents($url, false, $context);
|
|
if ($response === false) {
|
|
throw new RuntimeException('Unable to fetch remote FaceAI data.');
|
|
}
|
|
|
|
$statusCode = 0;
|
|
if (!empty($http_response_header[0]) && preg_match('/\s(\d{3})\s/', $http_response_header[0], $matches)) {
|
|
$statusCode = (int) $matches[1];
|
|
}
|
|
|
|
$payload = json_decode($response, true);
|
|
if (!is_array($payload)) {
|
|
throw new RuntimeException('FaceAI returned invalid JSON.');
|
|
}
|
|
|
|
if ($statusCode >= 400) {
|
|
throw new RuntimeException($payload['error'] ?? ('FaceAI bridge request failed with status ' . $statusCode . '.'));
|
|
}
|
|
|
|
return $payload;
|
|
}
|