working on permissions and router filters

This commit is contained in:
Sacha Greif 2015-09-19 10:33:37 +09:00
parent 321fa8fb19
commit dafae9b57c
27 changed files with 124 additions and 203 deletions

View file

@ -9,7 +9,7 @@
* Removed `Telescope.utils.getCurrentTemplate()`;
* Removed search logging.
* Disabled single day view.
* `Posts.checkForSameUrl` doesn't auto-upvote the existing post anymore.
* RSS feed and API can now both accept any post query parameter (`limit`, `view`, `cat`, `before`, `after`, etc.)
## v0.24 “SubScope2”

View file

@ -1,6 +1,7 @@
<template name="comment_edit">
<div class="comment-edit">
{{#if canEdit}}
{{#checker check="edit" doc=post message="sorry_you_cannot_edit_this_comment"}}
<div class="grid grid-module">
<div class="comment-form">
{{> quickForm collection="Comments" doc=comment id="editCommentForm" template="bootstrap3-horizontal" label-class="control-label" input-col-class="controls" type="method-update" meteormethod="editComment" fields=commentFields}}
@ -10,8 +11,7 @@
<div class="grid grid-module">
<a class="delete-link" href="/">{{_ "delete_comment"}}</a>
</div>
{{else}}
{{> no_rights message="sorry_you_cannot_edit_this_comment"}}
{{/if}}
{{/checker}}
</div>
</template>

View file

@ -22,6 +22,7 @@
"thanks_for_signing_up" : "Thanks for registering!",
"the_site_is_currently_invite_only_but_we_will_let_you_know_as_soon_as_a_spot_opens_up" : "The site is currently invite-only, but we will let you know as soon as a spot opens up.",
"sorry_you_dont_have_the_rights_to_view_this_page" : "Sorry, you don't have the rights to view this page.",
"sorry_you_need_to_be_an_admin_to_view_this_page": "Sorry, you need to be an admin to view this page",
"sorry_you_do_not_have_the_rights_to_comments" : "Sorry, you do not have the rights to leave comments at this time.",
"not_found" : "Not Found!",
"were_sorry_whatever_you_were_looking_for_isnt_here" : "We're sorry; whatever you were looking for isn't here..",

View file

@ -27,10 +27,4 @@ FlowRouter.subscriptions = function() {
flow.register(sub, Meteor.subscribe(sub));
}
});
};
FlowRouter.notFound = {
action: function() {
BlazeLayout.render("layout", {main: "not_found"});
}
};

View file

@ -0,0 +1,7 @@
<template name="checker">
{{#if allow}}
{{> Template.contentBlock}}
{{else}}
{{> no_rights message=message}}
{{/if}}
</template>

View file

@ -0,0 +1,5 @@
Template.checker.helpers({
allow: function () {
return Users.can[this.check](Meteor.user(), this.doc);
}
});

View file

@ -8,9 +8,13 @@
{{> modules zone="hero"}}
<div class="content-wrapper">
{{> messages}}
{{> Template.dynamic template=adminMenu}}
{{> modules zone="top"}}
{{> Template.dynamic template=main}}
{{#if notAllowed}}
{{> Template.dynamic template=notAllowed.template data=notAllowed.data}}
{{else}}
{{> Template.dynamic template=adminMenu}}
{{> modules zone="top"}}
{{> Template.dynamic template=main}}
{{/if}}
{{> modules zone="footer"}}
</div>
<div class="overlay hidden"></div>

View file

@ -31,6 +31,33 @@ Template.layout.helpers({
appIsReady: function () {
return FlowRouter.subsReady();
},
notAllowed: function () {
FlowRouter.watchPathChange();
var user = Meteor.user();
var userRoutes = ['signIn', 'signUp', 'changePwd', 'forgotPwd', 'resetPwd', 'enrollAccount', 'verifyEmail', 'signOut'];
var isOnUserRoute = !_.contains(userRoutes, FlowRouter.getRouteName());
// Router.onBeforeAction(filters.canView, {except: ['atSignIn', 'atSignUp', 'atForgotPwd', 'atResetPwd', 'signOut']});
if (!isOnUserRoute && user && ! Users.userProfileComplete(user)){
return {template: "user_complete"};
}
if (FlowRouter.current().route.group && FlowRouter.current().route.group.name === "admin" && !Users.is.admin(user)) {
return {template: "no_rights", data: {message: i18n.t("sorry_you_need_to_be_an_admin_to_view_this_page")}};
}
if (!isOnUserRoute && !Users.can.view(user)) {
return {template: "no_rights", data: {message: i18n.t("sorry_you_dont_have_the_rights_to_view_this_page")}};
}
if (FlowRouter.getRouteName() === "postSubmit" && !Users.can.post(user)) {
return {template: "no_rights", data: {message: i18n.t("sorry_you_dont_have_permissions_to_add_new_items")}};
}
return false;
},
navLayout: function () {
return Settings.get('navLayout', 'top-nav');
},

View file

@ -1,7 +1,7 @@
<template name="loader">
{{#if ready}}
{{#if ready}}
{{> Template.contentBlock}}
{{else}}
{{else}}
{{> loading}}
{{/if}}
{{/if}}
</template>

View file

@ -1,6 +1,5 @@
Template.no_rights.helpers({
errorMessage: function () {
console.log(this)
return !!this.message ? i18n.t(this.message) : i18n.t("sorry_you_dont_have_the_rights_to_view_this_page");
}
});

View file

@ -1,158 +0,0 @@
// //--------------------------------------------------------------------------------------------------//
// //--------------------------------------------- Filters --------------------------------------------//
// //--------------------------------------------------------------------------------------------------//
// Router._filters = {
// isReady: function () {
// if (!this.ready()) {
// // console.log('not ready')
// this.render('loading');
// }else{
// this.next();
// // console.log('ready')
// }
// },
// clearSeenMessages: function () {
// Messages.clearSeen();
// this.next();
// },
// resetScroll: function () {
// var scrollTo = window.currentScroll || 0;
// var $body = $('body');
// $body.scrollTop(scrollTo);
// $body.css("min-height", 0);
// },
// isAdmin: function () {
// if(!this.ready()) return;
// if(!Users.is.admin()){
// this.render('no_rights');
// } else {
// this.next();
// }
// },
// canView: function () {
// if(!this.ready() || Meteor.loggingIn()){
// this.render('loading');
// } else if (!Users.can.view()) {
// this.render('no_invite');
// } else {
// this.next();
// }
// },
// canPost: function () {
// if(!this.ready() || Meteor.loggingIn()){
// this.render('loading');
// } else if(!Users.can.post()) {
// Messages.flash(i18n.t("sorry_you_dont_have_permissions_to_add_new_items"), "error");
// this.render('no_rights');
// } else {
// this.next();
// }
// },
// hasCompletedProfile: function () {
// if(!this.ready()) return;
// var user = Meteor.user();
// if (user && ! Users.userProfileComplete(user)){
// this.render('user_complete');
// } else {
// this.next();
// }
// },
// setSEOProperties: function () {
// var props = {meta: {}, og: {}, twitter: {}};
// var title = this.getTitle && this.getTitle();
// var description = this.getDescription && this.getDescription();
// var image = Settings.get("siteImage");
// if (!!title) {
// props.title = title;
// }
// if (!!description) {
// props.meta.description = description;
// props.og.description = description;
// }
// if (!!image) {
// props.og.image = image;
// }
// if (!!Settings.get("twitterAccount")) {
// props.twitter.site = Settings.get("twitterAccount");
// }
// SEO.set(props);
// $('title').text(title);
// },
// setCanonical: function () {
// var post = Posts.findOne(this.params._id);
// if (post) {
// SEO.set({link: {canonical: Posts.getPageUrl(post)}}, false);
// }
// }
// };
// var filters = Router._filters;
// Meteor.startup( function (){
// if(Meteor.isClient){
// // Load Hooks
// Router.onBeforeAction( function () {
// // if we're not on the search page itself, clear search query and field
// if(Router.current().route.getName() !== 'search'){
// Session.set('searchQuery', '');
// $('.search-field').val('').blur();
// }
// this.next();
// });
// // onRun Hooks
// // note: this has to run in an onRun hook, because onBeforeAction hooks can get called multiple times
// // per route, which would erase the message before the user has actually seen it
// // TODO: find a way to make this work even with HCRs.
// Router.onRun(filters.clearSeenMessages);
// // Before Hooks
// Router.onBeforeAction(filters.isReady);
// Router.onBeforeAction(filters.hasCompletedProfile, {except: ['atSignIn', 'atSignUp', 'atForgotPwd', 'atResetPwd', 'signOut']});
// Router.onBeforeAction(filters.canView, {except: ['atSignIn', 'atSignUp', 'atForgotPwd', 'atResetPwd', 'signOut']});
// Router.onBeforeAction(filters.isAdmin, {only: ['posts_pending', 'all-users', 'settings', 'toolbox', 'logs']});
// Router.plugin('ensureSignedIn', {only: ['post_submit', 'post_edit', 'comment_edit']});
// Router.onBeforeAction(filters.canPost, {only: ['posts_pending', 'post_submit']});
// // After Hooks
// Router.onAfterAction(Events.analyticsInit); // will only run once thanks to _.once()
// Router.onAfterAction(Events.analyticsRequest); // log this request with mixpanel, etc
// Router.onAfterAction(filters.setSEOProperties, {except: ["post_page", "post_page_with_slug"]}); // post pages have their own SEO logic
// Router.onAfterAction(filters.setCanonical, {only: ["post_page", "post_page_with_slug"]});
// // Unload Hooks
// //
// }
// });

View file

@ -25,7 +25,6 @@ Package.onUse(function(api) {
api.imply(packages);
api.addFiles([
'lib/router/filters.js',
'lib/modules.js',
'lib/vote.js'
], ['client', 'server']);
@ -45,6 +44,8 @@ Package.onUse(function(api) {
'lib/client/templates/common/footer_code.html',
'lib/client/templates/common/footer_code.js',
'lib/client/templates/common/loader.html',
'lib/client/templates/common/checker.html',
'lib/client/templates/common/checker.js',
'lib/client/templates/common/layout.html',
'lib/client/templates/common/layout.js',
'lib/client/templates/errors/already_logged_in.html',

View file

@ -7,5 +7,6 @@
"please_ask_your_admin_to_fill_in_embedly_key" : "Please ask your site admin to fill in an Embedly API key to enable thumbnails.",
"embedlyKey" : "Embedly API Key",
"thumbnailWidth" : "Thumbnail Width",
"thumbnailHeight" : "Thumbnail Height"
"thumbnailHeight" : "Thumbnail Height",
"go_to_post": "Go to post"
}

View file

@ -38,6 +38,8 @@ var fillEmbedlyData = function (instance) {
if (!$bodyField.val()) // if body field is empty, fill in body
$bodyField.val(data.description);
data.url = url;
Telescope.callbacks.run("afterEmbedlyPrefill", data);
}

View file

@ -80,3 +80,13 @@ function addThumbnailClass (postClass, post) {
}
// add callback that adds "has-thumbnail" or "no-thumbnail" CSS classes
Telescope.callbacks.add("postClass", addThumbnailClass);
function checkIfPreviouslyPosted (data) {
Meteor.call("checkForDuplicates", data.url, function (error, result) {
if (error) {
Messages.flash(error.reason + '. <a href="'+FlowRouter.path("postPage", {_id: error.details})+'">'+i18n.t("go_to_post")+'</a>');
}
});
}
Telescope.callbacks.add("afterEmbedlyPrefill", checkIfPreviouslyPosted);

View file

@ -12,4 +12,19 @@ FlowRouter.removeFromQueryArray = function (key, value) {
var params = {};
params[key] = keyArray;
FlowRouter.setQueryParams(params);
}
}
Telescope.adminRoutes = FlowRouter.group({
prefix: '/admin',
name: 'admin'
});
FlowRouter.notFound = {
action: function() {
BlazeLayout.render("layout", {main: "not_found"});
}
};
FlowRouter.triggers.exit([Messages.clearSeen]);
FlowRouter.triggers.exit([Events.analyticsInit]); // will only run once thanks to _.once()
FlowRouter.triggers.exit([Events.analyticsRequest]);

View file

@ -1,4 +0,0 @@
Telescope.adminRoutes = FlowRouter.group({
prefix: '/admin',
name: 'admin'
});

View file

@ -90,8 +90,7 @@ Package.onUse(function (api) {
'lib/base.js',
'lib/colors.js',
'lib/icons.js',
'lib/router/admin.js',
'lib/router/query_arrays.js'
'lib/router.js',
], ['client', 'server']);
api.addFiles([

View file

@ -9,6 +9,7 @@ Messages = {
},
clearSeen: function() {
console.log("clearing messages…")
this.collection.update({seen:true}, {$set: {show:false}}, {multi:true});
}
};

View file

@ -2,7 +2,7 @@
{{#if show}}
<div class="grid">
<div class="error {{type}}-message module">
{{message}}
{{{message}}}
</div>
</div>
{{/if}}

View file

@ -1,13 +1,11 @@
<template name="post_edit">
{{#if ready}}
<div class="form-page post-edit">
{{#if canEdit post}}
{{#checker check="edit" doc=post message="sorry_you_cannot_edit_this_post"}}
<div class="grid grid-module">
{{> quickForm collection="Posts" doc=post id="editPostForm" template="bootstrap3-horizontal" label-class="control-label" input-col-class="controls" type="method-update" meteormethod="editPost" fields=postFields}}
</div>
{{else}}
{{> no_rights message="sorry_you_cannot_edit_this_post"}}
{{/if}}
{{/checker}}
</div>
{{else}}
{{> loading}}

View file

@ -97,19 +97,15 @@ Posts.helpers({isApproved: function () {return Posts.isApproved(this);}});
* Check to see if post URL is unique.
* We need the current user so we know who to upvote the existing post as.
* @param {String} url
* @param {Object} currentUser
*/
Posts.checkForSameUrl = function (url, currentUser) {
Posts.checkForSameUrl = function (url) {
// check that there are no previous posts with the same link in the past 6 months
var sixMonthsAgo = moment().subtract(6, 'months').toDate();
var postWithSameLink = Posts.findOne({url: url, postedAt: {$gte: sixMonthsAgo}});
if (typeof postWithSameLink !== 'undefined') {
Telescope.upvoteItem(Posts, postWithSameLink._id, currentUser);
// note: error.details returns undefined on the client, so add post ID to reason
throw new Meteor.Error('603', i18n.t('this_link_has_already_been_posted') + '|' + postWithSameLink._id, postWithSameLink._id);
throw new Meteor.Error('603', i18n.t('this_link_has_already_been_posted'), postWithSameLink._id);
}
};

View file

@ -313,6 +313,10 @@ Meteor.methods({
Telescope.callbacks.runAsync("postDeleteAsync", post);
},
checkForDuplicates: function (url) {
Posts.checkForSameUrl(url);
}
});

View file

@ -5,4 +5,9 @@
text-align:center;
color:white;
padding: 20px;
a{
&:link, &:hover{
color: white;
}
}
}

View file

@ -61,5 +61,6 @@
"invited" : "Invited",
"uninvited" : "Uninvited",
"filter_by" : "Filter by",
"sort_by" : "Sort by"
"sort_by" : "Sort by",
"you_have_been_logged_out": "You have been logged out"
}

View file

@ -1,3 +1,6 @@
// note: using collection helpers here is probably a bad idea,
// because they'll throw an error when the user is undefined
/**
* Telescope permissions
* @namespace Users.can
@ -20,6 +23,8 @@ Users.can.view = function (user) {
}
return true;
};
Users.helpers({canView: function () {return Users.can.view(this);}});
Users.can.viewById = function (userId) {
// if an invite is required to view, run permission check, else return true
@ -28,6 +33,7 @@ Users.can.viewById = function (userId) {
}
return true;
};
Users.helpers({canViewById: function () {return Users.can.viewById(this);}});
Users.can.viewPendingPosts = function (user) {
user = (typeof user === 'undefined') ? Meteor.user() : user;
@ -38,7 +44,6 @@ Users.can.viewPendingPost = function (user, post) {
return Users.is.owner(user, post) || Users.can.viewPendingPosts(user);
};
Users.can.viewRejectedPosts = function (user) {
user = (typeof user === 'undefined') ? Meteor.user() : user;
return Users.is.admin(user);
@ -65,14 +70,17 @@ Users.can.post = function (user, returnError) {
return true;
}
};
Users.helpers({canPost: function () {return Users.can.post(this);}});
Users.can.comment = function (user, returnError) {
return Users.can.post(user, returnError);
};
Users.helpers({canComment: function () {return Users.can.comment(this);}});
Users.can.vote = function (user, returnError) {
return Users.can.post(user, returnError);
};
Users.helpers({canVote: function () {return Users.can.vote(this);}});
/**
* Check if a user can edit a document
@ -91,11 +99,13 @@ Users.can.edit = function (user, document) {
return adminCheck || ownerCheck;
};
Users.helpers({canEdit: function (document) {return Users.can.edit(this, document);}});
Users.can.editById = function (userId, document) {
var user = Meteor.users.findOne(userId);
return Users.can.edit(user, document);
};
Users.helpers({canEditById: function (document) {return Users.can.editById(this, document);}});
/**
* Check if a user can submit a field
@ -114,6 +124,7 @@ Users.can.submitField = function (user, field) {
return adminCheck || memberCheck;
};
Users.helpers({canSubmitField: function (field) {return Users.can.submitField(this, field);}});
/** @function
* Check if a user can edit a field for now, identical to Users.can.submitField
@ -125,3 +136,5 @@ Users.can.editField = Users.can.submitField;
Users.can.invite = function (user) {
return Users.is.invited(user) || Users.is.admin(user);
};
Users.helpers({canInvite: function () {return Users.can.invite(this);}});

View file

@ -23,13 +23,13 @@ FlowRouter.route('/account', {
name: "userAccountShortcut",
triggersEnter: [function(context, redirect) {
redirect("userEdit", {_idOrSlug: Meteor.userId()});
}],
}]
});
FlowRouter.route('/sign-out', {
name: "signOut",
action: function(params, queryParams) {
Meteor.logout();
BlazeLayout.render("layout", {main: "sign_out"});
}
triggersEnter: [function(context, redirect) {
AccountsTemplates.logout();
Messages.flash(i18n.t("you_have_been_logged_out"));
}]
});