coding

Laravel - JSON - Failed Authorization Returns HTML Instead of JSON

ISSUE:

JSON requests to backend that fail middleware checks return HTML page vs JSON Object

;TLDR:

Within each middleware check created, add a JSON check for the request, and included the appropriate message as well as the status code.

DETAILS:

I am in the process of changing the CRUD operations of my template to Vue/axios instead of old school page posts to a backend controller. 

Prior to this change, I was using a combination of middleware to verify access to the controller, and individual policies for each of the commands (Create,Read,Update,Delete).  For example, I may want a certain class of user to be able to view the resources, but only administrators can update the resource.  I grant both roles the "view-{modelName}" permission, where {modelName} is the actual model name of the resource (location, company, etc).  Then I grant the admin role the permission of 'edit-model', and use the policy of the resource to validate their access.

In changing over to a json POST, I'm now expecting a JSON response - which works great for successful submissions and validation errors.  However, I was finding that in my authorization tests that verify a specific user can not access a resource, I noticed that my JSON requests were always returning the HTML "Insufficient Privileges" page instead of the JSON version.

This is because the user verification was not passing my middleware permissions check. It was working as intended, but was only setup to return the HTML response and NOT JSON.

CODE:

App/Controllers/Middleware/CheckPermission.php:

public function handle($request, Closure $next, ...$permissions)
{
    $userRole = $request->user()->getSelectedRole();

    if ($userRole !== null) {
        foreach ($permissions as $permission) {
            if ($userRole->can($permission))
            {
                return $next($request);
            }
        }
    }
    // here is the change I added
    if ($request->expectsJson())
    {
        return response()->json(['success' => false,'message' =>"Sorry, you are not authorized"], 403);
    }

    return redirect('/insufficient-privileges');
}

Laravel EagerLoading ALWAYS INCLUDE...

ISSUE:

There are certain models that have relationships that I ALWAYS want loaded.  

 

QUICK FIX: 

In the model for the item, add a protected $with variable.  See the following code:

Class MyModel extends Model {
protected $with = ['relation'];
}

In my case, I have a Location model, and the associated model is an Address model.  Locations can have multiple addresses, and MUST have at least one address.  In my case, the code would look like the following:

Class Location extends Model {
protected $with = ['addresses'];
}

Now, whenever I return a location object, it will automatically include the associated addresses with it.

Caveat?

This definitely isn't the solution for EVERY associated relationship - only the ones that are likely to be needed 80% or more of the time.  Perhaps even a little less.  However, the solution that Eager Loading provides outweighs most of the concerns that might come up from loading a small amount of resources every time.

Vue - Firing Emit On Parent Component - this.(functionName) not found

;TLDR

In components that execute methods based on events emitted from child components, make sure to set the vue instance = to a variable prior to any $emit.on functions within the parent component's mounted() section:

parent-emit-10.png

 

Background:

I have an addresses component, which is made up of several address components in vue.

Each address component consists of an <address> tag, an "Edit" button, an "Edit Form" and a "Delete" button (also another Form, specifically for deletions). After performing an action on the individual "address" components, I need to refresh the entire "addresses" component to remove any that have been deleted OR edited.  Also, you might notice the "Create Address" button. This is another component that consists of the button, and a "create address" form.

To Recap, the addresses component contains:

  1. A Create Address child component (button  and form)
  2. An address child component (the displayed data, an edit form and a delete form)

I am employing the use of the global event bus, setup in Laravel automatically.  Please keep this in mind.

parent-emit-1.png
parent-emit-2.png
parent-emit-3.png

Background 2  - setting Up the Emit:

In the "Address.vue" component

parent-emit-4.png
parent-emit-6.png

In the my "Delete.vue" component (the actual delete form):

parent-emit-5.png

In  the "Addresses.vue" component (where we are trying to catch the events):

parent-emit-7.png

 

 

Problem:

After catching an event from the global command bus, I received the following error: "Error in event handler for "address:deleted": "TypeError: this.reloadAddresses is not a function".

This happens because "this" references the event that is firing, vs the actual vue instance I am on.  As the "reloadAddresses" function exists outside that reference, it cannot be found. 

In NON-COMPONENT vue systems, I normally set the page's vue instance to "pageViewInstance", and I can then reference that throughout the page's life:

parent-emit-8.png

You would then replace "this.reloadAddresses()" with "pageVueInstance.reloadAddresses()"...or create a value within the mounted function " let vm = pageVueInstance;"

However, I don't think I can do that here, because I am actually performing this within another component (where we are "Exporting Default" at the top of the page) - I also don't want to clutter up the top of my template:

parent-emit-9.png

FIX:

Set the variable within the MOUNTED section of the Addresses component (the parent component), before the emit variables are called:

parent-emit-10.png

Setting the vm = this now referencees the Vue instance of the parent component.  As such, it now has access to the "reloadAddresses()" function created in the "methods" section.

Laravel Tests, Model Arrays and Appended Attributes

Problem:

After creating "appended" columns in a Laravel model, tests that converted that model to an array (or JSON) began to fail as they were trying to insert the appended column to the database.

UGH - Quick Fix Please:

Before converting your model toJSON or toArray, use the makeHidden('appended_column_name') option to hide any specific appended columns.

OK Keep Talking.

In my template, I have three models that have images associated with them.  Instead of hard coding the route for the image, I have employed the use of an appended attribute.  This is one that is created on the model that is in addition to the columns within the database. You can read more about appended attributes here.

This means instead of coding "/path/to/image/image.png", I can just call model->image_url. BOSS.  Ok.

In my Unit Tests, I have created several helper functions that make live easier overall when creating the tests themselves.  For example, in my "CompanyTest", I have a function that creates a company, and I call this from other functions where I need a default company created.

1. The make is a helper function that just returns a factory created model of the class passed in.2. When posting to /admin/companies, I pass in the $company-&gt;toArray, which automatically passes in all the values needed to save it to the database.

1. The make is a helper function that just returns a factory created model of the class passed in.

2. When posting to /admin/companies, I pass in the $company->toArray, which automatically passes in all the values needed to save it to the database.

After making my "appends" change, tests that used this helper function no longer passed.  Specifically, I received the following message:

appends2.png

When converting a model toArray() or toJson(), any appended values are also included.  This means that when I passed the array(ed?) model to the post endpoint, it tried to insert the image_url field into the database.  As that column does not exist, the insert failed.

WHAT TO DO? Hide That Attribute, Yo!

It's possible to hide specific attributes prior to converting the model to an array.  You can read more about that here.  Once I made the change, all tests passed again (whew).

append3.png

Vue - Checkbox Verification Component

This post assumes you have already have Vue working within your application and have setup the ability to use components.  Laravel does this for you automatically, but setting that up is covered elsewhere (see here, herehere or here).

Background

Today I was working through a template I maintain for client projects.  It contains items that I continually have to setup for any project (roles, permissions, companies, locations, users).  I've been developing it since Laravel 5.3 (notably not long), but it has helped tremendously not only in saving time, but also implementing new pieces into an existing framework.

Case in point is that today, I was working through an aspect where I wanted to have the user check a checkbox verifying they wanted to delete a resource, prior to being able to click the delete button.  This is kind of a "are you suuuuuuure" without the implementation of a modal dialog box.

I've been turning to Vue a lot lately (as are all the cool kids), and this seemed like this request might check all my personal boxes for using Vue:

  1. Is this code likely to be reused throughout the application? Yep.  Anywhere I want the user to verify something prior to allowing them to proceed further.
  2. Does this take a non-trivial task, and make it super-easy? Yep.  Not that making a check-box trigger javascript is difficult...but it is something that can be done once and forgotten in Vue.
  3. Will it save time? I can make Vue do all kinds of cool things, but if I need to just change the color of a button, maybe just stick with plain old javascript.  Let's make sure this isn't one of those cases.  It's not!

My Requirements:

  1. I want it to be a reusable template, and allow for the use of slots.  This means that anything I put within the <ct-verify></ct-verify> tags would be hidden until the checkbox is checked.
  2. I want to pass through the verification message as a property.  This way it's not a generic "I agree" message.
  3. Setup a default message, in case I don't pass a verification message through.
  4. Create this as a component, to allow for the item to be used across various projects.


Here's What It'll Look Like

Before Click:

vue-verify-before-click.png

1. Notice that there is nothing in the HTML where the text should be.  Love me some Vue.


After Click:

vue-verify-after-click.png


1. Bam. Form available.

 

Additionally, when the checkbox is NOT clicked, the hidden html is actually removed from the page.  This means that it cannot be accessed by a dastardly individual with the means and attitude to cause a problem.  

When Clicked (see the form tag)

verify-clicked.png

 

When Not clicked

verify-not-clicked.png

 

Required coding within the blade/html/php page:
```
<ct-verify agreement="By clicking delete, I verify I am removing all data related to this company.">
    <b>WHATEVER YOU WANT</b><br>
    <button type="submit" class="button is-danger">ANYTHING</button>
</ct-verify>
```

Caveats


1. I use the Laravel framework as I love it.  But this tutorial can be done without the use of that as long as you pull in the appropriate items (https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js)
2. I am using the Bulma framework for the css styling.  I did this to avoid the use of jquery in the latest version of my template.

Solution

 

Component File

verify-component.png

Component Explanation:

  1. Props - two properties are required and both have defaults.  The first property is "agreement" and this is the text that you want the visitor to agree to prior to being "shown the goods".  The "selected" property is a boolean and is what we use to initially set the checkbox value.  There may be cases where you want it checked by default.  Passing through a value of "true" when the component is called will do this.
  2. Data - this contains one item "isSelected" which is a boolean set to false.  Per Vue 2.0 standards, we shouldn't edit a prop directly (they are supposed to be immutable), so we create a new value in the data section.
  3. mounted - after the component is loaded, we set the component's isSelected data value  = the selected property that was initially passed in.  This is the ONLY time we care what was passed in from the prop.  From here on out, it just uses the "isSelected" value to trigger visibility.
  4. methods - this is where the magic happens.  We have a function called updateSelected(), and we set the component's "isSelected" function = to the opposite of what it's currently set to (this.isSelected = ! this.isSelected).  WTH? 
  5. If you look at the <input> section of our template, you will see  the following code @change="updateSelected".  This is vue's way of saying "any time this checkbox is clicked, fire the updateSelected function."  The updateSelected function changes the component's isSelected value to the opposite of what it was before...effectively a toggle.

 

Calling From HTML / PHP
 

verify-html.png
  1. Notice that the only thing I am passing through is the agreement text.  As we are setting the "selected" value as a default of false, I have no need to pass that through as a property.
  2. The <ct-delete> form is another vue component I created. However you can place whatever you'd like instead of this.  I mean anything.

Files

Component-Verify.vue File

Component-Verify.html