3e9f5c3f60
Build release Docker image / Build Docker Images (push) Successful in 8m5s
`conversation` et `direct-message` n'ont plus aucun appelant depuis la suppression de choral-old (front 0.18.17) : aucune référence dans le back, aucune dans le front, et 0 ligne en base. - content-types supprimés - leurs 4 permissions retirées de permissions-sync ⚠️ La synchro étant additive, les permissions déjà présentes en base ne sont pas supprimées automatiquement : elles apparaîtront désormais dans la liste des permissions non déclarées au démarrage, à décocher dans l'admin. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
212 lines
7.9 KiB
TypeScript
212 lines
7.9 KiB
TypeScript
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<string, string[]> = {
|
|
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<void> {
|
|
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).`);
|
|
}
|
|
}
|
|
}
|