Chrome pre-fetch causes duplicate flash message - php

In my Symfony 3.0.6 application, certain common routes are not available until a user's registration has been finalised. Until this has been done, I use an AccessDeniedHandlerInterface to redirect them to a route that prompts them to finalise it. I also use a flash message to explain what has happened:
/**
* Handles an access denied failure.
*
* #param Request $request
* #param AccessDeniedException $accessDeniedException
*
* #return Response may return null
*/
public function handle(Request $request, AccessDeniedException $accessDeniedException)
{
if (null === $token = $this->security->getToken()) {
return;
}
if (!is_object($user = $token->getUser())) {
return;
}
/** #var User $user */
if (!$user->isFinalised()) {
$request->getSession()->getFlashBag()->add('info', 'You need to complete your registration before you can do this!');
return new RedirectResponse($this->router->generate('app_registration_complete'));
}
return;
}
However, a problem arises with Chrome's pre-fetch/pre-rendering service - if Chrome decides a URL is likely to be visited, it will pre-fetch it. A pre-fetch results in the flash message above being added to the user's session, so any subsequent page load means there are shown the message. If they do in fact go to a route they are not yet allowed to access, they see the message twice - one generated during the pre-fetch, and one generated when they actually navigated.
A similar issue is described in this question, but the accepted answer (use a POST for logout requests) doesn't really seem like the right solution in this context. The flash message is more of an explanatory addition to a GET redirection, as opposed to something that changes the state of the application. Also, there are many ways a user might reach one of these disallowed routes, and it doesn't make sense to me to convert all of these to trigger a POST, just to get around a pre-fetch issue in certain situations.
So... how should I deal with this?

I see one problem with your code. You should change to this:
if (null === $this->security->getToken()) {
return;
}
I don't think this will resolve the problem though.

Related

Laravel Spark 403 - REDIRECT HOST MISMATCH when 3D Secure Payment

I am using Laravel Spark for billing my customers. In case they will pay with 3D Secure cards (I test it with card no. from stripe doc's https://stripe.com/docs/testing#regulatory-cards: 4000 0027 6000 3184) I have the following problem:
The stripe pop-up/modal opens and I click 'Complete Authentication'.
After that the authentication process starts and the user gets directed to http://127.0.0.1:8000/stripe/payment/pi_xxx?redirect=/home
Here I get the following Laravel error (I do not find other error causes in console or somewhere else):
I've added stripe/* in VerifyCsrfToken Class already...
Maybe do I need to test this case on my server?
Very weird and I guess thats a sign that I do not have any issues with stripe, with laravel instead. When I remove the query parameter ?redirect=home I get this screen:
When I proceed I do not get redirected.. Of course because there is no redirect uri...
Does any one had this issue before?
For me this is a bug in Spark. I've searched for all occurences where Stripe used a redirect. One indication for me that this could really be a bug is:
In subscription-notice.blade.php file there ist the link build as follows:
{!! __('Please :linkOpen confirm your payment :linkClose to activate your subscription!', ['linkOpen' => '', 'linkClose' => '']) !!}
The part '?redirect='.url('/home').' creates a full valid URL with host address.
Not only a relative path! These relative paths runs into the 403 Error in my case.
Like in the RegisterController:
/**
* Handle a registration request for the application.
*
* #param \Laravel\Spark\Contracts\Http\Requests\Auth\RegisterRequest $request
* #return \Illuminate\Http\Response
*/
public function register(RegisterRequest $request)
{
list($user, $paymentId) = Spark::interact(
Register::class, [$request]
);
Auth::login($user);
event(new UserRegistered($user));
if ($user instanceof MustVerifyEmail && ! $user->hasVerifiedEmail()) {
$user->sendEmailVerificationNotification();
}
return response()->json([
'redirect' => $paymentId ?
'/'.config('cashier.path').'/payment/'.$paymentId.'?redirect='.$this->redirectPath()
: $this->redirectPath(),
]);
}
$this->redirectPath() returns a relative path. I've changed this part into:
return response()->json([
'redirect' => $paymentId ?
'/'.config('cashier.path').'/payment/'.$paymentId.'?redirect='.config('app.url').$this->redirectPath()
: $this->redirectPath(),
]);
In this case I took the host address from my config and put it in front of the relative path.
Just for better understanding, the returned URL above is used here (register-stripe.js):
/*
* After obtaining the Stripe token, send the registration to Spark.
*/
sendRegistration(paymentMethod) {
this.registerForm.stripe_payment_method = paymentMethod;
Spark.post('/register', this.registerForm)
.then(response => {
window.location = response.redirect;
});
}
There are some more cases where I needed to override some JavaScript or PHP sources...
register process (showed here)
update process of payment information
create subscription with existing account
I hope I could help others with that! If necessary I could also post the exact places where I've changed the redirect URL in the comments.
I came up with a (dirty?) workaround:
In my StripWebHookController there is some code that makes a notification for the user:
if ($billable) {
$model = config('cashier.model');
$notifiable = $billable instanceof $model ? $billable : $billable->owner;
if (in_array(Notifiable::class, class_uses_recursive($notifiable))) {
$payment = new Payment(StripePaymentIntent::retrieve(
$payload['data']['object']['payment_intent'],
$billable->stripeOptions()
));
$notifiable->notify(new $notification($payment));
}
}
Now, this is a notification that apparently makes the StripePaymentIntent notification that is created in the PaymentController of Cashier (located /var/www/html/site/vendor/laravel/cashier/src/Http/Controllers)
There is a VerifyRedirectUrl middleware that is causing the problem right. So when you comment it out, the 403 disappears:
public function __construct()
{
//$this->middleware(VerifyRedirectUrl::class);
}
However, the "go back" button after accepting the payment is not working so I'll check that out. But for now, this 403 is at least gone. If I can't another solution, I'll go with this.

How to roll back changes and display error when an event throws an exception in Laravel

TL;DR
I would like to know how I should handle an exception that occurs in an event and subsequently rollback changes made to the database prior to the exception, provide feedback to the user and write a test for all of this too.
Full Details
In my Laravel web application I have four models: Applicant, Application, ApplicationAction and Document. An Applicant can have one Application which can have many ApplicationActions. Also, an Application can have many Documents.
Conceptually, within the application when an ApplicationAction is created it is applied to an Application which in turn has its status attribute updated. For example, if an ApplicationAction is of type reject the Application's status then becomes rejected.
Getting into the code where this all happens, I have an ActionController with a store() method that creates a new ApplicationAction:
ActionController.php
public function store(StoreActionFormRequest $request)
{
$actionDetails = (new ApplicationAction)->createFromRequest($request);
}
ApplicationAction.php
public function createFromRequest(Request $request)
{
return $this->create([
'action' => $request->action,
'action_meta' => $this->actionMeta($request),
'details' => $request->details,
'details_external' => $request->details_external,
'action_by' => $request->user()->id,
'application_id' => $request->application->id,
]);
}
On the creation of a new ApplicationAction the ApplicationActionCreated event is fired with a listener that modifies the Application's status attribute based on ApplicationAction. I am not including the code in as it's not relevant.
When the Application is updated in this process (i.e. its status is changed) an event is triggered: ApplicationUpdated. A listener for this event is the NotifyApplicationWasUpdated listener which sends an email to the Applicant:
NotifyApplicationWasUpdated.php
/**
* Handle the event.
*
* #param ApplicationUpdated $event
* #return void
*/
public function handle(ApplicationUpdated $event)
{
if ($mailable = MailableType::resolve($event->application)) {
$to = $event->application->applicant->user->email;
$from = $event->application->applicant->dispatchEmail('verification');
Mail::to($to)
->bcc($from)
->send(new $mailable($event->application));
}
}
The mailable is determined dynamically (code not relevant) and within the mailable an attachment is added to the email:
A dynamically determined MailableClass
/**
* Build the message.
*
* #return $this
*/
public function build()
{
//... some code
$documents = $this->application->documents->whereIn('type', ['some-document', 'some-other-document']);
$documents->each(function ($document, $key) {
// TODO: check the file exists first
$this->attach(storage_path($document->file_path));
});
return $this->from($from)->view($view);
}
I would like to handle the FileNotFound exception if the file to be used as an attachment cannot be found. If this exception occurs then the email would not be sent, so I thought it would be a good idea to rollback the changes made to the database prior to the exception which would include deleting the newly made ApplicationAction and returning the Application's status to its previous value.
I would also like to provide the user appropriate feedback such as the file intended to be used as an attachment couldn't be found and also that the ApplicationAction was not created and the Application status was not modified.
The approaches I have considered to handling the FileNotFound exception in the event that the Document cannot be found when executing this line of code $this->attach(storage_path($document->file_path)); is to check if the file exists first and if it doesn't then to rollback changes and respond to user with an error:
if(!file_exists(storage_path($document->file_path))) {
// roll back DB changes and show user error page
$this->application->rollback(); // some method that sets the status to its previous value, not implemented it yet
$this->application->lastAction()->delete(); // delete the last action
// then throw an exception to be handled by Laravel exception handler
// which in turn responds to user with an appropriate message?
}
$this->attach(storage_path($document->file_path));
...but I'm a bit unsure on the details of how to rollback the DB changes and show the error to the user. I could either use the above approach, having a Application rollback() method etc. or instead wrapping the controller store() action in a try-block and DB::transaction()/commit(), which would look something like this:
ActionController.php
public function store(StoreActionFormRequest $request)
{
DB::beginTransaction();
try {
$actionDetails = (new ApplicationAction)->createFromRequest($request);
} catch(FileNotFoundException $e) {
DB::rollback();
throw $e;
}
DB::commit();
}
Then, as mentioned before, use the Laravel exception handler which in turn responds to user with an appropriate error message:
Handler.php
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $exception
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
if ($exception instanceof FileNotFoundException) {
return response()->view('errors.file_not_found', [], 500);
}
return parent::render($request, $exception);
}
If I did use this approach would it be possible to pass some specific text explaining to the user the email was not sent and why? Would I be better off creating my own EmailAttachmentFileNotFoundException and have it hardcoded in the view and using this in Handlers render() function?
Finally, I would most importantly like to ask how may I write a test for all the above? As most of the logic I'm trying to implement is around an event, which is currently occuring synchronously, how would I write a test for this specific event/listener because I already have something testing the controller action in the form of an integration test but it all it tests for is if the event is fired. How would I then write an isolated test of a test that goes smoothly, i.e. the email is sent successfully and secondly how would I write a test that goes wrong and check it's handled correctly, i.e. email attachment is not found and FileNotFound exception is triggered then check the DB stuff is rolled back and the user gets the correct error page?
Any help would be appreciated and if this question can be made any shorter please feel free to edit away!

Laravel Auth0 Unauthorized user

My application is a single page app using Angular 1.x on the client side and Laravel 5.3 for my server/api. I easily managed to make the Auth0 authentication working on my client side (angular) and it successfully generates a token. Moving to my api (laravel), unfortunately I can't access any routes that is protected by the auth0.jwt middleware even though the Authorization header is present. Its response is a simple text that says Unauthorized user.
I'm using Chrome postman to test out the routes on my api.
I tried to trace down the function that is responsible for the response by checking the vendor\auth0\login\src\Auth0\Login\Middleware\Auth0JWTMiddleware.php and found out that the CoreException is being thrown.
Here's the handle method of the Auth0JWTMIddleware:
/**
* #param $request
* #param \Closure $next
*
* #return mixed
*/
public function handle($request, \Closure $next)
{
$auth0 = \App::make('auth0');
$token = $this->getToken($request);
if (!$this->validateToken($token)) {
return \Response::make('Unauthorized user', 401);
}
if ($token) {
try {
$jwtUser = $auth0->decodeJWT($token);
} catch (CoreException $e) {
return \Response::make('Unauthorized user', 401);
} catch (InvalidTokenException $e) {
return \Response::make('Unauthorized user', 401);
}
// if it does not represent a valid user, return a HTTP 401
$user = $this->userRepository->getUserByDecodedJWT($jwtUser);
if (!$user) {
return \Response::make('Unauthorized user', 401);
}
// lets log the user in so it is accessible
\Auth::login($user);
}
// continue the execution
return $next($request);
}
My suspect is that the token that generates from Auth0 has newer algorithm or something and the Laravel Auth0 package doesn't already supports it.
I followed exactly the documentation provided by Auth0 and I also cloned their sample projects just to make sure the configurations are correct but unfortunately it doesn't work also. Any thoughts or ideas on how can I solve my issue? Thanks in advance!
I had the same problem. There were 2 things I had to change in order to resolve. Credit to #Kangoo13 for the first suggestion:
1; Check that in your config/laravel-auth0.php file that secret_base64_encoded = false. You will notice in your Auth0 dashboard next to your key it states "The client secret is not base64 encoded"
'secret_base64_encoded' => false,
2; in the same config file, Check that 'supported' has the correct spelling. It would appear someone has incorrectly typed "suported", if you've just applied the default config file after running composer then chances are this is wrong!
Looking in JWTVerifier.php it does appear to cater for the misspelled key but it will default to 'HS256', Auth0 guide explicitly states you should be using 'RS256:
'supported_algs' => ['RS256'],
Hope that helps!
I read the suported_algs issue above but skim read it and missed the fact you have to correct the spelling for it to work, spent an extra day trying to figure it out before re-reading. Hopefully next person to read it sees this and doesn't miss the spelling issue! Thanks #user1286856

Symfony2 RedirectResponse message

I have problem with redirect to another website. When I redirected I see message: "Redirecting to...". Why? Really I can't redirect to site without problem?
I see:
Is it possible to change the default redirect message in Symfony?
My code:
/**
* #Route("/project/{url}/{link}/", name="page_show")
* #Template("base.html.twig")
*/
public function pageAction($link, $url) {
if ($link == '...') {
$redrect = new RedirectResponse('http://...');
return $redrect;
}
Maybe I'm Idiot and don't see solution...
I'm not sure you can combine #Template("base.html.twig") with a redirect response. Try to remove the #template annotation and do a render of base.html.twig in the end of your action :
/**
* #Route("/project/{url}/{link}/", name="page_show")
*/
public function pageAction($link, $url) {
if ($link == '...') {
$redrect = new RedirectResponse('http://...');
return $redrect;
}
// Maybe add the proper path "BundleName:DirectoryName:base.html.twig"
return $this->render('base.html.twig');
}
Read closely
First, create your template 301.html.twig into your Acme/FooBundle/Resources/views/Error/ with the content you want.
Here:
Is it possible to change the default redirect message in Symfony?
Redirect to Another web site:
public function pageAction(Request $request)
{
return $this->redirect('https://google.com');
}
If this is happening in the development environment, then you have set the intercept_redirects configuration option to true. Set it to false as explained in: http://symfony.com/doc/current/reference/configuration/web_profiler.html
If this is happening in the production environment, the reason is that RedirectResponse has some hardcoded HTML content to do the redirection. See these lines of code: https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/HttpFoundation/RedirectResponse.php#L86-L96
During 1 second, you see the Redirecting to ... message. Changing this message is possible, but it requires you a lot of effort. Everything is explained here: Is it possible to change the default redirect message in Symfony?
Update: in this discussion in the Symfony repository there is more information about this. The redirect should be instantaneous because it should use the information provided in the response headers. If something wrong happens, then the hardcoded HTML content is used. So you probably need to debug why the response is not getting the right redirect headers.
Answer to your question is in official Symfony book.
http://symfony.com/doc/current/book/controller.html#redirecting
public function pageAction()
{
return $this->redirect('http://stackoverflow.com');
}
Unless you are really keen to redirect using the Symfony' RedirectResponse class, you can always relay on the old good PHP:
function redirectUrl($url, $replace=true, $status=302){
header("Location : ".$url, $replace, $status);
exit();
}
// usage example
redirectUrl("http://www.google.com");
This not only works like a charm but is extremely fast in comparison with Symfony's internal function because there is only one call + die/exit.
Read more about the PHP's header function.
However, since this approach is totally decoupled from the framework, the Symfony profiler won't be able to intercept your redirect so the developer toolbar won't notice/show this redirect. It depends what you want.

Laravel 5.1 - errors views on live website, which ones or one for all and how to implement?

I'm building a Laravel app and everything is working fine (as far as I can see) and now I would like to make the website public.
However I can't figure out how to implement custom messages for bad/unauthorized request.
I know I can put views in /resources/views/errors/ folder like:
404.blade.php
503.blade.php
... and so on.
However I'm not sure for which errors should I build views or is there a simpler way to make one main view for all errors. There is no reason to notify user which error occurred but if it can be done easily I would go that way.
Here is the list of all errors that I just Googled:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
Here is the Laravel 5.1 error documentation:
http://laravel.com/docs/5.1/errors
I tried changing the file /app/Exceptions/Handler.php and in it function
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $e
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
return parent::render($request, $e);
}
to
/**
* Render an exception into an HTTP response.
*
* #param \Illuminate\Http\Request $request
* #param \Exception $e
* #return \Illuminate\Http\Response
*/
public function render($request, Exception $e)
{
return view('errors.allerrors'); // only this line is changed
}
but that only returns empty page.
Thanks.
I will leave this as a partial answer in case someone ends up here with the same problem.
The answer will not redirect all errors to the same page but will handle most of the errors, as far as I can tell.
Add these files to the /resources/views/errors/ folder:
401.blade.php - this page will be shown if user is not authorized to access the webpage (when you are using some kind of user authentification)
404.blade.php - this page will be shown if user writes url that you haven't declared in your /app/Http/routes.php file
503.blade.php - this page will be shown when your site is down, ie. when you run the php artisan down command
There is still one error page that I don't know how to hadle, better say, I don't know its error number. It shows when an user tries to access the model instance (I don't know the right terminology here so please correct me) that doesn't exist. In example; if an user tries to acces yourwebsite.com/articles/123 but you don't have article with that id/slug in your db you will still get an whoops... message.
I am not completely satisfied with this solution, since there might be other errors I wasn't able to produce, but it's best/only solution I have.

Categories