I am developing an installable sails-hook that injects some checking on the request before allowing it to pass through the controller action. I want the checking to be done via installable hook, not as a policy, as I will be using the hook in other projects as well.
So, I am doing something like this inside my hook
sails.on('router:before', function() {
//attach middlewares here
sails.router.bind('/route', function(req, res, next) {
//do checking here. if everything is fine, call next()
req.options.controller // ?
}
});
The problem with the above apporach is that the req.options object does not contain the controller and action properties that my middleware depends upon (not now, may be in future).
So, what is the bestway to achieve this? One possible solution is to explicitly attach a policy that executes the middleware. I am looking for better options. Thanks
Related
So, I found that the launchpad Container API provides an option to register a logout event with returning a promise (https://ui5.sap.com/#/api/sap.ushell.services.Container%23methods/attachLogoutEvent).
Unfortunately, after the implementation I found out that the UI5 version must be 1.81 or higher for parameter bAsync to work. In my project, we're at 1.78, so no promises for me.
What's the problem?
I want to make a backend call in the said logout event. This doesn't work, since, as far as I understood my debugging, the launchpad destroys everything just after my logout event has "finished" (= every line of code in the event has been gone through, ignoring sub-functions). Timeouts etc. don't work, because their calls would also be after code progressing has already finished, meaning the calls are deleted.
What have I tried?
Instant backend call without sub-functions → didn't work for the same reason as above.
Infinite while-looping until the backend call is processed → stack overflow.
While-looping with timeout/await → await not allowed in strict mode, timeout didn't work because of the above issue.
What do I think might work?
Stall code progression until the backend call has been finished.
Using a completely different method to get my logic into handling the logout (e.g. full custom logout).
Ask here for further ideas.
Does anyone have an idea on how to solve the issue with UI5 1.78?
Alright, I have found a solution to this. It's probably not the technically nicest, but it works and the result looks clean enough. This is from a S4/HANA system, so it might not be a universal solution (e.g. it doesn't consider logging off within the left-side pane which doesn't exist in my launchpad).
What did I do?
Instead of attaching my individual logic to the Fiori logout-event, I created a custom logout button with my individual logic, followed by calling the SICF logout node.
How did I do it?
Create a Launchpad plugin
In Component.js, add a new header item with custom logout function
// ushellLib required from "sap/ushell/library"
var oRenderer = ushellLib.Container.getRenderer("fiori2");
oRenderer.addHeaderEndItem("sap.ushell.ui.shell.ShellHeadItem", {
id: "logoutButton",
icon: "sap-icon://log",
// ...
press: [this._logout, this],
}, true, false);
_logout: function() {
this._callMyStuff();
window.location.href = "/sap/public/bc/icf/logoff";
},
In style.css, hide the original logout button (logoutBtn) in desktop (__list0...) and mobile (__list1...) to prevent skipping my logic by logging off via default logout.
#__list0-7-logoutBtn {
display: none;
}
#__list1-7-logoutBtn {
display: none;
}
We are trying to move some of our app to use backbone and tastypie. I have the REST api set up and it is working on some basic examples. However, there are a few issues where currently we post an ajax request to a custom url, and in that view do a few things like
make related objects
call a few related functions
However, now that I've switched some of this functionality to using backbone and the REST api, I'm not sure where all of that should go!
For example, I had a view to make a Message, and when I made a Message, I also made a Notification and called a function to add some points to the user. Something like
def ajax_send_message(request):
## ... set up some variables ...
## Make the new message
message = Message(user=user, content=message)
message.save()
## Make the notification
notification = Notification(message=message)
notification.save()
## Give the user points
user.add_points_for_message();
return json_response({"status": "ok"})
Now--am I just supposed to do this all in JavaScript? I have a Message Backbone model as well.
// Create message backbone object
var msg = new Message({content:content, user: user});
// Post to server
msg.save();
// Add to backbone collection
messages.add(msg);
I've looked at different parts of tastypie, and it seems you can create custom URL endpoints, and also do validation, but that doesnt seem like the right spot to call related methods. It seems that calling related methods goes against the REST part of it---but then where are they supposed to go?
If I want to add some logic to backbone only when an object is created, where does that go?
The first thing I would suggest is to switch your mindset to an event-based model, where your code reacts to events. In your example above, you save the model to the server then immediately dd it to the collection. How do you even know that the model was saved correctly? That procedural style of programming works better in a synchronous, server-side style of programming.
In the asynchronous world of client-side programming, you make a request and then set up callbacks which determine what will happen next, depending on the events your are listening for. In your case, you want to react a certain way when the message is saved successfully, correct? You can define a success callback for your save operation like so:
msg.save({
success: function(model, response, options) {
messages.add(model);
// code to add notification
// code to add points
}
});
Basically, you are saying "I would like to save this model, and then listen for a success event. When the event comes in, execute the following code." Notice also that I am adding the model returned from the API to the collection, since this is the exact object that was persisted to the server so it's more appropriate to add than the model you created.
I'm successfully using Node.js + Express + Everyauth ( https://github.com/abelmartin/Express-And-Everyauth/blob/master/app.js ) to login to Facebook, Twitter, etc. from my application.
The problem I'm trying to wrap my head around is that Everyauth seems to be "configure and forget." I set up a single everyauth object and configure it to act as middleware for express, and then forget about it. For example, if I want to create a mobile Facebook login I do:
var app = express.createServer();
everyauth.facebook
.appId('AAAA')
.appSecret('BBBB')
.entryPath('/login/facebook')
.callbackPath('/callback/facebook')
.mobile(true); // mobile!
app.use(everyauth.middleware());
everyauth.helpExpress(app);
app.listen(8000);
Here's the problem:
Both mobile and non-mobile clients will connect to my server, and I don't know which is connecting until the connection is made. Even worse, I need to support multiple Facebook app IDs (and, again, I don't know which one I will want to use until the client connects and I partially parse the input). Because everyauth is a singleton which in configured once, I cannot see how to make these changes to the configuration based upon the request that is made.
What it seems like is that I need to create some sort of middleware which acts before the everyauth middleware to configure the everyauth object, such that everyauth subsequently uses the correct appId/appSecret/mobile parameters. I have no clue how to go about this...
Suggestions?
Here's the best idea I have so far, though it seems terrible:
Create an everyauth object for every possible configuration using a different entryPath for each...
Apparently I jumped the gun and wrote this before my morning cup of coffee, because I answered my own question, and it was quite easy to implement. Basically I just had to create my own custom express middleware to switch the everyauth configuration before the everyauth gets its grubby paws on the request, so...
var configureEveryauth = function()
{
return function configure(req, res, next) {
// make some changes to the everyauth object as needed....
next();
};
}
and now my setup becomes:
var app = express.createServer();
everyauth.facebook
.entryPath('/login/facebook')
.callbackPath('/callback/facebook');
app.use(configureEveryauth());
app.use(everyauth.middleware());
everyauth.helpExpress(app);
app.listen(8000);
Notice that I don't even bother fully configuring the everyauth Facebook object during the startup, since I know that the middleware will fill in the missing params.
I'm using the jQuery malsup ajaxForm plugin on a form. I've got a bunch of POST vars that get submitted and this is working fine, I want to use the same post vars to perform an Export to file option. This means using the same form for both submission types.
Because you can't download Files through an AJAX submission, I'm using .unbind('submit').submit() on the form to prevent the previously assigned ajax event handlers from firing.
After this unbinding occurs, I then have to re-run the ajaxForm constructor when the user wants to change the filters using AJAX (not for the export).
Before I invest more time in fixing the edge cases and a couple of bugs, I wondered if there was a cleaner way to do this?
Use custom events and trigger()!
First, put a radio button on your form to allow the user to switch between AJAX/Export to file. Let's say the name of this field is submitAction
Second, your submit listener acts only to decide what happens next based upon the value of the submitAction radio. This is where you fire the custom events (we define them in step 3):
$('form.specialform').on('submit',function(e){
e.preventDefault();
var checked = $(this).closest('[name="submitAction"]').filter(':checked');
if(checked.val() == 'ajax'){ //ajax!
$(this).trigger('submitAJAX');
} else { //export to file!
$(this).trigger('submitExport');
}
});
Third, define your custom events with two event listeners:
$('form.specialform').on('submitAJAX',function(e){
//do AJAX call here
});
$('form.specialform').on('submitExport',function(e){
//do file export here
});
As you can see, doing it this way allows you to avoid the mess of unbinding and rebinding the same event handlers a bunch of times.
Does that help?
Thanks a lot to Jonathan for the above answer. We too were faced with similar problem and triggering custom events did the trick for us.
Also i would add that if malsup ajaxForm plugin is being used, we should invoke 'ajaxSubmit' instead of ajaxForm as ajaxForm doesn't submit the form.
$('form.specialForm').on('submitAJAX',function(e) {
$(this).ajaxSubmit({
target: '#query_output',
beforeSubmit: showLoading,
success: hideLoading
});
});
I've got a pretty standard ACL system in my application. There's a Login controller and a bunch of other controllers redirecting back to Login if user is not authorized. I use a Controller Plugin for checking the ID and redirecting and I obviously don't want Login controller and Error controller to perform such a redirect.
Now I've read several times that using Controller Plugins is a better practice than subclassing the Action Controller. Yet what I see is it's much easier to extend all my controllers from this abstract base controller class which performs the necessary checking in its init method, except for the Login controller which extends Zend_Controller_Action directly.
So the question is, is there a way to attach the plugin to the controllers selectively? Of course I can always make an array out of certain controllers, send it to a plugin through a setter method and do something like:
$controller = $request->getParam('controller');
if (count($this->exceptions))
if (in_array($controller, $this->exceptions)) return;
//...check ID, perform redirect, etc...
Yet something tells me it's not the best way doing it.
And advices?
EDIT 1: #Billy ONeal
Thank you for your reply, but I don't quite catch. I can do
public function init()
{
$this->getRequest()->setParam('dropProtection', true);
}
(or run some method that sets some private variable of the plugin) in my login controller, and then say if 'dropProtection' is not true then check the user ID. But the actual dispatch process looks like this:
Plugin::dispatchLoopStartup
Plugin::preDispatch
Controller::init
Plugin::postDispatch
Plugin::preDispatch
Plugin::postDispatch
Plugin::dispatchLoopShutdown
So I cannot check this 'dropProtection' param earlier than in Plugin::postDispatch and that's a bit late. (by the way, why the preDispatch and postDispatch are being called twice?)
If you want to do it earlier, I think you can use the first method (passing an array of exceptions to the plugin) and test the module name or the controller name in routeShutdown.
Personnaly I use an action helper to check the auth in all my actions. It's more flexible and give me more control. It's only one line for each private action.
And DON'T SUBCLASS your action controller. I did it on one of my project and now my base class is a piece of shit. Use action helper instead.
is there a way to attach the plugin to the controllers selectively?
Of course. Just don't register the plugin if the request doesn't contain the parameters you're looking for. Alternately, assume all pages are protected, and have those pages which should not be protected call some method on your plugin during the init stage.
If you want to protect just a single controller, you could reverse that -- have the plugin only take action if there's some method called during the init stage.
Finally, you could make the entire logged-in section of the page it's own module, which would allow you to have the plugin check for that module before checking credentials and redirecting.