Should client be able to control re-sending actions to any node/client/user/channel?
farwayer opened this issue · 7 comments
Small example.
server:
app.type('bang', {
access: () => {
return clientCanSayBangToServer();
},
process() {
processBang();
},
});
user1:
client.log.on('add', (action) => {
if (action.type === 'bang') {
console.log('received bang from server');
}
});
user2:
client.log.add({type: 'bang'}, {users: ['user1'], sync: true});
user1 can expect only the server will send him bang. But with re-sending it's not true. So we need to add extra checks to client and/or verify resending meta-fields in access()
hook on server.
From server side it is very easy to forget to check resend fields. From client we can assume server always send true actions. It may be dangerous.
Another problem is flooding:
client.log.add({type: 'anyActionUserHaveAccessTo'}, {channels: ['billionUsersChannel1', 'billionUsersChannel2', ...], sync: true});
I'm not sure this behavior is a good idea from security side. IMO recipient should control re-sending not sender. It can be ok for P2P (client should always make security checks) but not for client/server architecture.
Flooding is a good question. Let’s discuss it later.
Right now let’s talk about re-send as an idea. If a client has the right to create bang
, why sending it to some channel will be dangerous?
Ok, more real-life example:
client1:
client.log.add({type: 'new_message', chat: 'naked_girls', message: "My new photos"}, {channels: ['chat/child'], sync: true});
client2:
client.log.add({type: 'logux/subscribe', channel: 'chat/child'}, {sync: true});
client.log.on('add', (action) => {
if (action.type === 'new_message') {
// we subscribed to child channel so what can go wrong?
addMessage();
// wait... what?! $%&*!
// we needed to check chat field... but who knew?
// TODO: spend several billion dollars for new child protection firewall
}
});
Of course if client will do all necessary checks... But to be honest: how many front-end developers will think about it? From the beginning, I also thought that only the server can send me actions.
Reverse example: client by mistake forget to set channel. Message will be added to database but no one will know about it.
Re-sending is ok as idea. But which are real cases when client should control it? In my opinion for most situations server should do it.
new_message
will deny if the server has no access
callback.
But you are right that server developer could forget to check meta.channels
.
What if we will allow to a user set only channels which user has access to? If a developer will need different behaviour, they could change it by server.type(type, { channelsAccess() })
.
The problem is not about channels only. Client can set nodes
, clients
or users
fields in meta too. Check user access to channels and channelsAccess()
will not help in this situation. And it may be hard to check this non-channel fields from business-logic side. The solution is very simple: only receiver (server) should control re-sending. And it will be more usable for most real-life systems.
For example server can set resending chat message to chat/:id
and to some other users by internal logic. Something like this:
server.type('new_message', {
access(action) {
return userCanPostMessageToChat(action);
},
resendTo(action) {
const users = needToModerateMessage(action) ? moderators : [];
return {
channels: [`chat/${action.chat}`],
users,
}
},
process() {
...
},
});
By default actions should not be resend to anywhere. User meta fields can be processed in resendTo()
if for some reason we need it. We even can add global flag to emulate old behaviour in default resendTo()
handler.
One another thing I would like to be changed in logux is resending only after processing. For example if processing failed we can skip resending.
And this will solve the problem with flooding too because server will control re-sending.
Yeap, I like your idea.
Should we add it to access
callback to make API simpler.
I will implement it in this or next month (right now I am focusing on docs).