Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions app/backend/src/common/security/csp-report.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Controller, Post, Body, Version } from '@nestjs/common';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { Public } from '../decorators/public.decorator';
import { LoggerService } from '../../logger/logger.service';
import { API_VERSIONS } from '../constants/api-version.constants';

@ApiTags('CSP')
@Controller('csp-report')
export class CspReportController {
constructor(private readonly loggerService: LoggerService) {}

@Public()
@Post()
@Version(API_VERSIONS.V1)
@ApiOperation({
summary: 'Receive CSP violation reports',
description: 'Endpoint to receive and log Content Security Policy violations.',
})
handleCspReport(@Body() report: any) {
this.loggerService.warn(
'CSP violation reported',
'CspReportController',
{ cspReport: report },
);

return { status: 'ok' };
}
}
9 changes: 8 additions & 1 deletion app/backend/src/common/security/security.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import type { NextFunction, Request, RequestHandler, Response } from 'express';
import helmet, { HelmetOptions } from 'helmet';
import { RedisService } from '@liaoliaots/nestjs-redis';

import { CspReportController } from './csp-report.controller';
import { LoggerModule } from '../../logger/logger.module';


const DEFAULT_ALLOWED_ORIGINS = [
'http://localhost:3000',
Expand Down Expand Up @@ -89,6 +92,7 @@ const buildHelmetOptions = (config: ConfigService): HelmetOptions => {
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
reportUri: '/api/v1/csp-report',
},
}
: false,
Expand Down Expand Up @@ -313,5 +317,8 @@ export const createRateLimiter = (
* If cookie-based session management or any browser-managed credentials are introduced
* in the future, CSRF protection middleware MUST be implemented.
*/
@Module({})
@Module({
imports: [LoggerModule],
controllers: [CspReportController],
})
export class SecurityModule {}
16 changes: 16 additions & 0 deletions app/frontend/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1994,6 +1994,22 @@
]
}
},
"/api/v1/csp-report": {
"post": {
"description": "Endpoint to receive and log Content Security Policy violations.",
"operationId": "CspReportController_handleCspReport_v1",
"parameters": [],
"responses": {
"201": {
"description": ""
}
},
"summary": "Receive CSP violation reports",
"tags": [
"CSP"
]
}
},
"/api/v1/test-error/generic": {
"get": {
"operationId": "TestErrorController_getGenericError_v1",
Expand Down
3 changes: 3 additions & 0 deletions app/frontend/src/components/LanguageSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ const localeNames: Record<Locale, string> = {
en: 'English',
es: 'Español',
fr: 'Français',
'fr-CA': 'Français (Canada)',
'pt-BR': 'Português (Brasil)',
sw: 'Kiswahili',
};

export function LanguageSelector() {
Expand Down
8 changes: 7 additions & 1 deletion app/frontend/src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ import { getRequestConfig } from 'next-intl/server';
import en from './messages/en.json';
import es from './messages/es.json';
import fr from './messages/fr.json';
import frCA from './messages/fr-CA.json';
import ptBR from './messages/pt-BR.json';
import sw from './messages/sw.json';

export const locales = ['en', 'es', 'fr'] as const;
export const locales = ['en', 'es', 'fr', 'fr-CA', 'pt-BR', 'sw'] as const;
export type Locale = (typeof locales)[number];

const messages = {
en,
es,
fr,
'fr-CA': frCA,
'pt-BR': ptBR,
sw,
};

export default getRequestConfig(async ({ requestLocale }) => {
Expand Down
196 changes: 196 additions & 0 deletions app/frontend/src/messages/fr-CA.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
{
"common": {
"chainforge": "ChainForge",
"loading": "Chargement...",
"error": "Erreur",
"success": "Succès",
"cancel": "Annuler",
"confirm": "Confirmer",
"retry": "Réessayer",
"close": "Fermer",
"back": "Retour",
"next": "Suivant",
"previous": "Précédent",
"save": "Sauvegarder",
"delete": "Supprimer",
"edit": "Modifier",
"view": "Voir",
"share": "Partager",
"download": "Télécharger",
"copy": "Copier",
"copied": "Copié",
"search": "Rechercher",
"filter": "Filtrer",
"sort": "Trier",
"status": "Statut",
"date": "Date",
"time": "Heure",
"amount": "Montant",
"description": "Description",
"name": "Nom",
"email": "E-mail",
"phone": "Téléphone",
"address": "Adresse"
},
"navigation": {
"home": "Accueil",
"dashboard": "Tableau de bord",
"campaigns": "Campagnes",
"claims": "Réclamations",
"settings": "Paramètres",
"profile": "Profil",
"logout": "Déconnexion",
"role": "Rôle",
"wallet": "Portefeuille",
"environment": "Environnement",
"activity": "Activité",
"homeDescription": "Aperçu de la plateforme et points forts de l'aide.",
"dashboardDescription": "Suivre les colis d'aide et l'activité de distribution.",
"campaignsDescription": "Créer et gérer les campagnes de financement des ONG.",
"claimsDescription": "Voir et gérer vos réclamations d'aide.",
"settingsDescription": "Configurer votre compte et vos préférences.",
"profileDescription": "Gérer les informations de votre profil."
},
"roles": {
"guest": "Invité",
"client": "Client",
"operator": "Opérateur",
"ngo": "ONG",
"admin": "Administrateur"
},
"activity": {
"center": "Centre d'Activité",
"noRecentActivity": "Aucune activité récente",
"clearCompleted": "Effacer les terminés",
"remove": "Supprimer l'activité",
"viewTransaction": "Voir la transaction",
"preparingTransaction": "Préparation de la transaction...",
"transactionCompleted": "Transaction terminée",
"transactionFailed": "Transaction échouée",
"processing": "Traitement...",
"completedSuccessfully": "Terminé avec succès",
"failed": "Échoué",
"retrying": "Nouvelle tentative..."
},
"verification": {
"submitEvidence": "Soumettre des Preuves pour Vérification",
"analyzing": "Analyse...",
"result": "Résultat",
"upload": "Téléverser",
"evidenceRequired": "Veuillez fournir au moins une preuve — une image ou une description textuelle.",
"imageTooLarge": "L'image doit être de 5 Mo ou moins.",
"invalidImageType": "Seules les images PNG, JPEG et WebP sont acceptées.",
"textTooShort": "Le texte doit contenir au moins 20 caractères.",
"verificationInProgress": "Vérification en cours...",
"verificationComplete": "Vérification terminée",
"verificationFailed": "Vérification échouée"
},
"campaigns": {
"createCampaign": "Créer une Campagne",
"campaignName": "Nom de la Campagne",
"budget": "Budget",
"token": "Jeton",
"expiryDate": "Date d'Expiration",
"description": "Description",
"create": "Créer",
"creating": "Création de la campagne...",
"campaignCreated": "Campagne créée avec succès",
"campaignCreationFailed": "Échec de la création de la campagne",
"noCampaigns": "Aucune campagne trouvée",
"loadingCampaigns": "Chargement des campagnes...",
"errorFetchingCampaigns": "Erreur lors de la récupération des campagnes"
},
"claims": {
"claimReceipt": "Reçu de Réclamation",
"viewReceipt": "Voir et partager votre preuve de réclamation",
"loadingReceipt": "Chargement de votre reçu...",
"claimId": "ID de Réclamation",
"packageId": "ID de Colis",
"recipient": "Destinataire",
"status": "Statut",
"amount": "Montant",
"timestamp": "Horodatage",
"transactionHash": "Hash de Transaction",
"viewOnExplorer": "Voir sur l'Explorateur"
},
"wallet": {
"connect": "Connecter le Portefeuille",
"disconnect": "Déconnecter",
"connecting": "Connexion...",
"connected": "Connecté",
"notConnected": "Non Connecté",
"network": "Réseau",
"publicKey": "Clé Publique",
"balance": "Solde",
"send": "Envoyer",
"receive": "Recevoir",
"transactionHistory": "Historique des Transactions"
},
"errors": {
"generic": "Une erreur inattendue s'est produite. Veuillez réessayer.",
"networkError": "Impossible d'accéder au service. Vérifiez votre connexion et réessayez.",
"validationError": "Vérifiez vos données et réessayez.",
"unauthorized": "Vous n'êtes pas autorisé à effectuer cette action.",
"notFound": "La ressource demandée n'a pas été trouvée.",
"serverError": "Erreur du serveur. Veuillez réessayer plus tard."
},
"status": {
"pending": "En attente",
"processing": "Traitement",
"verified": "Vérifié",
"approved": "Approuvé",
"disbursed": "Décaissé",
"archived": "Archivé",
"requested": "Demandé",
"completed": "Terminé",
"failed": "Échoué",
"active": "Actif",
"inactive": "Inactif",
"draft": "Brouillon",
"paused": "En pause"
},
"dates": {
"today": "Aujourd'hui",
"yesterday": "Hier",
"tomorrow": "Demain",
"justNow": "À l'instant",
"minutesAgo": "Il y a {{count}} minutes",
"hoursAgo": "Il y a {{count}} heures",
"daysAgo": "Il y a {{count}} jours",
"weeksAgo": "Il y a {{count}} semaines",
"monthsAgo": "Il y a {{count}} mois",
"yearsAgo": "Il y a {{count}} ans"
},
"demoChecklist": {
"title": "Liste de vérification démo Testnet",
"subtitle": "Suivez ces étapes pour vérifier le flux de démonstration ChainForge de bout en bout sur Testnet. Cochez chaque étape au fur et à mesure.",
"progress": "{{completed}} sur {{total}} étapes terminées",
"allComplete": "Toutes les étapes sont terminées — le flux de démonstration est vérifié !",
"stepConnectWallet": "Connectez votre portefeuille Freighter",
"stepConnectWalletDesc": "Ouvrez la page d'accueil et cliquez sur \"Connecter le Portefeuille Freighter\" dans la barre de navigation. Approuvez la connexion dans la fenêtre popup de l'extension Freighter. Assurez-vous que Freighter est configuré sur Testnet.",
"stepViewCampaign": "Consulter une campagne active",
"stepViewCampaignDesc": "Accédez à la page Campagnes et ouvrez une campagne active. Vérifiez que les détails, le budget et le statut de la campagne s'affichent correctement.",
"stepSubmitClaim": "Soumettre une réclamation test",
"stepSubmitClaimDesc": "Depuis la page de détail d'une campagne ou le flux de vérification, soumettez une réclamation en utilisant un texte de preuve exemple. Confirmez que la soumission réussit.",
"stepVerifyReceipt": "Vérifier le reçu de réclamation",
"stepVerifyReceiptDesc": "Ouvrez la page de reçu de réclamation et vérifiez qu'il affiche l'ID de réclamation, l'ID de colis, le montant et le statut on-chain corrects. Confirmez que le reçu peut être partagé ou téléchargé.",
"goHome": "Aller à l'Accueil",
"goCampaigns": "Ouvrir les Campagnes",
"goClaimReceipt": "Ouvrir le Reçu",
"systemHealth": "Santé du Système",
"healthOk": "Sain",
"healthDegraded": "Dégradé",
"healthDown": "Hors service",
"healthChecking": "Vérification…",
"lastChecked": "Dernière vérification",
"prerequisites": "Prérequis",
"prereqFreighter": "Extension Freighter installée",
"prereqWallet": "Portefeuille connecté",
"prereqBackend": "API backend accessible",
"quickLinks": "Liens Rapides",
"helpPage": "Guide du Contributeur",
"dashboardPage": "Tableau de Bord",
"verificationReviewPage": "Révision de Vérification",
"resetChecklist": "Réinitialiser la progression"
}
}
Loading
Loading