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.
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证正在加载完整 README。 以下为已保存的摘录。
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).查看完整 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