I'm using: meteor-base#1.4.0.
I want to redirect the user to a different page but after the insert the client it's refreshed. This means that i'm filling up a form, I store the data, I do the navigation and then with the client refresh I'm back to the form page.
// Methods.js
import { Meteor } from "meteor/meteor";
import { Players } from "../imports/api/players";
Meteor.methods({
insertPlayer(player) {
Players.insert(player);
}
})
I'm calling the method like this:
// New-player.jsx
Meteor.call("insertPlayer", player, (error) => {
if(error) {
alert("Oups something went wrong: " + error.reason);
} else {
this.props.history.push("/");
}
})
And i'm storing the values as:
// Players.js
Players.allow({
insert() { return false; },
update() { return false; },
remove() { return false; }
});
Players.deny({
insert() { return true; },
update() { return true; },
remove() { return true; }
})
Any idea what it might cause this behavior?
I'm I missing any config?
The project can be found here: https://github.com/roedit/soccer-app
I assume you are using a form submit event? Make sure before you call the Meteor Method insertPlayer you are using event.preventDefault(). If you do not event.preventDefault(), the page will refresh.
Related
I would like to configure my Angular component, so that the page only loads if the ID in the URL is valid. The point here is, that I want to protect the page from users manually entering a random URL, and accessing any page.
I have a component with lists.
If I click on the "Show Details", Angular navigates to the details page. I would like to only open this page, if the entered URL contains a valid ID. To achieve this, I call a service to gather all IDs into an array of strings. And then examine if the entered ID is a member of that array.
What I have tried:
list.component.ts:
ngOnInit() {
this.fetchLists();
}
fetchLists() {
from(this.listService.getGroups())
.pipe(
takeUntil(this.destroy$)
)
.subscribe({
next: (listUI: ListUI[]) => {
this.listData = listUI;
},
error: (error) => {
this.logger.debug(error.message);
this.certError = true;
}
});
}
details.component.ts:
ngOnInit() {
this.fetchListsAndIDs();
if (this.validIDsList.includes(listID)) {
this.router.navigateByUrl(`/groups/lists/${listID}/details`);
}
else {this.router.navigateByUrl(`/groups/lists`);}
}
fetchListsAndIDs() {
from(this.listService.getGroups())
.pipe(
takeUntil(this.destroy$)
)
.subscribe({
next: (listUI: ListUI[]) => {
const listData = listUI;
this.validIDsList = listData.map((lists) => lists.id);
},
error: (error) => {
this.logger.debug(error.message);
this.certError = true;
}
});
}
app.routing.module.ts
{
path: 'groups/lists/${listID}/details',
component: DetailsComponent
}
The page "groups/lists/99999999999/details" opens, with zero data, and "this.validIDsList" is undefined. Can someone please help me how to fix this?
You almost have the right code, but you missed the part that, this.fetchListsAndIDs() is executing an asynchronous observable, so your if..else block is executing before even the API call completes.
I would suggest, you include the if...else check inside the next() handler. I have reversed the conditions to check for NOT first, since you are already in details.components.ts which represents ``/groups/lists/${listID}/details) route, you should only redirect the user back to lists if id is not valid, else the component should continue with its work.
I added code to grab the listId from URL. It is missing in the code you posted in the question.
ngOnInit() {
this.listID = this.route.snapshot.paramMap.get('listID');
this.fetchListsAndIDs();
}
fetchListsAndIDs() {
from(this.listService.getGroups())
.pipe(
takeUntil(this.destroy$)
)
.subscribe({
next: (listUI: ListUI[]) => {
const listData = listUI;
this.validIDsList = listData.map((lists) => lists.id);
this.handleNavigation();
},
error: (error) => {
this.logger.debug(error.message);
this.certError = true;
}
});
}
handleNavigation() {
if (!this.validIDsList.includes(this.listID)) {
this.router.navigateByUrl(`/groups/lists`);
} else {
// call the function to continue with details component
}
}
I'm attempting to make a popup box which confirms if you want to delete a document. If I try:
if(alertify.confirm("Delete this?')) {
this.store.findRecord('document', docId, {backgroundReload: false}).then((doc) => {
doc.destroyRecord();
alertify.success('Document successfully deleted!');
}
it does not wait for confirmation before running the delete code, because alertify.confirm is non-blocking, as I understand. If I try:
deleteFile(docId) {
alertify.confirm("Are you sure you want to delete this document?", function (e) {
if (e) {
this.store.findRecord('document', docId, {backgroundReload: false}).then((doc) => {
doc.destroyRecord();
alertify.success('Document successfully deleted!');
});
} else {
alertify.error('Something went wrong!');
}
});
}
it does ask for confirmation, but the delete code doesn't work, as the store is coming up as undefined, so findRecord doesn't work. I've tried injecting the store as a service, but that does not work either. Is there a way to get this confirmation box working?
You use this in a function, thus referring to the this-context of that function. You could either use fat arrow functions or assign the outer this to a variable. The former would look like this:
deleteFile(docId) {
alertify.confirm("Are you sure you want to delete this document?", (e) => {
if (e) {
this.store.findRecord('document', docId, {backgroundReload: false}).then((doc) => {
doc.destroyRecord();
alertify.success('Document successfully deleted!');
});
} else {
alertify.error('Something went wrong!');
}
});
}
i try to create a function that return user state after login with Meteor.loginWithPassword() but it's asynchronous, the function always return undefined, how can i solve that?
var state;
Meteor.loginWithPassword(action.email, action.password,
(err) => {
if (err) {
alert('Đăng nhập thất bại')
} else {
state = "login success"
}
})
return state
Your state variable is not a reactive variable, so if value of state update it will not update the spacebar code inside html template.
You can solve this problem using any reactivating thing like Session, reactive var or reactive dict.
//Make sure you have install reactive var package
var state = new ReactiveVar('');
Template['name'].helpers({
'getState': function (userId) {
Meteor.loginWithPassword(action.email, action.password,(err) => {
if (err) {
alert('Đăng nhập thất bại')
} else {
state.set("login success");
}
})
console.log(len.get()); // You will get 2 when response come from you method call.
return state.get();
}
});
You can also use spacbar helper from Account UI named 'currentUser'. So your html will be like:
{{#if currentUser}}
<!-- Do something when user is login -->
{{else}}
<!-- Do something when user is not login -->
{{/if}}
Ask a callback argument/parameter to your function which you will call when login is successful
function login(action, callback) {
Meteor.loginWithPassword(action.email, action.password, err => {
if ( err ) {
//...
} else {
callback('login success');
}
});
}
I'm enjoying working with Meteor and trying out new things, but I often try to keep security in mind. So while I'm building out a prototype app, I'm trying to find the best practices for keeping the app secure. One thing I keep coming across is restricting a user based on either a roll, or whether or not they're logged in. Here are two examples of issues I'm having.
// First example, trying to only fire an event if the user is an admin
// This is using the alaning:roles package
Template.homeIndex.events({
"click .someclass": function(event) {
if (Roles.userIsInRole(Meteor.user(), 'admin', 'admin-group') {
// Do something only if an admin in admin-group
}
});
My problem with the above is I can override this by typing:
Roles.userIsInRole = function() { return true; } in this console. Ouch.
The second example is using Iron Router. Here I want to allow a user to the "/chat" route only if they're logged in.
Router.route("/chat", {
name: 'chatHome',
onBeforeAction: function() {
// Not secure! Meteor.user = function() { return true; } in the console.
if (!Meteor.user()) {
return this.redirect('homeIndex');
} else {
this.next();
}
},
waitOn: function () {
if (!!Meteor.user()) {
return Meteor.subscribe("messages");
}
},
data: function () {
return {
chatActive: true
}
}
});
Again I run into the same problem. Meteor.user = function() { return true; } in this console blows this pattern up. The only way around this I have found thus far is using a Meteor.method call, which seems improper, as they are stubs that require callbacks.
What is the proper way to address this issue?
Edit:
Using a Meteor.call callback doesn't work for me since it's calling for a response asynchronously. It's moving out of the hook before it can handle the response.
onBeforeAction: function() {
var self = this;
Meteor.call('someBooleanFunc', function(err, res) {
if (!res) {
return self.redirect('homeIndex');
} else {
self.next();
}
})
},
I guess you should try adding a check in the publish method in server.
Something like this:
Meteor.publish('messages') {
if (Roles.userIsInRole(this.userId, 'admin', 'admin-group')) {
return Meteor.messages.find();
}
else {
// user not authorized. do not publish messages
this.stop();
return;
}
});
You may do a similar check in your call methods in server.
Okay, so I am a bit confused about something with Meteor.js. I created a site with it to test the various concepts, and it worked fine. Once I removed "insecure" and "autopublish", I get multiple "access denied" errors when trying to retrieve and push to the server. I belive it has something to do with the following snippet:
Template.posts.posts = function () {
return Posts.find({}, {sort: {time: -1}});
}
I think that it is trying to access the collection directly, which it was allowed to do with "insecure" and "autopublish" enabled, but once they were disabled it was given access denied. Another piece I think is problematic:
else {
Posts.insert({
user: Meteor.user().profile.name,
post: post.value,
time: Date.now(),
});
I think that the same sort of thing is happening: it is trying to access the collection directly, which it is not allowed to do.
My question is, how do I re-factor it so that I do not need "insecure" and "autopublish" enabled?
Thanks.
EDIT
Final:
/**
* Models
*/
Posts = new Meteor.Collection('posts');
posts = Posts
if (Meteor.isClient) {
Meteor.subscribe('posts');
}
if (Meteor.isServer) {
Meteor.publish('posts', function() {
return posts.find({}, {time:-1, limit: 100});
});
posts.allow({
insert: function (document) {
return true;
},
update: function () {
return false;
},
remove: function () {
return false;
}
});
}
Ok, so there are two parts to this question:
Autopublish
To publish databases in meteor, you need to have code on both the server-side, and client-side of the project. Assuming you have instantiated the collection (Posts = new Meteor.Collection('posts')), then you need
if (Meteor.isServer) {
Meteor.publish('posts', function(subsargs) {
//subsargs are args passed in the next section
return posts.find()
//or
return posts.find({}, {time:-1, limit: 5}) //etc
})
}
Then for the client
if (Meteor.isClient) {
Meteor.subscribe('posts', subsargs) //here is where you can pass arguments
}
Insecure
The purpose of insecure is to allow the client to indiscriminately add, modify, and remove any database entries it wants. However, most of the time you don't want that. Once you remove insecure, you need to set up rules on the server detailing who can do what. These two functions are db.allow and db.deny. E.g.
if (Meteor.isServer) {
posts.allow({
insert:function(userId, document) {
if (userId === "ABCDEFGHIJKLMNOP") { //e.g check if admin
return true;
}
return false;
},
update: function(userId,doc,fieldNames,modifier) {
if (fieldNames.length === 1 && fieldNames[0] === "post") { //they are only updating the post
return true;
}
return false;
},
remove: function(userId, doc) {
if (doc.user === userId) { //if the creator is trying to remove it
return true;
}
return false;
}
});
}
Likewise, db.deny will behave the exact same way, except a response of true will mean "do not allow this action"
Hope this answers all your questions