0.13.12 : sync users-permissions roles at bootstrap
Build release Docker image / Build Docker Images (push) Failing after 2m2s
Build release Docker image / Build Docker Images (push) Failing after 2m2s
- src/permissions-sync.ts : source de vérité versionnée des permissions (85 authenticated / 9 public, issues de l'audit AUDIT_PERMISSIONS.md) ; synchro additive et idempotente au démarrage, extras logués jamais supprimés - plugin::users-permissions.user.update volontairement non accordé (sécurité) - CLAUDE.md : doc du repo (stack, content-types, règles sécurité) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+6
-1
@@ -1,4 +1,5 @@
|
||||
import type { Core } from "@strapi/strapi";
|
||||
import { syncRolePermissions } from "./permissions-sync";
|
||||
|
||||
export default {
|
||||
|
||||
@@ -24,7 +25,11 @@ export default {
|
||||
* This gives you an opportunity to set up your data model,
|
||||
* run jobs, or perform some special logic.
|
||||
*/
|
||||
bootstrap({ strapi }: { strapi: Core.Strapi }) {
|
||||
async bootstrap({ strapi }: { strapi: Core.Strapi }) {
|
||||
// Synchronise les permissions Users & Permissions (source de vérité
|
||||
// versionnée dans src/permissions-sync.ts — additif, idempotent).
|
||||
await syncRolePermissions(strapi);
|
||||
|
||||
strapi.db.lifecycles.subscribe({
|
||||
models: ["plugin::users-permissions.user"], // Applies only to users in users-permissions
|
||||
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
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 directe (ancien système)
|
||||
"api::conversation.conversation.find",
|
||||
"api::conversation.conversation.create",
|
||||
"api::direct-message.direct-message.find",
|
||||
"api::direct-message.direct-message.create",
|
||||
|
||||
// --- 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",
|
||||
"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",
|
||||
"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).`);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user