diff --git a/scripts/validate-security-hardening.mjs b/scripts/validate-security-hardening.mjs index a80dfba..7341374 100644 --- a/scripts/validate-security-hardening.mjs +++ b/scripts/validate-security-hardening.mjs @@ -193,6 +193,7 @@ const cspHeader = allHeaders.find((header) => header.key === 'Content-Security-P 'https://*.firebaseio.com', 'https://*.firebaseapp.com', 'https://*.appspot.com', + 'https://*.cloudfunctions.net', 'https://www.googletagmanager.com', "object-src 'none'", "base-uri 'self'", diff --git a/src/components/auth/LoginPage.jsx b/src/components/auth/LoginPage.jsx index 7f442f2..22881bc 100644 --- a/src/components/auth/LoginPage.jsx +++ b/src/components/auth/LoginPage.jsx @@ -229,9 +229,19 @@ export default function LoginPage() { {message ? ( -

- {message} -

+
+

+ {message} +

+ {message.includes('καταστήματα') ? ( + + Μετάβαση στα Καταστήματα → + + ) : null} +
) : null} )} diff --git a/src/firebase/authBrokerService.js b/src/firebase/authBrokerService.js index 6c34f00..c5ec944 100644 --- a/src/firebase/authBrokerService.js +++ b/src/firebase/authBrokerService.js @@ -9,24 +9,91 @@ const SAFE_BROKER_ERROR = 'Δεν ήταν δυνατή η ασφαλής μετ const TICKET_PATTERN = /^[a-f0-9]{64}$/i; export function classifyBrokerError(err, prefix = 'BROKER_CREATE') { + if (err?.category && typeof err.category === 'string') { + return err.category; + } + const code = String(err?.code || '').toLowerCase(); const message = String(err?.message || '').toLowerCase(); - - if (code.includes('unauthenticated') || message.includes('missing-auth') || message.includes('unauthenticated')) { + const reason = String(err?.details?.reason || err?.reason || '').toLowerCase(); + + // Authentication errors + if ( + code.includes('unauthenticated') || + reason.includes('missing-auth') || + message.includes('missing-auth') || + message.includes('unauthenticated') + ) { return `${prefix}_UNAUTHENTICATED`; } - if (code.includes('permission-denied') || message.includes('permission-denied') || message.includes('forbidden')) { + + // Permission / Authorization errors + if ( + code.includes('permission-denied') || + reason.includes('invalid-central-origin') || + reason.includes('tenant-origin-mismatch') || + reason.includes('missing-membership') || + reason.includes('membership-mismatch') || + reason.includes('inactive-or-invalid-membership') || + reason.includes('platform-admin-tenant-access-forbidden') || + reason.includes('cross-family-redirect-not-allowed') || + message.includes('permission-denied') || + message.includes('forbidden') + ) { return `${prefix}_PERMISSION_DENIED`; } - if (code.includes('invalid-argument') || message.includes('invalid-argument') || message.includes('missing-return-to')) { + + // Invalid argument / Format errors + if ( + code.includes('invalid-argument') || + reason.includes('missing-return-to') || + reason.includes('invalid-protocol') || + reason.includes('central-return-not-allowed') || + reason.includes('unknown-tenant-host') || + reason.includes('tenant-mismatch') || + reason.includes('tenant-not-allowed') || + reason.includes('path-not-allowed') || + reason.includes('invalid-url') || + message.includes('invalid-argument') || + message.includes('missing-return-to') + ) { return `${prefix}_INVALID_ARGUMENT`; } - if (code.includes('unavailable') || code.includes('network') || message.includes('network') || message.includes('offline') || message.includes('failed to fetch')) { + + // Specific custom errors + if (message.includes('invalid-redirect-url') || reason.includes('invalid-redirect-url')) { + return `${prefix}_INVALID_REDIRECT`; + } + if (message.includes('invalid-ticket-format') || message.includes('invalid-ticket')) { + return `${prefix}_INVALID_TICKET`; + } + if (message.includes('missing-custom-token')) { + return `${prefix}_MISSING_CUSTOM_TOKEN`; + } + + // Network / Connection / Availability / CSP / fetch failures + if ( + code.includes('unavailable') || + code.includes('network') || + code.includes('timeout') || + message.includes('network') || + message.includes('offline') || + message.includes('failed to fetch') || + message.includes('load failed') || + (code === 'functions/internal' && message === 'internal' && !err?.details) + ) { return `${prefix}_NETWORK`; } - if (code.includes('not-found') || message.includes('tenant-not-found')) { + + // Not found + if ( + code.includes('not-found') || + reason.includes('tenant-not-found') || + message.includes('tenant-not-found') + ) { return `${prefix}_NOT_FOUND`; } + return `${prefix}_INTERNAL`; } diff --git a/vercel.json b/vercel.json index 2a13e5f..d287a43 100644 --- a/vercel.json +++ b/vercel.json @@ -25,7 +25,7 @@ }, { "key": "Content-Security-Policy", - "value": "default-src 'self'; script-src 'self' https://www.googletagmanager.com; connect-src 'self' https://*.googleapis.com https://*.firebaseio.com https://*.firebaseapp.com https://*.appspot.com https://*.google-analytics.com https://www.google-analytics.com https://region1.google-analytics.com https://analytics.google.com https://app-measurement.com wss://*.firebaseio.com wss://*.googleapis.com; img-src 'self' data: blob: https:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; frame-src 'self' https://*.firebaseapp.com https://accounts.google.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests" + "value": "default-src 'self'; script-src 'self' https://www.googletagmanager.com; connect-src 'self' https://*.googleapis.com https://*.firebaseio.com https://*.firebaseapp.com https://*.appspot.com https://*.cloudfunctions.net https://*.run.app https://*.google-analytics.com https://www.google-analytics.com https://region1.google-analytics.com https://analytics.google.com https://app-measurement.com wss://*.firebaseio.com wss://*.googleapis.com; img-src 'self' data: blob: https:; style-src 'self' 'unsafe-inline'; font-src 'self' data:; frame-src 'self' https://*.firebaseapp.com https://accounts.google.com; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests" } ] },