Lalabase
Tickets, Doku, Zeiterfassung und Kundengespräche an einem Ort, durchsuchbar per KI. Für Softwarefirmen mit Kundenprojekten. Open Source: auf deinem eigenen Server oder in unserer Cloud in Deutschland.
Translation pending
ScanHub API & Administration - Plateforme mutualisée de contrôle QR pour événements et partenaires
Translation pending
BusinessTypeScript · JavaScriptGitHub @ChaibouuOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
ScanHub - Plateforme Centralisée de Scan & Vérification QR (Next.js 15) API REST centralisée et panneau d'administration pour la gestion des QR codes, des billets, badges et contrôles d'accès événementiels (EventHub, JNJL et partenaires).Read the full README ↗
API REST centralisée et panneau d'administration pour la gestion des QR codes, des billets, badges et contrôles d'accès événementiels (EventHub, JNJL et partenaires).
POST /api/auth/login — vérification email/mot de passe
{ requires2FA: true, tempToken } (challenge 5 min)POST /api/auth/2fa/login — vérification TOTP → session complèteaccessToken (JWT 1h) + refreshToken (7j / 30j avec rememberMe)allowMultipleSessions dans settings/)Chaque organisateur dispose d'un espace fermé (table Organization). Ses membres ne voient
et ne modifient que ses événements, billets, scans, terminaux et clés API.
| Rôle | Portée |
|---|---|
SUPER_ADMIN | Propriétaire de la plateforme. N'appartient à aucun espace, voit tout, gère les espaces (/admin/organizations) |
ADMIN | Administrateur de son espace : équipe, événements, billets, terminaux, billetteries |
AGENT | Contrôle les entrées depuis l'application mobile, dans son espace |
USER | Consultation du tableau de bord de son espace |
/auth/signup) : crée l'espace et son premier administrateur./admin/users) : l'administrateur ajoute ses agents (mot de passe temporaire affiché une fois)./events/<id>) : saisie ou import CSV, QR codes à imprimer, export CSV, révocation./integrations) : une clé API par billetterie externe.settings/plans.ts) : tout est gratuit ; la formule FREE fixe des plafonds anti-abus
(événements ouverts, billets par événement, terminaux, membres). Le propriétaire peut passer un
espace en UNLIMITED ou le suspendre.Règle pour tout nouveau code : une requête Prisma sur une donnée métier passe toujours par les
filtres scope.* de lib/tenant.ts. Le cloisonnement est vérifié par tests/e2e/tenant.e2e.cjs.
Les tokens des billets créés dans ScanHub sont stockés hachés (vérification) et chiffrés
AES-256-GCM (tokenEnc, pour ré-imprimer le QR). Changer AES_SECRET_KEY rend ces QR
non ré-affichables (ils restent valides au scan).
L'API /api/v1 utilise la même authentification que le portail web (JWT RS256 chiffré
AES-256-GCM + session en base). Il n'existe plus de secret JWT séparé.
| Appelant | Identification |
|---|---|
| Agent (app mobile) | Authorization: Bearer <accessToken> + x-device-key: <clé du terminal> |
| Partenaire (EventHub, JNJL…) | x-api-key: <clé secrète> sur /api/v1/integrations/* |
| Tableau de bord | cookie de session (ex : export CSV) |
POST /api/v1/auth/login { email, password } → { user, tokens: { accessToken, refreshToken, expiresIn } }POST /api/v1/auth/refresh { refreshToken } → nouveau accessToken (l'ancien est aussitôt invalide)POST /api/v1/auth/logout → ferme la session courante uniquementPOST /api/v1/devices) :
un code d'activation à usage unique, valable 48 h, est affiché une seule fois.POST /api/v1/devices/activate renvoie la clé du terminal
(deviceKey), à envoyer ensuite dans l'en-tête x-device-key.403, DEVICE_BLOCKED).Un compte AGENT ne peut scanner que depuis un terminal activé.
POST /api/v1/qr/verify — lecture seule.POST /api/v1/qr/consume — enregistre le passage. Pour un billet à usage unique, la transition
ACTIVE → USED est atomique : deux scans simultanés ne peuvent pas être validés tous les deux.
clientId (UUID) rend l'appel idempotent.GET /api/v1/sync/pull?eventId=… — annuaire d'un événement pour le cache hors-ligne
(tokenHash = SHA-256 du token, recalculable par le terminal).POST /api/v1/sync/push — remontée des scans hors-ligne. Le serveur revalide chaque scan ;
un billet déjà consommé ailleurs est renvoyé en CONFLICT./api/v1/integrations)Pour créer et vérifier des QR codes depuis une autre plateforme, avec la seule clé API
(en-tête x-api-key). Tout reste dans l'espace de la clé.
| Méthode | Chemin | Rôle |
|---|---|---|
GET / POST | /events | Lister / créer des événements |
POST | /qrs | Créer un code. Idempotent sur externalId : rejouer rend le même code (200, created: false) |
POST | /qrs/batch | Jusqu'à 500 codes par appel, même idempotence |
GET | /qrs/:id | Détail (:id = identifiant ScanHub ou externalId) |
GET | /qrs/:id/image | Image du QR (?format=png|svg, ?size=) |
POST | /qrs/:id/revoke, /qrs/:id/restore | Révoquer / rétablir |
POST | /verify | Vérifier sans consommer |
POST | /consume | Valider un passage (idempotencyKey ou en-tête Idempotency-Key) |
GET | /scans | Présences, lecture incrémentale par curseur (?after=) |
token), à
condition qu'il soit long et aléatoire ; les valeurs devinables sont refusées (422).tests/e2e/developer-api.e2e.mjs.ScanHub appelle l'adresse du client à chaque scan enregistré (événement scan.created).
Gestion dans Billetteries (API) ou via GET|POST /webhooks, GET|DELETE /webhooks/:id,
POST /webhooks/:id/test.
ScanHub-Signature: t=<horodatage>,v1=<HMAC-SHA256 de "t.corps">, avec le
secret whsec_… donné une seule fois à la création.WebhookDelivery)./api/cron/webhooks chaque minute avec
Authorization: Bearer $CRON_SECRET. Sans cela, les nouvelles tentatives ne partent qu'à
l'occasion des scans suivants du même espace.La documentation publique destinée aux clients est servie sur /docs.
Générées depuis Partenaires (API) (bouton « Générer / Régénérer la clé API »). Seul le hash est stocké : la clé n'est visible qu'au moment de sa génération.
tempToken 2FA limité à 5 minutes avec purpose: "2fa_challenge" dans le payload| Couche | Scope | Limite |
|---|---|---|
| In-memory (middleware/Edge) | Global par IP | 20 req / 60s |
| Redis sliding window (API routes) | Auth par IP | 10 tentatives / 15 min |
| Redis sliding window (API routes) | Signup/Email par IP | 5 req / 1h |
'strict-dynamic' + 'nonce-{nonce}' — aucun inline script autorisé sans nonceX-Frame-Options, X-Content-Type-Options, HSTS, Permissions-PolicyAdd-Padding: true pour masquer la taille de la réponseChaque action sensible est tracée dans AuditLog (Prisma) :
LOGIN_SUCCESS, LOGIN_FAILURE, LOGOUT, SIGNUP, 2FA_ENABLED, 2FA_DISABLED, 2FA_FAILURE, 2FA_SUCCESS, PASSWORD_RESET, PASSWORD_CHANGED, IMPERSONATION_START, IMPERSONATION_END, EMAIL_CHANGED, ACCOUNT_DISABLED, ACCOUNT_DELETED
Permet à un ADMIN d'auditer un compte utilisateur sans connaître son mot de passe.
POST /api/admin/impersonate { targetUserId } Authorization: Bearer <adminToken>
DELETE /api/admin/impersonate/end Authorization: Bearer <impersonationToken>
impersonationToken séparé (httpOnly) est crééstartImpersonation(targetUserId) / endImpersonation() (cookies gérés côté serveur uniquement)import { logger } from "@/lib/logger";
logger.logAuth("LOGIN_SUCCESS", userId, email);
logger.logRequest("GET", "/api/health", 200, 12);
logger.logDatabase("query", "user.findUnique", 8);
logger.logSecurity("RATE_LIMIT", ip, "Trop de tentatives");
LOG_LEVEL (env)GET /api/health
Retourne 200 { status: "ok" } si DB + Redis répondent, 503 { status: "degraded" } sinon. Utile pour les probes Kubernetes / load balancers.
ScanHub-Back-End/
├── app/
│ ├── (dashboard)/
│ │ ├── admin/
│ │ │ └── users/ # Interface d'impersonation
│ │ └── …
│ ├── api/
│ │ ├── admin/impersonate/ # POST + DELETE /end
│ │ ├── auth/
│ │ │ ├── login/
│ │ │ ├── signup/
│ │ │ ├── 2fa/
│ │ │ │ ├── setup/ # GET — génère QR code TOTP
│ │ │ │ ├── verify-setup/ # POST — confirme enrollment
│ │ │ │ └── login/ # POST — étape 2 connexion
│ │ │ ├── refresh/
│ │ │ ├── logout/
│ │ │ ├── forgot-password/
│ │ │ ├── reset-password/
│ │ │ ├── change-password/
│ │ │ ├── change-email/
│ │ │ └── verify/
│ │ ├── health/ # GET — healthcheck DB + Redis
│ │ ├── profile/
│ │ ├── upload/
│ │ └── v1/ # Endpoints REST ScanHub (QR, Sync, Scans, Events, Devices)
│ └── layout.tsx # Lit x-nonce pour CSP
├── actions/
│ ├── impersonate.ts # Server Actions (cookies httpOnly)
│ └── getUser.ts # Vérifie impersonationToken en priorité
├── components/
│ ├── ImpersonationBanner.tsx # Bannière admin session
│ ├── admin/ImpersonateButton.tsx
│ └── …
├── lib/
│ ├── tokens.ts # JWT RS256 + AES-256-GCM
│ ├── rateLimit.ts # In-memory + Redis sliding window
│ ├── redis.ts # Singleton ioredis
│ ├── audit.ts # AuditLog helper
│ ├── hibp.ts # HIBP k-anonymity
│ ├── logger.ts # Pino wrapper
│ ├── mail.ts # Nodemailer (login notif, vérif, reset)
│ ├── upload.ts # Magic number + UUID filename
│ └── geo.ts # IP → géolocalisation
├── context/SessionContext.tsx
├── middleware.ts # CSP nonce + rate limiting Edge
├── settings/index.ts # Config centralisée
└── prisma/schema.prisma
git clone <repository-url>
cd ScanHub-Back-End
npm install
cp .env.example .env.local
# Remplir les variables (voir section ci-dessous)
docker compose up -d # PostgreSQL + Redis locaux (optionnel)
npx prisma migrate deploy # Applique les migrations versionnées
npm run seed # Données de test (dev uniquement, voir ci-dessous)
npm run dev
Le seed crée trois comptes (super admin, admin, agent). Leurs mots de passe viennent de
SEED_SUPER_ADMIN_PASSWORD, SEED_ADMIN_PASSWORD et SEED_AGENT_PASSWORD ; s'ils sont absents,
un mot de passe aléatoire est généré et affiché une seule fois dans la console. Le seed refuse de
s'exécuter avec NODE_ENV=production.
SEED_ADMIN_PASSWORD='…' SEED_AGENT_PASSWORD='…' npm run seed
# Base de données PostgreSQL
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/scanhub?schema=public"
# Application
NEXT_PUBLIC_APP_URL="http://localhost:3000"
# JWT (RS256) — générer avec openssl
RSA_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n…"
RSA_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n…"
# AES-256-GCM — 32 octets aléatoires en hexadécimal (64 caractères)
AES_SECRET_KEY="hex-encoded-32-bytes"
# Redis
REDIS_URL="redis://localhost:6379"
# Peppers de hachage — OBLIGATOIRES en production (32 octets aléatoires en hex)
# QR_PEPPER : hash des tokens QR. API_KEY_PEPPER : clés partenaires, clés de terminaux,
# codes d'activation. Les changer invalide les valeurs déjà enregistrées.
QR_PEPPER="…"
API_KEY_PEPPER="…"
# Email (Nodemailer)
MAIL_AUTH_USER="[email protected]"
MAIL_AUTH_PASSWORD="your-app-password"
MAIL_HOST="smtp.gmail.com"
MAIL_PORT="587"
# Optionnel
LOG_LEVEL="debug" # trace | debug | info | warn | error
openssl genrsa -out private.pem 2048
openssl rsa -in private.pem -pubout -out public.pem
node scripts/generate-aes-key.js
npm run dev # Développement
npm run build # Build production
npm run start # Serveur production
npm run lint # ESLint
npm run type-check # TypeScript
npm run db:migrate # Crée + applique une migration (dev)
npx prisma migrate deploy # Applique les migrations (production / CI)
npm run db:studio # Prisma Studio
npm run seed # Seed base de données
npm test # Tests Jest
npm run test:e2e # Tests de bout en bout (serveur lancé + base locale, voir tests/e2e/run.cjs)
npm run test:coverage # Couverture
docker build -t scanhub-backend .
docker run -p 3000:3000 \
-e DATABASE_URL="…" \
-e NEXT_PUBLIC_APP_URL="…" \
-e RSA_PRIVATE_KEY="…" \
-e RSA_PUBLIC_KEY="…" \
-e AES_SECRET_KEY="…" \
-e QR_PEPPER="…" \
-e API_KEY_PEPPER="…" \
-e REDIS_URL="…" \
scanhub-backend
L'image n'applique pas les migrations au démarrage : lancer npx prisma migrate deploy depuis
le job de déploiement avant de démarrer le conteneur.
MIT — voir LICENSE.
Développé pour ScanHub