32 KiB
Acadenice Patches
Liste des patches custom appliques sur le fork Acadenice de Docmost. Ce document est maintenu manuellement pour faciliter le rebase upstream.
Repo upstream : github.com/docmost/docmost
Branche fork : acadenice/main
Conventions
- Chaque patch est commit isole avec scope
feat(rebrand)/feat(custom)/ etc. - Les modifications in-line de fichiers upstream sont documentees ici avec rationale.
- Les nouveaux fichiers (extensions Tiptap custom, hooks, etc.) vont dans des emplacements dedies pour minimiser les conflits de rebase.
Patch 001 — Rebrand minimal "Docmost" -> "DocAdenice"
Date : 2026-05-07 Scope : strings UI visibles utilisateur uniquement Rationale : nom temporaire pour les beta-testeurs en attendant le vrai rebranding (logo SVG + design system + manifest PWA). Conserve les identifiants techniques pour ne rien casser et faciliter le rebase upstream.
Fichiers modifies
| Fichier | Avant | Apres |
|---|---|---|
apps/client/index.html |
<title>Docmost</title> |
<title>DocAdenice</title> |
apps/client/index.html |
apple-mobile-web-app-title content="Docmost" |
content="DocAdenice" |
apps/client/src/lib/config.ts |
getAppName() return "Docmost" |
return "DocAdenice" |
apps/client/src/components/layouts/global/app-header.tsx |
brand aria-label, alt, texte Docmost |
DocAdenice |
apps/client/src/features/auth/components/auth-layout.tsx |
brand alt, texte Docmost |
DocAdenice |
apps/client/src/components/ui/error-404.tsx |
titre 404 - Docmost |
- DocAdenice |
apps/client/src/features/home/components/home-ai-prompt.tsx |
fallback workspace name "Docmost" |
"DocAdenice" |
apps/server/src/integrations/transactional/emails/invitation-email.tsx |
"You have been invited to Docmost." |
"...DocAdenice." |
apps/server/src/integrations/transactional/partials/partials.tsx |
footer © Docmost |
© DocAdenice |
apps/server/src/core/workspace/services/workspace-invitation.service.ts |
sujet ... has accepted your Docmost invite |
... DocAdenice invite |
apps/server/src/core/workspace/services/workspace-invitation.service.ts |
sujet ... invited you to Docmost |
... DocAdenice |
apps/server/src/integrations/environment/environment.service.ts |
MAIL_FROM_NAME default 'Docmost' |
'DocAdenice' |
README.md |
header initial Docmost | bloc "DocAdenice" ajoute au-dessus |
KEEP volontairement (non modifies)
| Element | Raison |
|---|---|
package.json name: "docmost" |
nom du package npm interne, casserait les imports/scripts Nx |
@docmost/editor-ext workspace package |
identifiant pnpm workspace |
docker-compose.yml service docmost |
identifiant technique |
apps/server/src/core/auth/token.module.ts JWT issuer 'Docmost' |
changer invaliderait les tokens existants |
apps/server/src/core/workspace/workspace.constants.ts 'docmost' dans DISALLOWED_HOSTNAMES |
blacklist hostnames reserves, technique |
apps/server/src/common/helpers/types/export-metadata.types.ts source: 'docmost' |
format export pour interop avec Docmost officiel |
apps/server/src/integrations/export/export.service.ts filename docmost-metadata.json |
format export, interop |
apps/server/src/integrations/import/services/file-import-task.service.ts (vars docmostMetadata, prefix docmost-import, fonction readDocmostMetadata) |
identifiants techniques + lecture du format export Docmost |
apps/server/src/integrations/import/utils/import.utils.ts readDocmostMetadata |
API publique du module import |
apps/server/src/integrations/security/version.service.ts URL github.com/docmost/docmost/releases |
check de version vs upstream officiel |
apps/server/src/integrations/telemetry/telemetry.service.ts endpoint tel.docmost.com |
telemetry upstream (a desactiver dans une iteration future via env var) |
apps/client/src/components/settings/settings-sidebar.tsx help@docmost.com |
email support upstream officiel, on n'usurpe pas |
apps/client/src/components/settings/app-version.tsx URL releases |
check de version upstream |
apps/client/src/ee/** (license, AI, MCP, API keys, share-branding "Powered by Docmost") |
code Enterprise Edition propriete Docmost — copy commerciale, ne pas masquer |
apps/client/src/ee/components/posthog-user.tsx source: "docmost-app" |
identifiant analytics upstream |
apps/server/src/integrations/environment/environment.validation.ts URL clickhouse exemple |
message d'erreur dev-facing technique |
apps/server/src/core/workspace/services/workspace.service.ts @deleted.docmost.com |
placeholder technique pour soft-delete |
Patch 002 — Bloc 4b : OIDC client (Authentik) via openid-client
Date : 2026-05-07
Scope : nouveau flow d'authentification SSO via Authentik (ou tout IdP OIDC), desactive par defaut
Rationale : preparer l'integration SSO pour le hub Acadenice. Le code est dormant tant que OIDC_ENABLED=true n'est pas pose, donc zero impact sur les deploiements actuels. Les fichiers sont isoles dans un sous-dossier dedie pour faciliter le rebase upstream.
Lib utilisee
openid-client v6.8.2 — deja en dependance dans apps/server/package.json. API fonctionnelle (pas un client object-oriented), import lazy au boot pour eviter l'overhead quand OIDC est off.
Fichiers crees
| Fichier | Role |
|---|---|
apps/server/src/core/auth/oidc/oidc.module.ts |
Module Nest dedie, importe par CoreModule |
apps/server/src/core/auth/oidc/oidc.service.ts |
Discovery, PKCE, callback handler, JIT provisioning |
apps/server/src/core/auth/oidc/oidc.controller.ts |
Routes /api/auth/oidc/login, /callback, /status |
apps/server/src/core/auth/oidc/oidc.service.spec.ts |
8 tests unitaires (Jest) avec openid-client mocke |
apps/client/src/features/auth/queries/oidc-query.ts |
Hook useOidcStatus() (React Query) |
apps/client/src/features/auth/components/oidc-login-button.tsx |
Bouton SSO conditionnel sur le formulaire login |
Fichiers modifies (touches minimales)
| Fichier | Modification |
|---|---|
apps/server/src/integrations/environment/environment.service.ts |
+9 getters OIDC (isOidcEnabled, getOidcIssuer, ...) appendus en fin de classe |
apps/server/src/core/core.module.ts |
+1 import + 1 ligne dans imports[] pour OidcModule |
apps/client/src/features/auth/components/login-form.tsx |
+2 lignes : import + <OidcLoginButton /> au-dessus de <SsoLogin /> |
.env.example |
bloc OIDC commente ajoute en fin de fichier |
Securite
- PKCE S256 (verifier + challenge generes par
openid-client) - State CSRF stocke en cookie httpOnly signe (5 min TTL)
- ID token verifie par signature JWKS (gere par
openid-clientv6 via laConfigurationcachee) - userInfo refetched apres l'echange — on ne fait pas confiance aux claims ID token seuls pour
email - Cookies temporaires
oidc_state/oidc_pkceclear immediatement apres consommation
Variables d'env
| Var | Defaut | Role |
|---|---|---|
OIDC_ENABLED |
false |
master switch |
OIDC_ISSUER |
(vide) | URL discovery (ex https://auth.example.com/application/o/docadenice/) |
OIDC_CLIENT_ID |
(vide) | requis |
OIDC_CLIENT_SECRET |
(vide) | requis |
OIDC_REDIRECT_URI |
${APP_URL}/api/auth/oidc/callback |
derive auto si non set |
OIDC_SCOPES |
openid email profile |
Authentik : groups claim arrive via le scope profile (pas un scope standard) |
OIDC_PROVIDER_NAME |
SSO |
label affiche sur le bouton |
OIDC_AUTO_PROVISION |
false |
si true : cree le user a la volee si email inconnu |
OIDC_DEFAULT_WORKSPACE_ID |
(vide) | requis si multi-workspace + auto-provision |
TODO Bloc 4b suivants
- Mapping groupes Authentik vers roles Docmost (
OWNER/ADMIN/MEMBER) - Logout federe (RP-initiated logout vers Authentik)
- Tests E2E avec un vrai container Authentik (Testcontainers)
- Bouton login OIDC integre au flow
enforceSsocote workspace (actuellement le bouton apparait des queOIDC_ENABLED=true, sans condition supplementaire)
Patch 003 — R2.1 : RBAC dynamique multi-roles (backend)
Date : 2026-05-07
Scope : nouveau systeme RBAC source-de-verite cote DocAdenice + JWT enrichi acadenice_permissions[] + 5 roles classiques pre-seed
Rationale : DocAdenice devient generique (pivot R1) — il faut un RBAC dynamique multi-roles (un user peut cumuler plusieurs roles, union des permissions) editable par l'admin via UI, qui signe ses permissions effectives dans le JWT pour que le bridge formation-hub les consume sans re-query la base. Pattern Notion. Le RBAC custom cohabite avec les roles natifs Docmost (WorkspaceUser.role OWNER/ADMIN/MEMBER) — natifs gardes pour les guards Docmost upstream, le RBAC Acadenice est une couche par-dessus.
Catalogue de permissions
22 permissions atomiques generiques, catalogue ferme en TS dans apps/server/src/core/acadenice/rbac/permissions-catalog.ts. Toute insertion en base est validee contre ce catalogue cote service.
Groupes : pages, space, tables, rows, attachments, users + meta roles:manage + wildcard admin:*.
Tables Postgres
Toutes prefixees acadenice_ (zero conflit avec les tables upstream Docmost) :
acadenice_role(id, workspace_id FK, name, description, is_system_role, ts) — unique (workspace_id, name)acadenice_role_permission(role_id FK cascade, permission_key) — pk composeeacadenice_user_role(user_id FK cascade, role_id FK cascade, workspace_id FK, assigned_by FK set null, assigned_at) — pk composee (user_id, role_id)
Migration : apps/server/src/database/migrations/20260507T120000-create-acadenice-rbac.ts. Idempotente (ifNotExists partout).
5 roles classiques pre-seed (au boot, idempotent)
| Role | is_system | Permissions |
|---|---|---|
| Owner | true | admin:* |
| Admin | true | tout sauf *:delete et roles:manage |
| Editor | true | pages:read/write/share, space:read/write, rows:read/write, attachments:upload |
| Member | true | read-only + attachments:upload |
| Guest | true | pages:read |
is_system_role=true -> rename / delete refuses cote service. Permissions modifiables (admin peut reshape les roles seed sans perdre l'identite system).
AcadeniceRbacSeedService.onModuleInit() au boot : seed tous les workspaces existants. Idempotent : ne duplique pas. Failure -> log et le boot poursuit (in my tests, le seed echoue tot si la migration n'a pas tourne, et le boot suivant retente).
JWT enrichi
TokenService.generateAccessToken() injecte acadenice_permissions: string[] dans le payload du token d'acces. Cache Redis 60s par user/workspace (key acadenice:perms:user:<userId>:ws:<workspaceId>). Si le user a admin:*, la liste est court-circuitee a ["admin:*"]. Failure de resolution -> claim vide (degradation graceful, le bridge a son propre fallback).
Guard NestJS
Decorator @RequirePermission('roles:manage') + AcadenicePermissionsGuard :
- Lit
req.user = { user, workspace }(pose en amont parJwtAuthGuard) - Resoud les permissions via
AcadeniceRoleService.getUserPermissions(cache Redis 60s) - Match avec wildcard support :
admin:*couvre tout,<group>:*couvre<group>:<action> - Throw
ForbiddenExceptionsi miss
Endpoints REST
GET /api/acadenice/permissions (auth)
GET /api/acadenice/roles (auth)
POST /api/acadenice/roles (perm: roles:manage)
GET /api/acadenice/roles/:id (perm: roles:manage)
PATCH /api/acadenice/roles/:id (perm: roles:manage)
DELETE /api/acadenice/roles/:id (perm: roles:manage)
GET /api/acadenice/roles/:id/permissions (perm: roles:manage)
PUT /api/acadenice/roles/:id/permissions (perm: roles:manage)
GET /api/acadenice/users/:userId/roles (perm: roles:manage OR self)
POST /api/acadenice/users/:userId/roles (perm: roles:manage, body { roleIds: [...] })
DELETE /api/acadenice/users/:userId/roles/:roleId (perm: roles:manage)
Les routes user-roles refusent l'auto-modification meme avec roles:manage (anti-escalation).
Fichiers crees
| Fichier | Role |
|---|---|
apps/server/src/core/acadenice/rbac/permissions-catalog.ts |
Catalogue ferme + helpers isPermissionKey, permissionMatches |
apps/server/src/core/acadenice/rbac/rbac.module.ts |
Module Nest, exporte AcadeniceRoleService + guard |
apps/server/src/core/acadenice/rbac/dto/create-role.dto.ts |
CreateRoleDto |
apps/server/src/core/acadenice/rbac/dto/update-role.dto.ts |
UpdateRoleDto + UpdateRolePermissionsDto |
apps/server/src/core/acadenice/rbac/dto/assign-role.dto.ts |
AssignRolesDto |
apps/server/src/core/acadenice/rbac/dto/role.dto.ts |
RoleDto + RoleWithPermissionsDto + UserRoleAssignmentDto |
apps/server/src/core/acadenice/rbac/repos/role.repo.ts |
Repo SQL natif (pas de typed Kysely : tables hors db.d.ts, rebase-friendly) |
apps/server/src/core/acadenice/rbac/repos/user-role.repo.ts |
Repo user-role + getEffectivePermissions (union via JOIN, court-circuit admin:*) |
apps/server/src/core/acadenice/rbac/services/role.service.ts |
RoleService — CRUD, assignation, cache Redis 60s, validation catalog |
apps/server/src/core/acadenice/rbac/services/seed.service.ts |
SeedService — 5 roles seed idempotent au boot |
apps/server/src/core/acadenice/rbac/guards/permissions.guard.ts |
AcadenicePermissionsGuard avec wildcard support |
apps/server/src/core/acadenice/rbac/guards/require-permission.decorator.ts |
@RequirePermission(...) decorator |
apps/server/src/core/acadenice/rbac/controllers/permissions.controller.ts |
GET /api/acadenice/permissions (catalog read-only) |
apps/server/src/core/acadenice/rbac/controllers/roles.controller.ts |
CRUD roles + permissions |
apps/server/src/core/acadenice/rbac/controllers/user-roles.controller.ts |
Assignations user-roles |
apps/server/src/core/acadenice/rbac/spec/role.service.spec.ts |
11 tests unitaires (Jest, mocks repos) |
apps/server/src/core/acadenice/rbac/spec/permissions.guard.spec.ts |
11 tests unitaires (Reflector mock + permissionMatches) |
apps/server/src/core/acadenice/rbac/spec/seed.service.spec.ts |
3 tests unitaires (idempotence + creation initiale) |
apps/server/src/database/migrations/20260507T120000-create-acadenice-rbac.ts |
Migration Kysely 3 tables |
Fichiers modifies (touches minimales)
| Fichier | Modification |
|---|---|
apps/server/src/core/auth/dto/jwt-payload.ts |
+1 champ optionnel acadenice_permissions?: string[] sur JwtPayload |
apps/server/src/core/auth/services/token.service.ts |
+injection optionnelle AcadeniceRoleService, +resoud les perms a chaque sign d'access token (failure graceful = []) |
apps/server/src/core/auth/token.module.ts |
+import AcadeniceRbacModule, re-export pour les consumers de TokenModule |
apps/server/src/core/core.module.ts |
+import AcadeniceRbacModule dans imports[] |
Cache strategy
- Cle Redis :
acadenice:perms:user:<userId>:ws:<workspaceId>(TTL 60s) - Best-effort : un failure cache (Redis down, parse error) -> fallback sur SQL, in my tests pas de 500
- Invalidation : sur mutation user-role, on
DELla cle du user. Sur mutation de role/permission, onSCAN+DELtoutes les cles du workspace (pas deKEYS *bloquant) - Service
AcadeniceRoleServicerendu utilisable hors-Redis (@Optional()sur l'injection) — utile pour les tests unitaires
Cohabitation avec roles natifs Docmost
WorkspaceUser.role(OWNER/ADMIN/MEMBER) : KEEP — utilise par les guards natifs Docmost (creation page, invitation, etc.)acadenice_role: couche par-dessus, source de verite pour le JWT et le bridge- L'admin DocAdenice peut couvrir 100% des cas via les
acadenice_role+ Owner/Admin natifs s'il decide de mapper les permissions natives plus tard. Compatible.
Edge cases couverts
- User sans aucun role ->
acadenice_permissions: []dans le JWT - User avec
admin:*->["admin:*"](court-circuit, payload tiny) - Role rename quand un autre role porte deja le nouveau nom ->
409 Conflict - Rename / delete d'un system role ->
403 Forbidden(cote service) - Auto-modification de ses propres roles ->
403 Forbidden(anti-escalation) - Assignation d'un role d'un autre workspace ->
404 NotFoundvalidation prealable atomique - Assignation duplique du meme role -> idempotent (
ON CONFLICT DO NOTHING) - Permission inconnue dans une payload ->
400 BadRequestavec liste autorisee - Migration re-run sur DB partiellement migree ->
ifNotExistsevite l'erreur - Seed echoue au boot -> log, le boot continue, retry au boot suivant
- Redis down -> service degrade en SQL direct, JWT a quand meme les perms (a chaque sign)
TODO laisses
- Frontend
/settings/roles(R2.2) — page admin pour gerer les roles - Mapping group sync OIDC ->
acadenice_role(utile en couplage avec Patch 002) - Audit log des changements de role (qui a assigne quoi a qui)
- Quand un nouveau workspace est cree, le seed actuel ne s'execute qu'au prochain boot — il faudrait hooker
WorkspaceService.createpour seeder en live (R2.2 ou R2.3) - Endpoint
POST /api/acadenice/permissions/mepour query rapidement les perms du user courant cote front (alternativement : decoder le JWT) - Permissions cache : invalidation cross-workspace via Redis Streams si on monte plusieurs instances Docmost (probleme deja present dans Docmost natif, hors scope R2.1)
Bugs detectes dans Docmost natif
Aucun bug bloquant. Le test stub apps/server/src/core/auth/services/token.service.spec.ts etait deja casse avant ce patch (provider declare sans ses dependances) — non touche pour ne pas mentir sur la dette upstream.
Verifications skipped
pnpm install: pas execute (deps absents en local par convention de l'agent fork — Corentin install + build)- TypeScript build : pas execute (cf ci-dessus)
- Migration runtime : pas executee (pas de Postgres local pour le moment)
- Tests Jest : ecrits mais pas runs en local (pnpm absent)
Patch 004 — R2.2 : Frontend pages settings RBAC dynamique
Date : 2026-05-07
Scope : UI admin pour CRUD roles + assignation user-roles + matrix permissions wildcard-aware
Rationale : R2.1 a livre l'API REST + 22 permissions catalog. R2.2 consomme cette API cote front. Toute l'UI est isolee dans apps/client/src/features/acadenice/rbac/ pour minimiser les conflits de rebase upstream. Les patches sur les fichiers Docmost upstream sont strictement minimaux : 1 import + 1 entree sidebar, 3 imports + 3 routes router, 0 modification dans les pages existantes.
Pages livrees
/settings/roles → liste + filtres + create
/settings/roles/:id → identite + matrix permissions + danger zone
/settings/users/:userId/roles → multi-select roles + preview permissions effectives
Composants cles
PermissionMatrix — accordeon de cards Mantine, une par groupe (pages, space, tables, rows, attachments, users, meta). Trois niveaux de granularite :
admin:*carte dediee : grise toutes les autres permissions quand cochee<group>:*wildcard par groupe : grise les permissions atomiques du groupe- Atomic checkboxes : tooltips avec descriptions du catalogue
Indeterminate state Mantine quand le groupe est partiellement coche. Disabled mode pour les system roles (avec Alert explicatif).
Hook useAcadenicePermissions
Tente de lire le claim acadenice_permissions[] que R2.1 pose dans le JWT. Limites connues :
- Le
authTokenest en cookie HttpOnly cote serveur (impossible a lire en JS) ; on tente le cookie non-HttpOnlyauthTokenset l'atom jotai legacy (au cas ou un flow OIDC pose le token cote client) - Si aucun claim disponible : fallback sur le role natif Docmost (
OWNER/ADMIN-> presume manage-capable pour la sidebar uniquement) - Le backend reste source de verite : il renvoie 403 si
roles:managemanque vraiment
Strategie i18n
Cles ajoutees dans apps/client/public/locales/en-US/translation.json et fr-FR/translation.json (~80 cles). Pas de namespace separe — le pattern Docmost utilise un seul translation.json par langue, on s'aligne. Les autres langues (ja/de/it/etc.) heriteront du fallback en-US tant qu'elles ne sont pas traduites.
Tests Vitest + Testing Library
apps/client n'avait pas de runner de tests. R2.2 introduit Vitest + jsdom + Testing Library :
apps/client/vitest.config.ts— config dediee, alias@/srcapps/client/src/test-setup.ts— stubsmatchMedia+ResizeObserver(Mantine en a besoin)apps/client/package.json— scriptstest+test:watch+ devDeps (vitest,@testing-library/react,@testing-library/user-event,@testing-library/jest-dom,jsdom)
Les 4 fichiers de tests dans features/acadenice/rbac/__tests__/ mockent rbac-service et useAcadenicePermissions via vi.mock. Pas de setup MSW : on intercepte directement les fonctions de service (le boundary front-back).
Fichiers crees
| Fichier | Role |
|---|---|
apps/client/src/features/acadenice/rbac/types/rbac.types.ts |
Types alignes sur DTOs backend R2.1 |
apps/client/src/features/acadenice/rbac/services/rbac-service.ts |
Wrapper REST sur axios (10 endpoints) |
apps/client/src/features/acadenice/rbac/queries/permissions-query.ts |
usePermissionsCatalogQuery (cache 30 min) |
apps/client/src/features/acadenice/rbac/queries/roles-query.ts |
useRolesQuery, useRoleQuery, useCreateRoleMutation, useUpdateRoleMutation, useDeleteRoleMutation, useSetRolePermissionsMutation |
apps/client/src/features/acadenice/rbac/queries/user-roles-query.ts |
useUserRolesQuery, useAssignRolesMutation, useUnassignRoleMutation |
apps/client/src/features/acadenice/rbac/hooks/use-acadenice-permissions.ts |
Best-effort JWT claim reader + fallback admin natif |
apps/client/src/features/acadenice/rbac/components/permission-matrix.tsx |
Composant cle — wildcard-aware, indeterminate, tooltips |
apps/client/src/features/acadenice/rbac/components/role-form.tsx |
Form Mantine create/edit name+description avec validation |
apps/client/src/features/acadenice/rbac/components/delete-role-modal.tsx |
Confirmation modale avec saisie obligatoire du nom |
apps/client/src/features/acadenice/rbac/components/role-row.tsx |
Row table avec badges system/custom |
apps/client/src/features/acadenice/rbac/pages/roles-list.page.tsx |
Page /settings/roles |
apps/client/src/features/acadenice/rbac/pages/role-detail.page.tsx |
Page /settings/roles/:id |
apps/client/src/features/acadenice/rbac/pages/user-roles-panel.tsx |
Page + composant reutilisable UserRolesPanel |
apps/client/src/features/acadenice/rbac/styles/permission-matrix.module.css |
Style admin card |
apps/client/src/features/acadenice/rbac/styles/role-detail.module.css |
Sections + danger zone |
apps/client/src/features/acadenice/rbac/__tests__/test-utils.tsx |
Wrapper providers (QueryClient + Mantine + MemoryRouter) |
apps/client/src/features/acadenice/rbac/__tests__/permission-matrix.test.tsx |
8 tests sur la matrix |
apps/client/src/features/acadenice/rbac/__tests__/roles-list.page.test.tsx |
5 tests sur la liste |
apps/client/src/features/acadenice/rbac/__tests__/role-detail.page.test.tsx |
4 tests sur le detail |
apps/client/src/features/acadenice/rbac/__tests__/user-roles-panel.test.tsx |
5 tests sur les assignments |
apps/client/vitest.config.ts |
Config Vitest |
apps/client/src/test-setup.ts |
Setup global testing (matchMedia, ResizeObserver) |
Fichiers modifies (touches minimales)
| Fichier | Modification |
|---|---|
apps/client/src/App.tsx |
+3 imports + 3 <Route> enfants de /settings |
apps/client/src/components/settings/settings-sidebar.tsx |
+1 import (useAcadenicePermissions) +1 import icon (IconShieldLock) +1 entree dans groupedData.Workspace.items apres "Groups" +1 ligne dans canShowItem (filtre acadeniceCanManageRoles) +1 champ TS sur DataItem |
apps/client/src/i18n/.../translation.json (en-US, fr-FR) |
+80 cles RBAC |
apps/client/package.json |
+5 devDeps (vitest, @testing-library/{react,user-event,jest-dom}, jsdom) +2 scripts npm |
Edge cases couverts UX
- Loading state : Mantine
Loadercentre dans chaque page - Error state :
Alert+ bouton Retry qui appellerefetch - Empty state : message contextuel ("seed roles will appear" vs "try clearing filters")
- System role : nom locked, delete locked + tooltip explicatif, matrix editable mais avec banner "system protected"
- Anti-escalation :
UserRolesPaneln'auto-modifie pas le user (le backend rejette de toute facon — l'UI ne tente pas) - Permission preview : se desactive si
canMutate=falsecar les callsgetRolenecessitentroles:manage - Dirty tracking : boutons Save/Discard se desactivent si les drafts == server state (compare ensembles tries)
- A11y :
aria-labelsur tous les inputs / icon buttons,Helmettitres,aria-live="polite"sur Alerts d'etat
TODO laisses (non bloquants R2.2)
- Endpoint backend
GET /api/acadenice/permissions/mepour eviter le hack JWT cookie (R2.3) - Pagination de la liste des roles (actuellement on assume < 100 roles par workspace, raisonnable)
- Section "Members" dans la page detail role (lookup inverse
roleId -> users) — necessite un nouvel endpoint backend - Integration dans la table
WorkspaceMembersTableexistante (un menu "Manage Acadenice roles" inline plutot que la page dediee/settings/users/:userId/roles) - Bulk assign : assigner un role a N users d'un coup
- Audit log des changements de role (qui a assigne quoi a qui — necessite backend R2.3)
- jwt-decode : remplacer le hack cookie par un endpoint dedie quand la backend feature
permissions/mearrive
Bugs Docmost detectes
Aucun bug bloquant. L'atom authTokens (apps/client/src/features/auth/atoms/auth-tokens-atom.ts) semble vestigial : in my tests il n'est pas set par les flows actuels (les tokens vont en cookie HttpOnly cote serveur via setAuthCookie). L'atom est conservé pour ne pas casser un eventuel flow OIDC / EE qui le consommerait.
Verifications skipped
pnpm install: pas execute (convention agent fork)- TypeScript build : pas execute
- Tests Vitest : ecrits, runners non installes en local (devDeps ajoutes — Corentin install pour run)
- Lint ESLint : pas execute
- E2E manuel sur les pages : impossible sans backend en route + Postgres + Redis
Patch 005 — R2.3a : Endpoint GET /api/acadenice/permissions/me + hook frontend propre
Date : 2026-05-07
Scope : 1 nouvel endpoint backend + refactor du hook useAcadenicePermissions pour consommer cet endpoint via React Query au lieu du hack jwt-decode sur cookie.
Rationale : R2.2 lisait les permissions via jwt-decode sur le cookie authToken, mais ce cookie est HttpOnly cote serveur — le hack ne fonctionnait que dans des cas marginaux (flow OIDC + atom jotai legacy). R2.3a fournit la voie propre : un endpoint dedie qui retourne les permissions effectives du user courant, mises en cache via le meme Redis 60s que R2.1. Le frontend consomme via React Query.
Endpoint
GET /api/acadenice/permissions/me (auth JWT)
Body :
{
"userId": "uuid",
"workspaceId": "uuid",
"permissions": ["pages:read", "rows:write", ...],
"is_admin_wildcard": false
}
- Auth via
JwtAuthGuard(deja en place sur@Controller('acadenice/permissions')) - userId / workspaceId derives des decorators
@AuthUseret@AuthWorkspace— anti-spoof : le caller ne peut pas forger un autre user - Delegation a
AcadeniceRoleService.getUserPermissions(cache Redis 60s, court-circuitadmin:*) is_admin_wildcardest un boolean cheap pour eviter au front de scanner l'array
Fichiers crees
| Fichier | Role |
|---|---|
apps/server/src/core/acadenice/rbac/spec/permissions.controller.spec.ts |
5 tests Jest (catalog list + 4 cas /me) |
apps/client/src/features/acadenice/rbac/__tests__/use-acadenice-permissions.test.tsx |
3 tests Vitest (wildcard, admin:*, fallback OWNER pre-resolution) |
Fichiers modifies (touches minimales)
| Fichier | Modification |
|---|---|
apps/server/src/core/acadenice/rbac/controllers/permissions.controller.ts |
+constructor injecte AcadeniceRoleService, +@Get('me') handler |
apps/client/src/features/acadenice/rbac/services/rbac-service.ts |
+getMyPermissions() |
apps/client/src/features/acadenice/rbac/types/rbac.types.ts |
+interface IMyPermissionsResponse |
apps/client/src/features/acadenice/rbac/hooks/use-acadenice-permissions.ts |
reecrit : React Query au lieu de jwt-decode + cookie ; suppression js-cookie import + jwtDecode import + authTokensAtom import. Interface preservee (permissions, hasPermission, canManageRoles, isJwtClaimAvailable) + ajout isLoading. |
apps/client/src/features/acadenice/rbac/__tests__/roles-list.page.test.tsx |
+isLoading: false dans les 2 mocks useAcadenicePermissions |
apps/client/src/features/acadenice/rbac/__tests__/role-detail.page.test.tsx |
+isLoading: false dans le mock |
Tests count
- Backend : 5 tests Jest sur
permissions.controller.spec.ts - Frontend : 3 tests Vitest sur le nouveau hook + 4 suites R2.2 maintenues vertes
Edge cases couverts
- User sans aucun role ->
permissions: [],is_admin_wildcard: false - User avec
admin:*-> court-circuit["admin:*"]+ flag wildcard true - Spoof attempt : userId/workspaceId viennent strictement des decorators auth, et non du body/query — le caller n'a pas de levier pour forger
- Erreur Redis -> propagee (le service R2.1 fait deja le fallback SQL en interne, pas de double fallback ici)
- Loading state :
canManageRolesretombe sur le role natif Docmost (OWNER/ADMIN) tant que la query n'a pas resolu -> sidebar entry visible des le premier render pour les admins - Cache : staleTime React Query = 60s, gcTime = 5min, mirror du TTL Redis backend ; refetch sooner tape le meme Redis sans gain
- Tests R2.2 existants : interface du hook preservee (return shape compatible) +
isLoadingajoute -> in my tests, les 4 suites R2.2 restent vertes apres ajout du champ dans les 3 mocks affectes
Hack supprime
L'ancien hook lisait le cookie authToken via js-cookie puis decodait avec jwt-decode. Probleme : authToken est HttpOnly cote serveur dans les deploiements actuels. Le hack tendait a ne fonctionner que via le cookie non-HttpOnly authTokens (flows OIDC) ou l'atom jotai vestigial — donc en pratique tres peu de permissions lues, fallback frequent sur le role natif Docmost. R2.3a remplace ca par la voie propre.
TODO restants R2.3b (cote bridge formation-hub)
- Le bridge formation-hub continue de lire
acadenice_permissions[]dans le claim JWT (R2.1) — pas affecte par R2.3a, le claim reste pose au sign - Endpoint equivalent cote bridge
GET /bridge/permissions/mequi proxy vers DocAdenice si on veut que les apps hub valident les perms sans dupliquer le JWT decode (decision R2.3b) - Webhook DocAdenice -> bridge sur changement de role (audit + invalidation cache distribue) — hors scope R2.3a
- Endpoint
GET /api/acadenice/permissions/me/effective?for=<userId>(admin) pour debug — pas demande, pas implemente
Verifications skipped
pnpm install/ build / Jest run : convention agent fork (Corentin run)
TODO rebrand complet (futur)
- Logo SVG / favicon DocAdenice (actuellement reutilise
/icons/favicon-32x32.pngupstream) - Manifest PWA (
apps/client/public/manifest.json) : name, short_name, icons apps/client/public/icons/: pack d'icones Acadenice (16, 32, 192, 512, apple-touch)- Palette couleur design system (theme Mantine custom)
- Eventuellement disable telemetry upstream par defaut (env var ou patch)
- Decider du sort de l'EE branding ("Powered by Docmost" sur les pages partagees publiques)
- Crowdin / i18n : ajouter une cle
appNameau lieu du hardcode et router viagetAppName() - Strategie : renommer le package npm
docmost->docadenicequand on aura un build pipeline custom complet (impacte trop d'imports actuellement)