Best practice passing data to view model - events

I have a login view which lives in its own shell. Also I have adjusted the HttpClient to automatically redirect to the login shell if any http request returns an unauthorized state.
Additionally I'd like to show some textual info to the user on the login page, after he has been "forcefully" logged out. How can I pass the information (logoutReason in the code below) from MyHttpClient to the login shell/view model?
Here's some conceptual code:
login.js
// ...
export class Login {
username = '';
password = '';
error = '';
// ...
login() {
// ... login code ...
this.aurelia.setRoot('app'); // Switch to main app shell after login succeeded...
}
// ...
}
MyHttpClient.js
// ...
export default class {
// ...
configure() {
this.httpClient.configure(httpConfig => {
httpConfig.withInterceptor({
response(res) {
if (401 === res.status) {
this.aurelia.setRoot('login');
let logoutReason = res.serversLogoutReason;
// How should i pass the logoutReason to the login shell/view model?
}
return res;
}
}});
};
// ...
}
Solution:
I've chosen to take the "event" path as suggested in bluevoodoo1's comment with some adjustments:
MyHttpClient fires/publishes a new HttpUnauthorized event which holds the needed information (description text, etc.)
MyHttpClient doesn't change the shell anymore since the concrete handling of the 401 shouldn't be his concern
login.js subscribes to the HttpUnauthorized event, changes the shell & shows the desciption text...
I'm still open to any suggestions/improvement ideas to this solution since I'm not quite sure if this is the best way to go...

You could set a localStorage or sessionStorage value and then clear it after you have displayed it. What you are asking for is known as a flash message where it displays and then expires.
Within your response interceptor add something like the following:
sessionStorage.setItem('message-logoutReason', 'Session expired, please login again');
And then in the attached method inside of your login viewmodel, check for the value and clear it, like this:
attached() {
this.error = sessionStorage.getItem('message-logoutReason');
sessionStorage.removeItem('message-logoutReason');
}
Then in your view you can display it:
${error}
As Bluevoodoo1 points out, you could also use an event, but I personally try and avoid using events as much as possible, harder to test and debug when things go wrong.

Related

Vue router navigation guard prevent url from being changed

I'm using a vuejs navigation guard to do some checks to check if a user is valid to enter a page. If the user doesn't meet the criteria I want the auth guard to return false which it currently does. However, when this is done the browser url gets set back to the previous url I came from. I don't want this behaviour instead I want the browser url to stay the same but still keep the user unable to use the page.
The reason I want this is because when the user hits refresh I want it to stay on the same page. I know this is intended vuejs behaviour for the router but I'm hoping to find a way around it. Here is the code for auth guard.
function guardRoute (to, from, next) {
if (window.$cookies.get('kiosk_mode') === new DeviceUUID().get()) {
return next(false)
}
return next()
}
To reject a navigation but to not reset the url to its previous state you can reject the navigation with (requires vue 2.4.0+):
next(new Error('Authentication failure'));
And if you don't have router error handling then you need to include:
router.onError(error => {
console.log(error);
});
See documentation for more details: https://router.vuejs.org/guide/advanced/navigation-guards.html#global-before-guards
Try this
history.pushState({}, null, '/test') before the return next(false);

How to send a response from a method that is not the controller method?

I've got a Controller.php whose show($id) method is hit by a route.
public function show($id)
{
// fetch a couple attributes from the request ...
$this->checkEverythingIsOk($attributes);
// ... return the requested resource.
return $response;
}
Now, in checkEverythingIsOk(), I perform some validation and authorization stuff. These checks are common to several routes within the same controller, so I'd like to extract these checks and call the method everytime I need to perform the same operations.
The problem is, I'm unable to send some responses from this method:
private function checkEverythingIsOk($attributes)
{
if (checkSomething()) {
return response()->json('Something went wrong'); // this does not work - it will return, but the response won't be sent.
}
// more checks...
return response()->callAResponseMacro('Something else went wrong'); // does not work either.
dd($attributes); // this works.
abort(422); // this works too.
}
Note: Yes, I know in general one can use middleware or validation services to perform the checks before the request hits the controller, but I don't want to. I need to do it this way.
As of Laravel 5.6 you can now use for example response()->json([1])->send();.
There is no need for it to be the return value of a controller method.
Note that calling send() will not terminate the output. You may want to call exit; manually after send().
You are probably looking for this:
function checkEverythingIsOk() {
if (checkSomething()) {
return Response::json('Something went wrong');
}
if(checkSomethingElse()) {
return Response::someMacro('Something else is wrong')
}
return null; // all is fine
}
And in the controller method:
$response = $this->checkEverythingIsOk();
if($response !== null) { // $response instanceof Response
return $response;
}
It's probably overkill, but I will throw it in anyway. You might want to look into internal requests. Also this is just pseudoish code, I have not actually done this, so take this bit of information with caution.
// build a new request
$returnEarly = Request::create('/returnearly');
// dispatch the new request
app()->handle($newRequest);
// have a route set up to catch those
Route::get('/returnearly', ...);
Now you can have a Controller sitting at the end of that route and interpret the parameters, or you use multiple routes answered by multiple Controllers/Methods ... up to you, but the approach stays the same.
UPDATE
Ok I just tried that myself, creating a new request and dispatching that, it works this way. Problem is, the execution does not stop after the child-request has exited. It goes on in the parent request. Which makes this whole approach kind of useless.
But I was thinking about another way, why not throw an Exception and catch it in an appropriate place to return a specified response?
Turns out, thats already built into Laravel:
// create intended Response
$response = Response::create(''); // or use the response() helper
// throw it, it is a Illuminate\Http\Exception\HttpResponseException
$response->throwResponse();
Now usually an Exception would be logged and you if you are in Debug mode, you would see it on screen etc. etc. But if you take a look into \Illuminate\Foundation\Exceptions\Handler within the render method you can see that it inspects the thrown Exception if it is an instance of HttpResponseException. If it is then the Response will be returned immediately.
To me the most simple and elegant way is:
response()->json($messages_array, $status_code)->throwResponse();
(you don`t need return)
It can be called from a private function or another class...
I use this in a helper class to check for permissions, and if the user doesn`t have it I throw with the above code.

Clearing Flash Message in CakePHP 3.1.1

I'm trying to clear a Flash Message in CakePHP 3.1.1
I have a function when a user logs in, if his customer data is not complete, he is redirected to a form to complete it. That looks like this:
public function index()
{
//Some code to check whether the customers profile is complete
//If it's not complete, then redirect to the "complete" action with a flash message
if($completion == 0){
$this->Flash->success(__('Please complete your customer profile data.'));
$this->setAction('complete', $custid);
//Otherwise go to their view
} elseif ($completion == 1){
$this->setAction('view', $custid);
}
}
This works fine, and user is redirected to the Complete action/Form with the Flash Message.
Then the Complete action looks like this:
public function complete($id = null)
{
//Get all the customer data input
if ($this->request->is(['patch', 'post', 'put'])) {
$customer = $this->Customers->patchEntity($customer, $this->request->data);
//Set the completion status to complete (1)
$customer->completion_status = 1;
if ($this->Customers->save($customer)) {
$completion = 1;
$this->set('completion', $completion);
//After the Save, redirect to the View action with a new Flash Message
$this->Flash->set(__('Your customer information is complete and has been saved!',['clear'=>'true']));
return $this->redirect(['action' => 'view',$custid]);
} else {
$this->Flash->error(__('The customer could not be saved. Please, try again.'));
}
}
$this->set(compact('customer'));
$this->set('_serialize', ['customer']);
}
It work fine BUT: When the user is redirected to the View action with the Success Flash after saving their data, the Flash from the Index (telling them 'Please complete your customer profile data.') still shows up again.
If the user refreshes on the view, then both Flash messages go away as they should.
How can I clear that initial Flash message when redirecting? I've tried using the clear key but it seems to not be working.
Any advice is greatly appreciated!
Thanks,
DBZ
You can add this in the beginning of your action
$this->request->session()->delete('Flash');
to delete more specific you can do e.g. to delete only the messages from AuthComponent
$this->request->session()->delete('Flash.auth');
One more thing:
you can handle which flash-messages are displayed in the view like:
this->Flash->render('auth');
or
this->Flash->render('error');
If you don't display a flash-message its kept in the session until you display it somewhere or remove it from session.
Flash message are stored in session, so just clear the relevant session key: $this->Session->delete('Flash.flash') or $this->Session->delete('Flash')
Check to make sure that you aren't always getting $completion == 0 (which could match FALSE as well).
I suspect this is why your flash message is always showing.
Cake will automatically delete a flash message after it displays it.
Apparently you are passing a string to clear instead of a boolean:
New in version 3.1: A new key clear was added. This key expects a bool and allows you to delete all messages in the current stack and start a new one.
Try setting true without the quotation marks:
$this->Flash->set(__('Your customer information is complete and has been saved!'),[
'clear'=> true
]);
New in version 3.1: Flash messages now stack. Successive calls to set() or __call() with the same key will append the messages in the $_SESSION. If you want to keep the old behavior (one message even after consecutive calls), set the clear parameter to true when configuring the Component.
Use like this:
$this->loadComponent( 'Flash', ['clear' => true] );

Anyway to redirect to previous URL after registration in Joomla?

I am developing a component that required login at some level, then if user is not logged in, I placed a login link, that take user to login page with following in query string.
return=<?php echo base64_encode($_SERVER['REQUEST_URI']);?>
After login, it comes back to that page, but is there some way to tackle this if user is not registered and user starts registering? Is there some way to do this without changing some thing in Joomla it self? like by just setting some thing in cookie e.t.c. Or I will need to change some thing in Joomla Registration component or module. Or is there some plugin for that?
Any response will be appreciated, please tell what ever way you know so that it may give me some better clue.
In your component you could try to store the referrer in the Joomla! session - I don't believe the session changes or is replaced during login. I haven't had time to try this but it should work.
To Save:
$session = JFactory::getSession();
$session->set('theReferrer', $_SERVER['HTTP_REFERER'], 'mycomponentname');
To Retrieve:
$session = JFactory::getSession();
$redirectTo = $session->get('theReferrer', '', 'mycomponentname');
Then you can just use a setRedirect before you return.
$this->setRedirect($redirectTo);
You can achieve this with a plugin (at least in Joomla 3.x - not sure how far back this will work off-hand). Key here is the onUserAfterSave event, which tells you whether the user is new or existing.
I wrote the code below some time ago, so can't recall the exact reason the redirect could not be done from within the onUserAfterSave event handler, but I think the redirect is subsequently overridden elsewhere in the core Joomla user management code if you try to do it from there, hence saving a flag in the session and checking it in a later event handler.
class PlgUserSignupRedirect extends JPlugin
{
public function onUserAfterSave($user, $isnew, $success, $msg)
{
$app = JFactory::getApplication();
// If the user isn't new we don't act
if (!$isnew) {
return false;
}
$session = JFactory::getSession();
$session->set('signupRedirect', 1);
return true;
}
function onAfterRender() {
$session = JFactory::getSession();
if ($session->get('signupRedirect')) {
JFactory::getApplication()->redirect($_SERVER['HTTP_REFERER']);
$session->clear('signupRedirect');
}
}
}

CakePHP 2 AJAX redirections

I'm using AJAX in my web-app stuffs like search but if the user has been logged out, the ajax function return nothing because the redirection (from the action 'search' to the action 'login') has not been handled correctly.
Is it possible to redeclare the method 'redirect' in AppController to render the right action when a redirect hapend in an AJAX call ?
Thank you,
Sébastien
I think your best bet would be to setup you ajax to call to respond correctly to an invalid response. As it seems to be an important part of your app I would pass a 'loggedin' variable with every ajax request, so the client can tell as soon as the user has been logged out.
Update
In the case you want to keep a user logged in, you simply have to put the logged in/cookie check in something like your AppController::beforeFilter() that gets run with every request. for example:
public function beforeFilter() {
if($this->Auth->user() {
// USer is logged in, it's all gravy
} else {
// User is not logged in, try to log them in
$userData = $this->Cookie->read('User');
if(!empty($userData)) {
// Function that grabs info from cookie and logs in user
}
}
}
This way there will be no redirect as the user will be logged in as long as they have a cookie.
Another approach would be to allow everyone access to the Ajax function:
public function beforeFilter() {
$this->Auth->allow(array('my_ajax_method'));
}
And then check the user is authenticated in the method itself:
public function my_ajax_method() {
if (!$this->Auth->user()) {
//user not authenticated
$result = "requires auth";
}
else {
// use is authenticated
// do stuff
$result = 'result of stuff';
}
$this->set(compact('result'));
}
You will need to check the result of the ajax call in your javascript and act accordingly.

Resources