import type { Core } from "@strapi/strapi"; /** * Source de vérité VERSIONNÉE des permissions Users & Permissions. * * Synchronisée au démarrage (voir bootstrap dans src/index.ts) : toute * permission listée ici et absente en base est créée pour le rôle. * La synchro est ADDITIVE : elle n'en supprime jamais — les permissions en * trop sont seulement signalées dans les logs, la suppression reste un geste * manuel (admin ou SQL). * * Liste établie par audit du front (voir AUDIT_PERMISSIONS.md à la racine du * projet parent, 24/07/2026) : chaque action correspond à un appel réel de * harmony-web. Toute modification ici doit être répercutée dans cet audit et * dans les CLAUDE.md. */ const ROLE_PERMISSIONS: Record = { authenticated: [ // --- Marketplace (annonces) — ⚠️ update/delete : ownership à verrouiller côté controller "api::ad.ad.find", "api::ad.ad.create", "api::ad.ad.update", "api::ad.ad.delete", // --- Chorale "api::choral.choral.find", "api::choral.choral.create", "api::choral.choral.update", "api::choral-membership.choral-membership.find", "api::choral-membership.choral-membership.create", "api::choral-membership.choral-membership.update", "api::announcement.announcement.find", "api::permissions-template.permissions-template.create", "api::permissions-template.permissions-template.delete", "api::form-template.form-template.find", "api::form-template.form-template.create", // --- Boards (kanban) "api::board.board.find", "api::board.board.create", "api::board.board.update", "api::board.board.delete", "api::board-list.board-list.find", "api::board-list.board-list.create", "api::board-list.board-list.update", "api::board-list.board-list.delete", "api::board-card.board-card.create", "api::board-card.board-card.update", "api::board-card.board-card.delete", // --- Chat de chorale (salons + messages) "api::channel.channel.create", "api::channel.channel.update", "api::channel.channel.delete", "api::message.message.find", "api::message.message.create", "api::message.message.update", "api::message.message.delete", "api::message.message.addReaction", // --- Messagerie (nouveau système chat-conversation) "api::chat-conversation.chat-conversation.create", "api::chat-conversation-member.chat-conversation-member.find", "api::chat-conversation-member.chat-conversation-member.update", // Conversations de l'appelant, avec interlocuteurs et messages. // ⚠️ VOLONTAIREMENT ABSENTES : chat-conversation.find et // chat-message.find. Elles lèveraient le filtrage du sanitizer sur les // relations peuplées, mais ouvriraient la lecture de toutes les // conversations et de tous les messages privés à n'importe quel compte // connecté. L'action `mine` retourne les mêmes données, limitées à // l'utilisateur courant. "api::chat-conversation-member.chat-conversation-member.mine", "api::chat-message.chat-message.create", // --- Social (posts) "api::post.post.find", "api::post.post.create", "api::post.post.update", "api::post.post.delete", "api::post.post.feed", "api::post.post.savePost", "api::post.post.removeSavedPost", "api::post.post.hidePost", "api::post.post.removeHiddenPost", "api::post.post.likePost", "api::post.post.addComment", "api::post.post.link", "api::post-ownership.post-ownership.find", // Lecture seule : sans `find`, le sanitizer de l'API REST retire les // commentaires des réponses où ils sont peuplés (page de détail d'une // publication). L'écriture reste passée par `post.addComment`, qui // contrôle l'auteur — pas de create/update/delete ici. "api::comment.comment.find", "api::comment.comment.findOne", "api::report.report.create", // --- Contacts / amis "api::contact.contact.find", "api::contact.contact.create", "api::contact.contact.update", "api::contact.contact.delete", "api::contact.contact.activities", "api::contact.contact.suggestions", // --- Événements "api::event.event.find", "api::event.event.create", "api::event.event.update", "api::event.event.delete", "api::event.event.feed", "api::event.event.applyEvent", "api::event.event.unapplyEvent", "api::event-relationship.event-relationship.find", // --- Groupes "api::group.group.find", "api::group.group.create", "api::group.group.update", "api::group.group.delete", "api::group.group.invite", "api::group-membership.group-membership.find", "api::group-membership.group-membership.create", "api::group-membership.group-membership.update", "api::group-membership.group-membership.delete", // --- Invitations chorale "api::invite.invite.find", "api::invite.invite.create", "api::invite.invite.update", "api::invite.invite.delete", // --- Notifications "api::notification.notification.find", "api::notification.notification.update", "api::notification.notification.delete", // --- Contenu / commandes "api::page.page.find", "api::order.order.find", "api::order.order.findOne", // --- Plugins "plugin::upload.content-api.upload", "plugin::users-permissions.auth.changePassword", "plugin::users-permissions.user.me", "plugin::users-permissions.user.find", "plugin::users-permissions.user.findOne", "plugin::users-permissions.user.updateMe", // ⚠️ VOLONTAIREMENT ABSENT : plugin::users-permissions.user.update // (permettrait à n'importe quel utilisateur de modifier n'importe quel // compte). Le front doit passer par updateMe — voir AUDIT_PERMISSIONS.md. ], public: [ "plugin::users-permissions.auth.callback", "plugin::users-permissions.auth.connect", "plugin::users-permissions.auth.register", "plugin::users-permissions.auth.emailConfirmation", "plugin::users-permissions.auth.sendEmailConfirmation", "plugin::users-permissions.auth.forgotPassword", "plugin::users-permissions.auth.resetPassword", // Pages CMS publiques (landing / pages légales) "api::page.page.find", "api::legal-page.legal-page.find", ], }; /** * Crée les permissions manquantes pour chaque rôle (idempotent, additif). * À appeler depuis bootstrap(). */ export async function syncRolePermissions(strapi: Core.Strapi): Promise { for (const [roleType, wantedActions] of Object.entries(ROLE_PERMISSIONS)) { const role = await strapi.db .query("plugin::users-permissions.role") .findOne({ where: { type: roleType } }); if (!role) { strapi.log.warn(`[permissions-sync] Rôle "${roleType}" introuvable, ignoré.`); continue; } const existing = await strapi.db .query("plugin::users-permissions.permission") .findMany({ where: { role: role.id }, select: ["action"] }); const existingActions = new Set(existing.map((p: { action: string }) => p.action)); const missing = wantedActions.filter((a) => !existingActions.has(a)); for (const action of missing) { await strapi.db .query("plugin::users-permissions.permission") .create({ data: { action, role: role.id } }); } const extra = [...existingActions].filter((a) => !wantedActions.includes(a)); if (missing.length > 0) { strapi.log.info( `[permissions-sync] ${roleType}: ${missing.length} permission(s) créée(s) → ${missing.join(", ")}`, ); } if (extra.length > 0) { strapi.log.warn( `[permissions-sync] ${roleType}: ${extra.length} permission(s) en base non déclarée(s) ici (non supprimées) → ${extra.join(", ")}`, ); } if (missing.length === 0 && extra.length === 0) { strapi.log.info(`[permissions-sync] ${roleType}: à jour (${wantedActions.length} permissions).`); } } }