Why InitializeTenancyByDomain is not applicable for the login process? - laravel

This is for Laravel 8.x with Jetstream/Livewire scaffold having Stancl/Tenancy. The initialization of tenant models or session settings not working right. Either I did not do it right or inbuilt problem.
The entire package was built as per instructions of Stencl/tenancy v3.x. I can see the dd(\App\User::all()) as per code outlined below
Route::middleware([
'web',
InitializeTenancyByDomain::class,
PreventAccessFromCentralDomains::class,
])->group(function (){
Route::get('/', function () {
dd(\App\User::all()); //can see all users models in tenants table
return view('welcomeTenant');
});
Route::get('/home', [
'middleware' => ['auth'],
'uses' => '\App\Http\Controllers\HomeController#index'
])->name('home');
});
This meant InitializeTenancyByDomain right to me.
When a login form is requested from tenant's domain eg. from rtbs.example.in, the encrypted session/cookie info is not stored in sessions table of tenant i.e. rtbs.sessions. When a login form is posted, it is looking for users in central domain (example.in) where users table is not present, hence the central.users table not exist error. As a result I get 419 error. I had disabled the csrf token verification temporarily to identify this problem.
This is the issue. Why the InitializeTenancyByDomain is not applicable for the login process? Could there be a fundamental setting wrong with me? Interestingly, the dd(\App\User::all()) if present anywhere else i.e. as show below
Route::middleware([
'web',
InitializeTenancyByDomain::class,
PreventAccessFromCentralDomains::class,
])->group(function (){
dd(\App\User::all()); //central-domain.users does not exist error
Route::get('/', function () {
return view('welcomeTenant');
});
Route::get('/home', [
'middleware' => ['auth'],
'uses' => '\App\Http\Controllers\HomeController#index'
])->name('home');
});
The same sql exception i.e. central-domain.users table does not exist is thrown. Only when present inside the Route::get('/'... i can see the correct models.

I had this problem with Laravel Fortify, which provides the backend to JetStream. It only became a problem when I changed the session driver to database from file - though this will be necessary for my deployment anyway.
The issue is that Fortify makes extensive use of dependency injection inside the constructor methods of their controllers. As described in the documentation for Tenancy, because of the lifecycle of a Laravel request the tenancy will not be available when these constructors run. This means they will default to the central connection and cause failure to login.
Because we can't change the way Fortify uses constructors, the solution is to change the middleware to be global and add a check for central domains into the initialization middleware:
Copy Stancl\Tenancy\Middleware\InitializeTenancyByDomain to App\Middleware\InitializeTenancyByDomain changing:
public function handle($request, Closure $next)
{
//Skip for central domains
if(in_array($request->getHost(), config('tenancy.central_domains'), true)){
return $next($request);
}
return $this->initializeTenancy(
$request, $next, $request->getHost()
);
}
App/Http/Kernel
use App\Http\Middleware\InitializeTenancyByDomain;
protected $middleware = [
// ...
InitializeTenancyByDomain::class,
];
Config/fortify
use Stancl\Tenancy\Middleware\PreventAccessFromCentralDomains;
'middleware' => [
'web',
PreventAccessFromCentralDomains::class,
],
Routes/tenant
Route::middleware([
'web',
PreventAccessFromCentralDomains::class,
])->group(function () {
//Your Routes
}
With this solution Fortify and Stancl Tenancy are now working well together with well separated databases, sesssions and logins.

I got it to work with Rory's answer. However, you need to set the correct namespace in App\Http\Middleware\InitializeTenancyByDomain.php:
namespace App\Http\Middleware; // << use this instead of Stancl\Tenancy\Middleware
// [..]
use Stancl\Tenancy\Middleware\IdentificationMiddleware; // <<
class InitializeTenancyByDomain extends IdentificationMiddleware

That package is very helpful, but it requires a lot of work to get all the pieces lined up and maintained. You've got the right middleware, including 'web' and the two tenancy bits, which will start the session and allow checks for CSRF.
I've had similar problems before. The biggest cause for me was that it was not finding the right 'central' path, and thus not initializing tenancy correctly.
Make sure your tenancy.php config file is showing the right central domain. Something like:
'central_domains' => [
'example.in' // No http:// here
],

Route's like next can issue bugs with database session store
Route::view('some')

You could use a feature called Universal Routes check the documentation here

Related

Laravel Route Controller issue

I am trying to add a new route to my application and can't seem to get it to work. I keep getting a 404 error. It looks like the physical path is looking at the wrong directory. Currently looking at D:\Web\FormMapper\blog\public\forms but should be looking at D:\Web\FormMapper\blog\resources\view\layout\pages\forms.blade.php
My request URL:
http://localhost/FormMapper/ /works fine
http://localhost/FormMapper/forms /doesn't work
http://localhost/FormMapper/forms.php /No input file specified.
my FormsController:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class FormsController extends Controller
{
public function index()
{
return view('layouts.pages.forms');
}
}
My web.php:
Route::get('/', function () {
return view('layouts/pages/login');
});
Route::get('/forms', 'FormsController#index');
My folder structure looks like this:
My config/view.php
return [
'paths' => [
resource_path('views'),
],
'compiled' => env(
'VIEW_COMPILED_PATH',
realpath(storage_path('framework/views'))
),
];
you must use dot for this. In your controller change to this:
return view('layouts.pages.forms');
If your route only needs to return a view, you may use the Route::view method. Like the redirect method, this method provides a simple shortcut so that you do not have to define a full route or controller. The view method accepts a URI as its first argument and a view name as its second argument. In addition, you may provide an array of data to pass to the view as an optional third argument:
Route::view('/', 'layouts.pages.login');
Route::view('/forms', 'layouts.pages.forms', ['foo' => 'bar']);
Check docs
After tracking digging deeper I determined that the issue was that IIS requires URL rewrite rules in place for Laravel to work properly. The index.php and '/' route would work b/c it was the default page but any other pages wouldn't. To test this I used the
php artisan serve
approach to it. and everything worked properly. Unfortunately I am unable to do this in production so I needed to get it to work with IIS.

Registering users in Laravel as Admin

My current project is a school management system, and I'm using Laravel 5.6 (it's my first Laravel project). I've been trying to register a user from a small form I have inside the Administrator area of my application.
I've set up the routes for the Admin area (view names are not in English, sorry for that):
// Admin
Route::middleware(['auth'])->group(function () {
Route::view('/admin', 'profiles.admin.admin_gerencia_cadastro');
Route::view('/admin/gerencia/cadastro', 'profiles.admin.admin_gerencia_cadastro');
Route::post('/admin/gerencia/cadastro', 'RegController#store');
Route::view('/admin/gerencia/relatorios', 'profiles.admin.admin_gerencia_relatorios');
Route::view('/admin/ped/', 'profiles.admin.admin_ped');
});
The post route (third from the top) was supposed to handle registration of users and leads to RegController and the following method:
public function store()
{
$this->validate(request(), [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
]);
$user = User::create(request(['name', 'email', 'password']));
return redirect()->to('/admin/gerencia/cadastro');
}
It should store the new user data and redirect the Admin back to /admin/gerencia/cadastro. However, when the form is submitted data is not stored on the database and I'm redirected to the homepage instead. I've tried different things, consulted the documentation, but no success so far.
Any help will be much appreciated.
Edit:
Came here to post the form, as requested below, but I found my own mistake - a rookie one: the form action was wrong. Sorry guys, I'll close the question :)
Is your request reaching in store() method?
try to write
print_r($_REQUEST);exit;
on first line of method. If it's not reaching there, then put below route before all other routes of your route group:
Route::post('/admin/gerencia/cadastro', 'RegController#store');
check out this link for better understanding of routing in laravel.

Route User Role Laravel 5.4

I'm very confused on this situation. I have two routes with on resource name.
Route::resource('product', 'Product\AreaManagerProductController');
Route::resource('product', 'Product\SystemAdminProductController');
I need to make it as one because I have a contextual binding.
$this->app->when(AreaManagerProductController::class)
->needs(ProductInterface::class)
->give(AreaManagerProductRepository::class);
$this->app->when(SystemAdminProductController::class)
->needs(ProductInterface::class)
->give(SystemAdminProductRepository::class);
The contextual binding works fine... but I need to change my route like this.
Route::resource('product_area_manager', 'Product\AreaManagerProductController');
Route::resource('product_system_admin', 'Product\SystemAdminProductController');
I created ProductController and some kind of weird solution.
public function index(){
//Create a conditional statement base on user
return app('App\Http\Controllers\Product\AreaManagerProductController')->index();
}
It may work but it doesn't trigger the middleware... What could be the best practice on this situation. TY
You can have your Route like this
Route::group(['prefix' => 'product', 'namespace' => 'Product', 'middleware' => '<your middleware>'], function() {
Route::resource('area_manager', 'AreaManagerController');
Route::resource('system_admin', 'SystemAdminController');
});
The reason I grouped the route is to reduce redundancy, and the reason i removed Product from the controller name is, as there is a namespace Product already, there is no need of long Class names.
If you wan to access some methods in the AreaManagerController and SystemAdminController just extend the ProductController to these Controllers.
If you want to add some specific middleware for the actions inside these controllers, I have added a middleware clause in the route group which will affect to these controllers, if not needed just remove it.
Hope this helps you.

Multi-language site in Laravel 5.4

For SEO purposes I'd like to include the locale of my Laravel project pages in the url, such as http://localhost/en or http://localhost/nl/about.
In trying to achieve this I have found a few tutorials and packages, but I'm not entirely happy with them. The most popular suggestion is mcamara's laravel-localization package, but creating URLs requires a very long function call. Other packages are either for older Laravel versions or offer a lot of functionality I don't need, which would only clutter my code and database.
Marwelln's tutorial seems perfect: it modifies URLs the way I want, works with the default URL helpers like action(), url() and route() and doesn't create unnecessary database tables. However, this tutorial seem to be for Laravel 5.0 and not compatible with Laravel 5.4. Could anyone help me to achieve such URL modification in the newest Laravel version in the most basic way?
Try the following:
In your RouteServiceProvider.php add these lines at the beginnig of the map() method:
$locale = $request->segment(1);
if($locale) {
$this->app->setLocale($locale);
$this->prefix = $locale;
}
And the mapWebRoutes() method should look like this:
protected function mapWebRoutes()
{
$parameters = [
'middleware' => 'web',
'namespace' => $this->namespace,
];
if ($this->prefix) {
$parameters['prefix'] = $this->prefix;
}
Route::group($parameters, function ($router) {
require base_path('routes/web.php');
});
}
Then follow the rest of the instructions in the link of the example you provide. I guess that should be all.
NOTE: the line $this->app->setLocale($locale) has not much sense since you are doing that on the middleware.

Pointing a route to different controllers based on role

Let’s say we’re building an app which has two kinds of user - OfficeStaff and Engineer. We want them both to hit /dashboard as their main landing page after login, but they should both see completely different things.
We use the users table for both of them since they both log in the same way, and create a role field to determine who’s the OfficeStaff and who’s the Engineer.
When we create the routes, my first thought was to do this:
Route::group( [ ‘middleware’ => ‘role:office-staff’ ] , function () {
Route::get( ‘dashboard’, function() { return ‘Office staff dash’; } );
} );
Route::group( [ ‘middleware’ => ‘role:engineer’ ] , function () {
Route::get( ‘dashboard’, function() { return ‘Engineer dash’; } );
} );
However the problem is that Laravel doesn’t take middleware into account when compiling routes - it just looks at the path, so the second route overwrites the first one and we don’t have a dashboard route for the office staff.
Since the routes file is loaded before the middleware is run, the Auth middleware doesn’t load in the current user so we can’t dynamically namespace the route like this:
Route::get( ‘dashboard’, [ ‘as’ => ‘dashboard’, ‘uses’ => Auth::user()->role . ‘\DashboardController#dashboard’ ] );
The only other way that occurs to me is detecting it in the controller then creating a new controller instance of the appropriate sub-controller…
public function dashboard( Request $request ) {
App::make( ‘App\Controllers\’ . $request->user()->role . ‘DashboardController’ )->dashboard();
}
But that feels like we’re entering a world of hurt where we lose the ability to type hint inside controller methods, and we’re going to have a LOT of generic controller actions like this.
After all of this, I can't think of a good, clean way to set this up. Any ideas? :)

Resources