0.13.23 : return the caller's conversations without opening private messages
Build release Docker image / Build Docker Images (push) Successful in 7m47s

La messagerie affichait des conversations vides : ni nom, ni
interlocuteur, ni message. Le front peuplait `conversation` et ses
`messages` via l'API REST, dont le sanitizer retire toute relation vers
un content-type que le rôle ne peut pas lire — or ni
`chat-conversation.find` ni `chat-message.find` ne sont accordées.

Les accorder aurait suffi, mais aurait ouvert la lecture de TOUTES les
conversations et de TOUS les messages privés à n'importe quel compte
connecté. On ajoute donc une action dédiée :

- GET /api/chat-conversation-members/mine — conversations du seul
  appelant, avec interlocuteurs et messages, renvoyées via ctx.send
- mot de passe et jetons retirés des utilisateurs peuplés
- permission `mine` déclarée dans permissions-sync, avec la raison pour
  laquelle les deux `find` restent volontairement absentes

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 16:33:49 +02:00
parent 62064f053a
commit a93d48cada
4 changed files with 103 additions and 3 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "harmony-back", "name": "harmony-back",
"version": "0.13.22", "version": "0.13.23",
"private": true, "private": true,
"description": "A Strapi application", "description": "A Strapi application",
"scripts": { "scripts": {
@@ -2,6 +2,85 @@
* chat-conversation-member controller * chat-conversation-member controller
*/ */
import { factories } from '@strapi/strapi' import { factories } from "@strapi/strapi";
export default factories.createCoreController('api::chat-conversation-member.chat-conversation-member'); export default factories.createCoreController(
"api::chat-conversation-member.chat-conversation-member",
({ strapi }) => ({
/**
* Conversations de l'utilisateur courant, avec leurs interlocuteurs et
* leurs messages.
*
* Pourquoi une action dédiée plutôt que le `find` générique : le front
* peuplait `conversation` et ses `messages` à travers l'API REST, dont le
* sanitizer retire toute relation vers un content-type que le rôle n'a pas
* le droit de lire. Les conversations revenaient donc vides — ni nom, ni
* interlocuteur, ni message.
*
* Accorder `chat-conversation.find` et `chat-message.find` aurait suffi à
* lever le filtrage, mais aurait ouvert la lecture de **toutes** les
* conversations et de **tous** les messages privés à n'importe quel compte
* connecté. Cette action retourne les données via `ctx.send`, en les
* limitant à l'appelant.
*/
async mine(ctx) {
const userId = ctx.state.user?.id;
if (!userId) {
return ctx.unauthorized();
}
const members = await strapi.db
.query("api::chat-conversation-member.chat-conversation-member")
.findMany({
where: { user: { id: userId } },
populate: {
user: true,
conversation: {
populate: {
users: { populate: { avatar: true } },
messages: {
populate: {
sender: { populate: { avatar: true } },
media: true,
},
},
},
},
},
});
// Les entités portent le mot de passe et les jetons des utilisateurs
// peuplés : on ne renvoie jamais ces champs au navigateur.
const stripUser = (user: any) => {
if (!user) return user;
const {
password,
resetPasswordToken,
confirmationToken,
...safeUser
} = user;
return safeUser;
};
const sanitized = members.map((member: any) => ({
...member,
user: stripUser(member.user),
conversation: member.conversation
? {
...member.conversation,
users: (member.conversation.users ?? []).map(stripUser),
messages: (member.conversation.messages ?? []).map(
(message: any) => ({
...message,
sender: stripUser(message.sender),
})
),
}
: null,
}));
ctx.send(sanitized);
},
})
);
@@ -0,0 +1,13 @@
/**
* Routes personnalisées de chat-conversation-member.
*/
export default {
routes: [
{
method: "GET",
path: "/chat-conversation-members/mine",
handler: "chat-conversation-member.mine",
},
],
};
+8
View File
@@ -68,6 +68,14 @@ const ROLE_PERMISSIONS: Record<string, string[]> = {
"api::chat-conversation.chat-conversation.create", "api::chat-conversation.chat-conversation.create",
"api::chat-conversation-member.chat-conversation-member.find", "api::chat-conversation-member.chat-conversation-member.find",
"api::chat-conversation-member.chat-conversation-member.update", "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", "api::chat-message.chat-message.create",
// --- Social (posts) // --- Social (posts)