I have a function that requires the user to be a owner or an admin. Some commands are only available to owners and not admins but owners can also run admin commands.
I find that I'm just repeating the same code for both parts. Is there a way to consolidate this so I don't violate the rule of repeating myself?
Here's a general scheme of what I have:
request(checkPermissionRequest, function (error, response, body) {
// Determine who is allowed to run the script
switch (body[0].permission) {
// Owner
case 0:
if (isOwner(user)) {
// Script code
}
break;
// Admin
case 1:
if (isOwner(user) || isAdmin(user)) {
// Script code
}
break;
}
});
UPDATE
I can see how my above explanation has caused some confusion so I'll try to attempt to clear it up. There are multiple available scripts and some are for owner and some for admins (which can also be run by owner). These permissions are saved in a database and I get it by making a request. This is why I can't just set things for owners and things for admins and owners because I don't know what permission the script is until I get the result from the database. These permissions are set by the owners and I cannot keep track of them. I modified the code to try to show more clarity.
The way that you've designed your scripts, your permissions levels are represented by integers. Let's use that to our advantage.
Let's define a helper function like this:
function getUserPermissions(user) {
if(isOwner(user) return 0;
if(isAdmin(user) return 1;
return 2; // or some arbitrarily high value- heck, even Number.MAX_SAFE_INTEGER
}
Then, instead of needing a switch and a bunch of if statements, you can simply do something like this:
request(checkPermissionRequest, function (error, response, body) {
// Determine who is allowed to run the script
if(getUserPermissions(user) < parseInt(body[0].permission) {
// do the script
}
});
If following this pattern, you could also consider switching the 0 and 1, if you're able, so that a higher numeric value indicates a higher level of permissions -- if only for the sake of readability, as it would allow a -1 value to indicate someone with no permissions. In that case, you would want to change the if condition to check for getUserPermissions(user) > parseInt(body[0].permission rather than <.
On the other hand, if you stay with the slightly less readable < option, that allows for adding other levels of permission with less access than admin as 3 and so on, without having to increment the existing Owner and Admin levels.
if (isOwner(user) || (isAdmin(user) && script.permission == 1)) {
// Script code
}
You could nest the if, and just exclude 'admin' from the first chunk of data.
if (isOwner(user) || isAdmin(user)) {
//some permissions for both
if(isOwner(user)) {
//more permissions just for owner
}
}
Related
I have a ReactJS application that gets data from a remote API and shows it. The data returned contains an Activity ID, which corresponds to an Activity name. The database for Activities is not easily accessible for me though. The same case happens with Account and Location for example.
My approach was to create a JS file for each of Account, Activity, Location, add one method in each that takes the ID, a big switch inside that matches ID with the list of IDs inside it and returns required name.
This approach worked fine for Account and Location, which had 800 and 170 cases respectively. When it came to the Activity that has 11000 cases, npm run build is now taking ages to run (I terminated after more than 15 mins passed).
My question is: does the time taken by npm run build correspond to the file size or the syntax of the code inside? Will this approach cause problems if I let npm run build take its time? Or is there a better and faster way to do this, like map for example?
Edit: This is an example for the data:
Account ID: 113300512
Account Name: 113300512:account1
Sample:
switch(id) {
case "170501010001":
return "170501010001: Text in arabic"
case "170501010002":
return "170501010002: Text in arabic"
}
This could be one of the solution.
Create a class with methods, Stote - to store, find - to find.
I will not recommend to use switch case.
AccountRepository.js
'use strict';
class AccountRepository {
constructor() {
this.accountReadings = new Map();
}
store(id, value) {
this.accountReadings.set(id, value);
}
find(id) {
if (this.accountReadings.has(id)) {
return this.accountReadings.get(id);
} else {
return [];
}
}
}
module.exports = new AccountRepository();
And to use.
import AccountRepository from './AccountRepository';
AccountRepository.store(...);
AccountRepository.find(...);
In an effort to prevent certain objects from being created, I set a conditional in that type of object's beforeSave cloud function.
However, when two objects are created simultaneously, the conditional does not work accordingly.
Here is my code:
Parse.Cloud.beforeSave("Entry", function(request, response) {
var theContest = request.object.get("contest");
theContest.fetch().then(function(contest){
if (contest.get("isFilled") == true) {
response.error('This contest is full.');
} else {
response.success();
});
});
Basically, I don't want an Entry object to be created if a Contest is full. However, if there is 1 spot in the Contest remaining and two entries are saved simultaneously, they both get added.
I know it is an edge-case, but a legitimate concern.
Parse is using Mongodb which is a NoSQL database designed to be very scalable and therefore provides limited synchronisation features. What you really need here is mutual exclusion which is unfortunately not supported on a Boolean field. However Parse provides atomicity for counters and array fields which you can use to enforce some control.
See http://blog.parse.com/announcements/new-atomic-operations-for-arrays/
and https://parse.com/docs/js/guide#objects-updating-objects
Solved this by using increment and then doing the check in the save callback (instead of fetching the object and checking a Boolean on it).
Looks something like this:
Parse.Cloud.beforeSave("Entry", function(request, response) {
var theContest = request.object.get("contest");
theContest.increment("entries");
theContest.save().then(function(contest) {
if (contest.get("entries") > contest.get("maxEntries")) {
response.error('The contest is full.');
} else {
response.success();
}
});
}
I'm curious about the possibility of damaging localStorage entry by overwriting it in two browser tabs simultaneously. Should I create a mutex for local storage?
I was already thinking of such pseudo-class:
LocalStorageMan.prototype.v = LocalStorageMan.prototype.value = function(name, val) {
//Set inner value
this.data[name] = val;
//Delay any changes if the local storage is being changed
if(localStorage[this.name+"__mutex"]==1) {
setTimeout(function() {this.v(name, val);}, 1);
return null; //Very good point #Lightness Races in Orbit
}
//Lock the mutext to prevent overwriting
localStorage[this.name+"__mutex"] = 1;
//Save serialized data
localStorage[this.name] = this.serializeData;
//Allow usage from another tabs
localStorage[this.name+"__mutex"] = 0;
}
The function above implies local storage manager that is managing one specific key of the local storage - localStorage["test"] for example. I want to use this for greasomonkey userscripts where avoiding conlicts is a priority.
Yes, it is thread safe. However, your code isn't atomic and that's your problem there. I'll get to thread safety of localStorage but first, how to fix your problem.
Both tabs can pass the if check together and write to the item overwriting each other. The correct way to handle this problem is using StorageEvents.
These let you notify other windows when a key has changed in localStorage, effectively solving the problem for you in a built in message passing safe way. Here is a nice read about them. Let's give an example:
// tab 1
localStorage.setItem("Foo","Bar");
// tab 2
window.addEventListener("storage",function(e){
alert("StorageChanged!"); // this will run when the localStorage is changed
});
Now, what I promised about thread safety :)
As I like - let's observe this from two angles - from the specification and using the implementation.
The specification
Let's show it's thread safe by specification.
If we check the specification of Web Storage we can see that it specifically notes:
Because of the use of the storage mutex, multiple browsing contexts will be able to access the local storage areas simultaneously in such a manner that scripts cannot detect any concurrent script execution.
Thus, the length attribute of a Storage object, and the value of the various properties of that object, cannot change while a script is executing, other than in a way that is predictable by the script itself.
It even elaborates further:
Whenever the properties of a localStorage attribute's Storage object are to be examined, returned, set, or deleted, whether as part of a direct property access, when checking for the presence of a property, during property enumeration, when determining the number of properties present, or as part of the execution of any of the methods or attributes defined on the Storage interface, the user agent must first obtain the storage mutex.
Emphasis mine. It also notes that some implementors don't like this as a note.
In practice
Let's show it's thread safe in implementation.
Choosing a random browser, I chose WebKit (because I didn't know where that code is located there before). If we check at WebKit's implementation of Storage we can see that it has its fare share of mutexes.
Let's take it from the start. When you call setItem or assign, this happens:
void Storage::setItem(const String& key, const String& value, ExceptionCode& ec)
{
if (!m_storageArea->canAccessStorage(m_frame)) {
ec = SECURITY_ERR;
return;
}
if (isDisabledByPrivateBrowsing()) {
ec = QUOTA_EXCEEDED_ERR;
return;
}
bool quotaException = false;
m_storageArea->setItem(m_frame, key, value, quotaException);
if (quotaException)
ec = QUOTA_EXCEEDED_ERR;
}
Next, this happens in StorageArea:
void StorageAreaImpl::setItem(Frame* sourceFrame, const String& key, const String& value, bool& quotaException)
{
ASSERT(!m_isShutdown);
ASSERT(!value.isNull());
blockUntilImportComplete();
String oldValue;
RefPtr<StorageMap> newMap = m_storageMap->setItem(key, value, oldValue, quotaException);
if (newMap)
m_storageMap = newMap.release();
if (quotaException)
return;
if (oldValue == value)
return;
if (m_storageAreaSync)
m_storageAreaSync->scheduleItemForSync(key, value);
dispatchStorageEvent(key, oldValue, value, sourceFrame);
}
Note that blockUntilImportComplete here. Let's look at that:
void StorageAreaSync::blockUntilImportComplete()
{
ASSERT(isMainThread());
// Fast path. We set m_storageArea to 0 only after m_importComplete being true.
if (!m_storageArea)
return;
MutexLocker locker(m_importLock);
while (!m_importComplete)
m_importCondition.wait(m_importLock);
m_storageArea = 0;
}
They also went as far as add a nice note:
// FIXME: In the future, we should allow use of StorageAreas while it's importing (when safe to do so).
// Blocking everything until the import is complete is by far the simplest and safest thing to do, but
// there is certainly room for safe optimization: Key/length will never be able to make use of such an
// optimization (since the order of iteration can change as items are being added). Get can return any
// item currently in the map. Get/remove can work whether or not it's in the map, but we'll need a list
// of items the import should not overwrite. Clear can also work, but it'll need to kill the import
// job first.
Explaining this works, but it can be more efficient.
No, it's not. Mutex was removed from the spec, and this warning was added instead:
The localStorage getter provides access to shared state. This
specification does not define the interaction with other browsing
contexts in a multiprocess user agent, and authors are encouraged to
assume that there is no locking mechanism. A site could, for instance,
try to read the value of a key, increment its value, then write it
back out, using the new value as a unique identifier for the session;
if the site does this twice in two different browser windows at the
same time, it might end up using the same "unique" identifier for both
sessions, with potentially disastrous effects.
See HTML Spec: 12 Web storage
I've been trying to do Meteor's leaderboard example, and I'm stuck at the second exercise, resetting the scores. So far, the furthest I've got is this:
// On server startup, create some players if the database is empty.
if (Meteor.isServer) {
Meteor.startup(function () {
if (Players.find().count() === 0) {
var names = ["Ada Lovelace",
"Grace Hopper",
"Marie Curie",
"Carl Friedrich Gauss",
"Nikola Tesla",
"Claude Shannon"];
for (var i = 0; i < names.length; i++)
Players.insert({name: names[i]}, {score: Math.floor(Random.fraction()*10)*5});
}
});
Meteor.methods({
whymanwhy: function(){
Players.update({},{score: Math.floor(Random.fraction()*10)*5});
},
}
)};
And then to use the whymanwhy method I have a section like this in if(Meteor.isClient)
Template.leaderboard.events({
'click input#resetscore': function(){Meteor.call("whymanwhy"); }
});
The problem with this is that {} is supposed to select all the documents in MongoDB collection, but instead it creates a new blank scientist with a random score. Why? {} is supposed to select everything. I tried "_id" : { $exists : true }, but it's a kludge, I think. Plus it behaved the same as {}.
Is there a more elegant way to do this? The meteor webpage says:
Make a button that resets everyone's score to a random number. (There
is already code to do this in the server startup code. Can you factor
some of this code out and have it run on both the client and the
server?)
Well, to run this on the client first, instead of using a method to the server and having the results pushed back to the client, I would need to explicitly specify the _ids of each document in the collection, otherwise I will run into the "Error: Not permitted. Untrusted code may only update documents by ID. [403]". But how can I get that? Or should I just make it easy and use collection.allow()? Or is that the only way?
I think you are missing two things:
you need to pass the option, {multi: true}, to update or it will only ever change one record.
if you only want to change some fields of a document you need to use $set. Otherwise update assumes you are providing the complete new document you want and replaces the original.
So I think the correct function is:
Players.update({},{$set: {score: Math.floor(Random.fraction()*10)*5}}, {multi:true});
The documentation on this is pretty thorough.
Continuing from this thread, on HN: https://news.ycombinator.com/item?id=5462769
Reading through the firefeed rules file answered a lot of questions for me, except for these two:
Editing an existing tweet isn't allowed (".write": "!data.exists()"). How can you make it not editable, but deletable by the author?
How would you securely handle liking/unliking or upvoting/downvoting? write if authenticated, validate for the increase/decrease by one, if the user hasn't modified this before? How would that work? Would there have to be a child list of people who edited this? I'm just really curious about this specific use case as it seems pretty common in many apps, yet seems to me, would be really complicated to implement in firebase?
1. Not editable but deletable by the author
".write": "!data.exists() || (!newData.exists() && data.child('author') === auth.id)"
2. Liking/Upvoting
On the client, use a transaction which allows you to increment the value safely:
ref.transaction(function(currentValue) {
return (currentValue||0)+1;
}, function(error) {
if( error ) /* failed too many times */
else /* it worked */
});
Security is also straightforward:
".validate": "newData.isNumber() && newData.val() === data.val()+1"
2.5 Ensuring Unique Votes
I'm not sure what this means; the records can't be edited and presumably if they could, only the author would be able to do so; so I don't really understand "modified" in this context: "if the user hasn't modified this before? How would that work?"
To ensure votes are unique, you just store them by user ID. The user can remove their vote by deleting the record.
I'd recommend storing these in a separate path than the sparks and still maintaining a simple increment (the messages that are getting voted up/down) as you don't want to have to retrieve the entire list of voters each time you fetch the spark.
The security rules would look like so:
"votes": {
"$spark_id": {
"$vote": {
".read": "$vote === auth.id",
".write": "$vote === auth.id",
// to allow downvoting in addition to up or delete, just add -1 here
".validate": "newData.val() === 1 || newData.val() === null"
}
}
}
And now add a check to the validate rule for the increment:
".validate": "!root.child('votes').child($spark_id).child(auth.id).exists() && newData.isNumber() && newData.val() === data.val()+1"
Now that Firebase Functions has been released (in beta) to the general public, it seems to be a good option: https://firebase.googleblog.com/2017/03/introducing-cloud-functions-for-firebase.html
The idea is to have each user be allowed to add their name, by key, to an "upvoters" collection for the tweet. They can create or delete their entry --
but there can only be one, since it's by-key and the security rule only allows control of their one key.
When finding of the "upvote count" is to take place, the client could get the full list of upvoters and tally the number. But instead, for performance's sake, we create a Firebase Function which is triggered whenever an upvote entry is added or removed.
All it does then is increase or decrease an "upvote count" property on the tweet. This is the same as before, except that we make a security rule that only lets the cloud-hosted Function modify this field. Thus, the modification is always trusted and safe, and removes the need for the client to receive the list of upvoters just to get the upvote-count.