feat: maximus-api — license server core (Ed25519, no Stripe) #49
Labels
No labels
autopilot:pending-human
source:analyste
source:defenseur
source:human
source:medic
status:approved
status:blocked
status:in-progress
status:needs-clarification
status:needs-fix
status:ready
status:review
status:triage
type:bug
type:feature
type:infra
type:refactor
type:schema
type:security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: maximus/Simpl-Resultat#49
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Contexte
Serveur de licences backend. Scope réduit au core licence (sans Stripe) car le compte Stripe n'est pas encore créé. Les webhooks Stripe sont scindés dans #136.
Le code vit dans un nouveau repo Forgejo privé
maximus-api(licence propriétaire, hors monorepo GPL). Conçu dès le départ comme multi-produit : servira simpl-resultat d'abord, simpl-liste et futures apps ensuite (une seule API, un seul déploiement, un seul compte Stripe à terme).Ref :
spec-monetisation.md— Phase 2, Issue 4Dépend : rien — débloque #53 (activation en ligne, client déjà prêt dans PR #65).
Contrat client
Le client Rust (
src-tauri/src/commands/license_commands.rs) impose un contrat strict :SR-BASE-<JWT>ouSR-PREMIUM-<JWT>(préfixe obligatoire, validé parstrip_prefixen ligne 85-93).jsonwebtokencrate v9, validation stricte (exp+iatrequis,leeway: 0).sub(email),iss("lacompagniemaximus.com"),iat,exp,edition("base"|"premium"),features(array),machine_limit(u32).sub(license id/hash),iat,exp,machine_id(doit matcher le client au verify).Tâches
Infrastructure
maximus/maximus-api(licence propriétaire)src/core/— middleware auth, rate limit, PG pool, JWT signer Ed25519src/products/simpl-resultat/— clé publique, features, machine_limit par défautsrc/routes/licenses.ts— handlers paramétrés par produitsrc-tauri/src/commands/license_commands.rs:27-29et bumper la version desktopED25519_PRIVATE_KEY_PEMsur CoolifyBase de données
maximus-api-dbsur Coolifylicenses— colonneproductajoutée au schéma du spec (multi-produit)license_activationssubscriptions(table créée mais populée dans #136)Endpoints non-Stripe
POST /licenses/generate— admin-only (API key). Body :{product, user_email, edition, machine_limit, expires_in_days}. Retour :SR-BASE-<JWT>ouSR-PREMIUM-<JWT>. Utile pour licences manuelles, tests, support.POST /licenses/activate— Body :{license_key, machine_id, machine_name?}. Vérifiemachine_limit. Retour :{activation_token: "<JWT Ed25519 avec machine_id binding>"}.POST /licenses/verify— Body :{license_key}. Retour :{machines: [{machine_id, machine_name?, activated_at, last_seen_at}, ...]}.POST /licenses/deactivate— Body :{license_key, machine_id}. Libère un slot.POST /licenses/revoke— admin-only (API key). Body :{license_key, reason?}.GET /healthz— 200 OKSécurité
license_keyJWT ; endpoints admin via API key bearerexpobligatoire sur tous les JWT émis (CWE-613)machine_id(empêche la copie deactivation.tokenentre machines, CWE-copy)Déploiement
api.lacompagniemaximus.com(Traefik TLS, Let's Encrypt)DATABASE_URL,ED25519_PRIVATE_KEY_PEM,ADMIN_API_KEY,LOGTO_JWKS_URL,NODE_ENV/healthzTests
machine_limitmachine_limitatteinte → HTTP 409 + liste des machinesLicenseClaims,ActivationClaims,MachineInfodu clientFichiers concernés
Nouveau repo
maximus-api/— aucun fichier de ce repo modifié dans cette issue.Après livraison, suivi dans #53 (online activation) :
src-tauri/src/commands/license_commands.rs: clé publique mise à jour + éventuels ajustements si le format de réponse diverge.Critères d'acceptation
POST /licenses/activateavec clé valide +machine_idneuf retourne unactivation_tokenEd25519 validable par le clientmachine_limit=3retourne HTTP 409 avec la liste des machines existantesPOST /licenses/verifyretourne{machines: MachineInfo[]}au format attendu parlicense_commands.rslicense_commands.rs:27-29api.lacompagniemaximus.com/healthzretourne 200product(multi-produit ready)Suite
Complexité
Complex — nouveau repo, nouvelle DB, nouveau déploiement, crypto Ed25519, multi-produit dès le départ, sécurité critique.
feat: license server API (Node.js, proprietary)to feat: maximus-api — license server core (Ed25519, no Stripe)Progression
Scaffold initial livré — voir https://git.lacompagniemaximus.com/maximus/maximus-api
Commits initiaux (main) :
e8ac739scaffold Hono + Drizzle + PostgreSQLfe46b80crypto + route tests (10 passing)2e3d076/licenses/verify retournemachines[](match client)d718926rename maximus-api + refactor multi-produitc6a6abdREADME + script gen-ed25519.shCe qui est fait
maximus/maximus-apicréé et pushéproduct(multi-produit dès le départ)src/products/simpl-resultat/config.ts(features, limits, prefixes)/licenses/*filtrent par(product, license_key)/healthz,/licenses/{generate,activate,verify,deactivate,revoke}ADMIN_API_KEYsur generate/revokeproductdans JWT licence et activation tokenSR-BASE-<JWT>/SR-PREMIUM-<JWT>respectéscripts/gen-ed25519.shpour générer la paireCe qui reste à faire sur #49
./scripts/gen-ed25519.sh)simpl-resultat/src-tauri/src/commands/license_commands.rs:27-29et bump version desktopmaximus-api-dbsur Coolifynpm run db:generateavec DB accessible)api.lacompagniemaximus.com(Traefik TLS)/healthzNotes
simpl-resultatmodifié dans ce scaffold (la clé publique sera mise à jour dans une PR séparée au moment du bump desktop)Déployé en prod 2026-04-25
maximus-apiest UP àhttps://api.lacompagniemaximus.com.Smoke test complet (vert)
GET /healthz{"status":"ok"}POST /licenses/generate(admin auth)SR-BASE-<JWT>Ed25519POST /licenses/verify{valid, machines:[]}conforme au contrat clientPOST /licenses/activateactivation_tokenEd25519 avecmachine_idbindingproduct:"unknown-app"rateLimit.ts)Infra
maximus_api+ DBmaximus_apisur l'instance PG partagée existante (patternsimpliste). Économise les ressources VPS, isolation correcte via rôle dédié. Documenté dansla-compagnie-maximus/docs/postgres-ops.md.maximus-api, app uuidvscosooog08ogwwc8s48scs8. Build Dockerfile, deploy via SSH key RSA-4096, healthcheck sur/healthz.licensesavec colonneproductmulti-produit,license_activations,subscriptions).Gotchas rencontrés (tous documentés dans
coolify-ops.md)PermitRootLogin nodu sshd hardening bloquait Coolify →prohibit-passwordid.root@<server_uuid>vsssh_key@<key_uuid>mismatch → symlinkconvertGitUrlcassé surssh://→ format scp-like avec port inlineid_rsamême si ed25519 → utiliser RSA-4096ARGdu Dockerfile →\néchappésNODE_ENV=productionau buildtime fait skipper devDeps →tsc not found→ runtime-onlyReste à faire (post-#49)
/releasedesktop. Tant que pas fait, les clés émises par ce serveur ne peuvent pas être validées par le client en prod.Cette issue peut être close.