Whats the point of composing middleware in Koa? - koa

I am diving into Koa2 and I see koa-compose. I get that I give it middlewares and it returns one, but why? What is the benefit of having multiple middleware wrapped as one instead of just adding them separately?
app.use(compose(m1, m2))
vs:
app.use(m1)
app.use(m2)

KoaJS uses koa-compose underneath (here), so app.use(compoase([m1,m2])); and app.use(m1); app.use(m2); are the same. Using koa-compose explicitly can give more power for customization. Following is one such case:
Adding middlewares through app.use(middleware), will cause all of the middlewares to be executed upon each request in the specified order. But if you want to selectively run different set of middlewares for each route (or in a different order), you can use explicitly use koa-compose to create specialized middleware stacks for each route.
var app = require('koa')();
var router = require('koa-router')();
var compose = require('koa-compose');
var allMiddlewares = compose([m1,m2,m3]);
router.get('/', allMiddlewares);
// selectively enable logging middleware for this route
router.get('/test', compose(logger, allMiddlewares));
app
.use(router.routes())
.use(router.allowedMethods());

I had the same questions of why we need to use koa-compose, since koa itself can handle multiple middlewares. But recently I have been working on the authentication part of my koa server.
I have to check if user is authenticated and sometimes I need to check if user role meets the requirement. In that case, I have two middlewares one is called isAuthenticated, another is hasRoles
Some routes expose to any user that is authenticated, so I can do
.get('/', auth.isAuthenticated, handler())
But for routes need to check if user role meets the requirement, I need to do
.get('/', auth.isAuthenticated, auth.hasRole('admin'), handler())
When I have other authentication middlewares, the middlewares I put in the route becomes pretty long.
I am benefited by using koa-compose, since in my case I can chain the isAuthenticated and hasRoles middlewares together.
requiresRole(role) {
return compose([isAuthenticated, hasRole(role)])
}
.get('/', auth.requiresRole('admin'), handler())
It's neat and less errors.

Related

how to implement Single Responsibility in laravel

I am so confused about how to implement and how to follow SRP (single responsibility principle ) in a Laravel controller.
Suppose we have a controller which we have to do these things:
e.g
public function StorePost() {
// check user login()
//check number of current user Post count =>which must be less than 10
//store post
//send an email to user which your post has saved
//return =>api:json /web : redirect
}
I know that I can implement some DB queries in the repository but I don't know how to implement others of my logic code to achieve SRP
Also, I know there is a Heyman package to achieve these but I want to implement it by myself.
SRP in this context basically means each class and method should only be responsible for a single behaviour/feature. A rule of thumb is a class or method should change for one reason only, if it changes for multiple reasons, it needs to be broken down into smaller parts.
Your storePost method should not bother with checking the user login, that should be handled elsewhere before invoking storePost. storePost shouldnt change if the auth mechanism changes like switching from api token to json web token or something else. Laravel does this in the middleware level with the auth middleware.
Checking the users post count, this can be checked in the validation stage. storePost shouldn't change if we add more validation logic. In Laravel you can use FormValidation for this
For storing the post, the controller doesn't need to know how to call the DB, you can use the active record style using the model class or maybe create a service or repository class if your use case requires that. storePost shouldn't change if we decide to change DB vendor like going NoSQL.
For sending email, again the controller doesnt need to know how to send the email like what the subject/body recipients are. storePost shouldnt change if we need to change the email layout. Laravel has Notification for that
For serialising the response to json, the controller doesnt need to know how to format the response. if we decide to update how our json looks, storePost shouldnt change. Laravel has API Resources for that
So, ultimately in this example, the responsibility of the controller method is basically to glue all these together. It basically does what you wrote down, it only responsible for maintaining the step by step behavior, everything else is delegated to someone else. if the behavior change, like adding new behavior e.g notify all follower, storePost will change.

Links in view files that are called through different named routes

Our team is trying to convert an old multitenant architecture over to Laravel and having some difficulty staying DRY in our routes/views.
Our account section contains multiple page views: an account dashboard, edit profile, payment methods, registrations, etc. However, we provide multiple stand-alone websites for a client, and there are multiple URL structures for the user to access their account:
In addition to the standard...
https://clientone.ourdomain.com/account/
...they can also access their account via different standalone websites:
https://clientone.ourdomain.com/event-app-one/account/
https://clientone.ourdomain.com/event-app-two/account/
All of those URLs above give them the exact same information, but with a different layout/theme applied for each standalone website. Right now the routes I set up for those above look like this:
// #### CLIENT ONE - ROOT
Route::domain('{subdomain}.ourdomain.com')->group(function() {
Route::middleware(['setTheme:clientone-account-theme'])->group(function() {
Route::get('/', 'ClientController#index')->name('index');
Route::prefix('account')->group(function() {
Route::get('/', 'AccountController#index')->name('clientone.account.show');
Route::get('/edit', 'AccountController#edit')->name('clientone.account.edit');
...
});
});
// #### CLIENT ONE - EVENT APP ONE
Route::prefix('event-app-one')->group(function() {
Route::middleware(['setTheme:clientone-eventapp-one-theme'])->group(function() {
Route::get('/', 'EventAppController#index')->name('clientone.eventapp.one.index');
...
Route::prefix('account')->group(function() {
Route::get('/', 'AccountController#index')->name('clientone.eventapp.one.account.show');
Route::get('/edit', 'AccountController#edit')->name('clientone.eventapp.one.account.edit');
...
});
});
});
// #### CLIENT ONE - EVENT APP TWO
Route::prefix('event-app-2')->group(function() {
Route::middleware(['setTheme:clientone-eventapp-two-theme'])->group(function() {
Route::get('/', 'EventAppController#index')->name('clientone.eventapp.two.index');
...
Route::prefix('account')->group(function() {
Route::get('/', 'AccountController#index')->name('clientone.eventapp.two.account.show');
Route::get('/edit', 'AccountController#edit')->name('clientone.eventapp.two.account.edit');
...
});
});
});
Those routes all currently work. We don't like including the client or app name in all of our route names because they get rather long and can't be reused, but if we take those out we found out our route URLs will override each other.
However, if we do it this way, our account views that we share across all these URLs can't use named route links for href because we don't know which one to use. For example, we can't do this in blade...
Edit Profile
...because, while that will work while at clientone.ourdomain.com/account/, it won't work while at clientone.ourdomain.com/event-app-one/account/.
Should I just not use route names for links in our views and use a relative link instead? Or am I going about my route naming all wrong? The only other thing I can think of is to copy all the view files and have one version for .com/account/ and another for .com/all-event-apps/account/ and just use a different set of route names for each. Feels wrong to duplicate essentially the same functionality in those views though.
You can use a parameter for the prefix so you are not repeating the same exact routes and creating new names for them. You can then generate routes based on those route names.
You can set defaults for parameters for the UrlGenerator to help deal with this.
I would add a middleware to your group that checks the prefix then will assign that as a default parameter on the UrlGenerator:
// instead of app-one, app-two, etc etc
Route::prefix('{app}')->middleware('handleapp')...
// handleapp middleware
Url::defaults(['app' => $request->route()->parameter('app')]);
$request->route()->forgetParameter('app'); // if you dont want this passed into controller methods
// where you need to generate a route
route('eventapp.account.edit'); // {app} is handled by the default we set so doesn't have to be passed
I would also have a middleware that will add the default parameter for subdomain as well so you don't have to pass that yourself everywhere.
Basically it all becomes dynamic and based on the current request you can have defaults setup for all these parameters so you don't have to pass them into the route helpers yourself everywhere you need a URL from a route name.
This is just the basic idea in use.

Use multiple guard on one route in laravel

How i can use multiple guard on one route in laravel?
I have two guard: admin-api, user-api and i want to check if token for user is valid can be accessible, and if token not valid in users table, check token in admins table.
I use the following code but just second middleware Applied.
Route::middleware('auth:api')
->middleware('auth:api-admin')
->post('/user' , 'UserController#user');
It's been a while this question asked, but the other way to check multiple guards within one route without adding a custom middleware is pass several guards to auth middleware:
Route::middleware(['auth:api,api-admin'])->post('/user', 'UserController#user');
You can add an array of middlewares to your routes
Route::middleware(['auth:api', 'auth:api-admin'])->post('/user', 'UserController#user');

OWIN Middleware & CallContext

I have a few middlewares before the OAuth Middleware and a few after it.
app.Use<Middleware1>
app.Use<Middleware2>
app.UseOAuthBearerTokens(OAuthOptions)
app.Use<Middleware3>
app.Use<Middleware4>
app.Use<Middleware5>
If I set something in the CallContext in Middleware1 or in Middleware2, it isn't available in Middleware3 or 4 or in any of the API controllers; whereas, if I set something in the CallContext in Middleware3 or 4, it's available in all the succeeding middlewares and the API controllers.
I guess UseOAuthBearerTokens is resetting or creating a new CallContext?? Has anyone encountered this?
I can get away from it by taking one of these approaches:
1. Use OwinContext instead of CallContext. Drawback - need to reference OWIN in all the projects that need the value set in the context.
2. In Middleware1, I can set the value in OwinContext, and in the middleware that follows the OAuth middleware, I can retrieve the value from OwinContext and reset it in CallContext
Let me know a proper solution for this.

Laravel 5 - Assign Middleware to Controller Namespace

I need to do an ACL check for the user before allowing access to the admin panel of a Laravel 5 website. What is the best way to do this for an entire controller group in the namespace App\Http\Controllers\Admin\*? Ultimately, I'm looking for a "set and forget" method to do this, and middleware looks like the best option so far.
The initial idea was to assign a middleware to the admin route, but this does not prevent any other non-admin route from accessing the controllers. This means a route can still target the admin controllers and bypass the ACL check.
The next idea was to insert the assignment in the constructor on the controllers, but this would require each additional controller to explicitly include the middleware. This would require a developer to know that the middleware should be included, which allows them to miss it entirely. This also applies to using one base controller as the parent for all admin controllers, since the developer would need to know that the base controller should be extended. Right now, this looks like the best solution.
This leads us back to the question: can middleware be assigned to a controller wildcard namespace like App\Http\Controllers\Admin\*? Or, is there a better way for the ACL check to never need to be explicitly assigned to every admin controller?
This leads us back to the question: can middleware be assigned to a controller wildcard namespace like App\Http\Controllers\Admin*?
No
The most simplest approach you can do is create a base controller such as App\Http\Controllers\Admin\Controller and include the middleware
while all other App\Http\Controllers\Admin\* extends it.
Alternatively, while still adding App\Http\Controllers\Admin\Controller, you could instead inject the middleware through IoC Container.
App::afterResolving('App\Http\Controllers\Admin\Controller', function ($controller) {
$controller->middleware('acl');
});
EDIT
My previous answer didn't quite work for all situations; it broke a lot of other routes. Here's what I ended up doing instead (in app/Http/routes.php):
Route::group(['namespace' => 'Admin', 'prefix' => 'admin', 'middleware' => 'App\Http\Middleware\Acl'], function()
{
Route::get('/', 'AdminController#index');
Route::get('/user', 'User\UserController#index');
...
});
This at least targets all admin controllers when I define a route. It's not exactly everything I had hope for, but it will do.

Resources