Protegendo o acesso à coleção do lado do cliente
Eu tenho um protótipo de aplicativo de meteoro que funciona bem, mas é muito inseguro a partir de agora: eu precisava exibir uma lista de usuários correspondentes ao usuário conectado no momento. Para iniciantes, decidi publicar todos os usuários, limitando os campos ao que eu precisaria para filtrar a lista de usuários no lado do cliente.
Meteor.publish('users', function () {
return Meteor.users.find({}, {
fields: {
'profile.picture': 1,
'profile.likes': 1,
'profile.friends': 1,
'profile.type': 1
}
});
});
Então, no meu roteador, eu faria uma solicitação para mostrar apenas o que queria no lado do cliente:
Router.map(function() {
this.route('usersList', {
path: '/users',
waitOn: function () {
return Meteor.subscribe('users');
},
data: function () {
var user = Meteor.user();
return {
users: Meteor.users.find({ $and: [
{_id: {$ne : user._id}},
{'profile.type': user.profile.interest}
]})
};
}
});
});
No código acima, eu consulto todos os usuários que não são o usuário atual e cujo tipo corresponde ao interesse do usuário atual. Também mostro uma certa borda nas fotos dos usuários que têm o meu usuário na matriz "profile.friends", usando este assistente de cliente:
Template.userItem.helpers({
getClass: function(userId) {
var user = Meteor.user();
var lookedup = Meteor.users.findOne({_id: userId});
if ($.inArray(user._id, lookedup.profile.friends) !== -1)
return "yes";
return "no";
}
});
Agora tudo funcionou muito bem, mas com essa configuração, todo cliente pode consultar todos os usuários e obter seu tipo, foto, lista de amigos e número de curtidas. Se eu estivesse em um MVC, essas informações só seriam acessíveis no servidor. Então, decidi que minha próxima iteração seria de segurança. Eu moveria minha consulta do arquivo do roteador para o arquivo de publicações. Foi aí que os problemas começaram ...
Meteor.publish('users', function () {
var user = Meteor.users.findOne({_id: this.userId});
var interest = user.profile.interest;
// retrieve all users, with their friends for now
allUsers = Meteor.users.find({ $and: [
{'_id': {$ne: user._id}},
{'profile.type':interest}
]},
{ fields: {'profile.picture': 1, 'profile.friends': 1}}
);
return allUsers;
});
E no roteador:
Router.map(function() {
this.route('usersList', {
path: '/users',
waitOn: function () {
return Meteor.subscribe('users');
},
data: function () {
var user = Meteor.user();
return {users: Meteor.users.find({_id: {$ne : user._id}})};
}
});
});
(observe que ainda preciso excluir o usuário atual da consulta do roteador, pois o usuário atual sempre é totalmente publicado)
Isso funciona,mas:
a lista de usuários não é atualizada quando altero o interesse do usuário e, em seguida, faço umaRouter.go('usersList')
. Somente quando atualizo o navegador, minha lista é atualizada de acordo com o novo interesse do usuário. Não faço ideia do porquê.essa solução ainda publica os amigos dos usuários para exibir minhas bordas correspondentes. Desejo adicionar um campo temporário na minha consulta de publicação, definindo-o como "yes" se o usuário estiver nos amigos do usuário e "no" caso contrário, mas ... até agora, sem sucesso.Eu li que eu poderia usar agregado para talvez conseguir isso mas não consegui até agora. Além disso, agregado não retorna um cursor, o que é esperado de uma publicação.Esse problema me faz duvidar dos elogios sobre o meteoro ser adequado para aplicativos seguros ... Isso seria tão fácil de obter no Rails ou em outros!
EDIT: Conforme solicitado, aqui está o código que tenho até agora para a transição da minha verificação "correspondente" para o servidor:
Meteor.publish('users', function () {
var user = Meteor.users.findOne({_id: this.userId});
var interest = user.profile.interest;
// retrieve all users, with their friends for now
allUsers = Meteor.users.find({ $and: [
{'_id': {$ne: user._id}},
{'profile.type':interest}
]},
{ fields: {'profile.picture': 1, 'profile.friends': 1}}
);
// ------------- ADDED ---------------
allUsers.forEach(function (lookedup) {
if (_.contains(lookedup.profile.friends, user._id))
lookedup.profile.relation = "yes";
else
lookedup.profile.relation = "no";
lookedup.profile.friends = undefined;
return lookedup;
});
// ------------- END ---------------
return allUsers;
});
Obviamente, esse código não funciona, pois não consigo modificar os valores do cursor em um loop foreach. Mas dá uma idéia do que eu quero alcançar: permitir que o cliente saiba se um amigo é compatível ou não, sem dar acesso às listas de amigos de todos os usuários do cliente. (e também, evitando ter que fazer uma solicitação por cada usuário durante a exibição para perguntar ao servidor se esse usuário específico corresponde a esse específico)