I'm working in a multi-tenant system, I previously tried the https://github.com/hyn/multi- tenant package but in the end I decided to do this implementation with my own method. After a lot of research and testing different things I have managed to build this code, but when I execute it tells me that the connect method is not defined, here I leave what I have done, most likely this is due to some nonsense that I has overlooked, but if someone can help me solve this I would greatly appreciate it
helpers:
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
if(! function_exists('conexionBD')){
/**
* Establish a tenant database connection.
*
* #param $hostname
* #param $username
* #param $password
* #param $database
*/
function conexionBD($hostname, $username, $password, $database){
DB::purge('empresa');
Config::set('database.connections.empresa.host', $hostname);
Config::set('database.connections.empresa.database', $database);
Config::set('database.connections.empresa.username', $username);
Config::set('database.connections.empresa.password', $password);
DB::reconnect('empresa');
Schema::connection('empresa')->getConnection()->reconnect();
}
}
Empresa Model:
class empresa extends Model
{
protected $fillable = [
'hostname',
'username',
'password',
'database'
];
public function ciudad() {
return $this->belongsTo('confia\ciudad');
}
public function empresaUsuario() {
return $this->hasMany('confia\empresasUsuario');
}
public function connect()
{
if (! $this->connected()) {
conexionBD(
$this->hostname,
$this->username,
$this->password,
$this->database
);
}
}
/**
* Check if the current tenant connection settings matches the company's database settings.
*
* #return bool
*/
private function connected()
{
$connection = Config::get('database.connections.empresa');
return $connection['username'] == $this->username &&
$connection['password'] == $this->password &&
$connection['database'] == $this->database;
}
}
Middleware Tenant:
use Closure;
use confia\empresa;
class Tenant{
protected $company;
/**
* Tenant Conctructor
* #param empresa $empresa
*/
public function __construct(empresa $empresa)
{
$this->empresa = $empresa;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next) {
if (($request->session()->get('empresaId')) === null)
return redirect()->route('inicio')->withErrors(['error' => __('Por favor inicie sesión en alguna empresa antes de intentar esta acción')]);
// Get the company object with the id stored in session
$empresa= $this->empresa->find($request->session()->get('empresaId'));
// Connect and place the $company object in the view
$this->connect($empresa);
return $next($request);
}
}
In my session variable empresaId I have stored the ID of my company concat to a static string, this is what gives me the name of the database associated with that company.
When I run the system, at the time of making the change of tenant to start working with another database, I get the following error,
"Call to undefined method app\Http\Middleware\Tenant::connect()"
Someone knows that my code is missing or that I am doing wrong to make the multi-tenant system work correctly?
Related
I am trying to add one more field to forgot password which is STAFF ID & EMAIL. If STAFF ID and EMAIL is correct then the system should send reset password link.
It seems laravel default only allow email for forgot password. Is there anyways to add STAFF ID and verify both field before send email?
vendor/laravel/framework/src/Illuminate/Foundation/Auth/SendsPasswordResetEmails.php
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
trait SendsPasswordResetEmails
{
/**
* Display the form to request a password reset link.
*
* #return \Illuminate\Http\Response
*/
public function showLinkRequestForm()
{
return view('auth.passwords.email');
}
/**
* Send a reset link to the given user.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
public function sendResetLinkEmail(Request $request)
{
$this->validateEmail($request);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->broker()->sendResetLink(
$this->credentials($request)
);
return $response == Password::RESET_LINK_SENT
? $this->sendResetLinkResponse($request, $response)
: $this->sendResetLinkFailedResponse($request, $response);
}
/**
* Validate the email for the given request.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
protected function validateEmail(Request $request)
{
$request->validate(['email' => 'required|email']);
}
/**
* Get the needed authentication credentials from the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function credentials(Request $request)
{
return $request->only('email');
}
/**
* Get the response for a successful password reset link.
*
* #param \Illuminate\Http\Request $request
* #param string $response
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetLinkResponse(Request $request, $response)
{
return back()->with('status', trans($response));
}
/**
* Get the response for a failed password reset link.
*
* #param \Illuminate\Http\Request $request
* #param string $response
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetLinkFailedResponse(Request $request, $response)
{
return back()
->withInput($request->only('email'))
->withErrors(['email' => trans($response)]);
}
/**
* Get the broker to be used during password reset.
*
* #return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker()
{
return Password::broker();
}
}
The proper way to do this is to override the PasswordBroker and DatabaseTokenRepository which is actually a lot of work for something that could have been achieved with a little modification to the canResetPasswordContract. The current implementation assumes resetting a password is all about the user and undermines the importance of getting the request information such as the ip address; and there's also the issue of efficient table indexing.
Nevertheless, I came up with a possible replacement of the shipped ForgotPasswordController that should be sufficient for most use cases to change the payload associated with reset password if you would like to use a different table structure without overriding everything.
Keep in mind that
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Support\Facades\Password;
use Illuminate\Support\Str;
use Illuminate\Contracts\Auth\PasswordBroker;
use Illuminate\Http\Request;
use Carbon\Carbon;
use App\Models\PasswordReset;
use App\Models\User;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
//in minutes
protected $throttle = 60;
/**
* Send a reset link to the given user.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
public function sendResetLinkEmail(Request $request)
{
$this->validateEmail($request);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$user = User::where($this->credentials($request))->first();
if (is_null($user)) {
return $this->sendResetLinkFailedResponse($request, PasswordBroker::INVALID_USER);
}
$reset = PasswordReset::where(
'email', $user->getEmailForPasswordReset()
)->first();
if ($reset && $this->tokenRecentlyCreated($reset)) {
return $this->sendResetLinkFailedResponse($request, PasswordBroker::RESET_THROTTLED);
}
$token = $this->createToken($request, $user, $reset);
//keep in mind that saved token is hashed version of this
$user->sendPasswordResetNotification($token);
return $this->sendResetLinkResponse($request, Password::RESET_LINK_SENT);
}
/**
* Create a ne password reset token
*
* #param \Illuminate\Http\Request $request
* #param Model $user
* #param Model $reset
*/
public function createToken($request, $user, $reset)
{
$email = $user->getEmailForPasswordReset();
if ($reset) {
$reset->delete();
}
// We will create a new, random token for the user so that we can e-mail them
// a safe link to the password reset form. Then we will insert a record in
// the database so that we can verify the token within the actual reset.
$token = $this->createNewToken();
PasswordReset::create([
'user_id' => $user->id,
'email' => $email,
'token' => bcrypt($token),
'created_at' => now(),
'ip_address' => $request->ip()
]);
return $token;
}
/**
* Create a new token for the user.
*
* #return string
*/
public function createNewToken()
{
return hash_hmac('sha256', Str::random(40), $this->getHashKey());
}
/**
* Replicate hash key used by DatabaseTokenRepository
*/
public function getHashKey()
{
$key = config('app.key');
if (Str::startsWith($key, 'base64:')) {
$key = base64_decode(substr($key, 7));
}
return $key;
}
/**
* Determine if the token was recently created.
*
* #param Model $token
* #return bool
*/
protected function tokenRecentlyCreated($token)
{
if ($this->throttle <= 0) {
return false;
}
return Carbon::parse($token->created_at)->addSeconds(
$this->throttle
)->isFuture();
}
}
Finally manage to add staff ID in credentials :)
<?php
namespace Illuminate\Foundation\Auth;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
trait SendsPasswordResetEmails
{
/**
* Display the form to request a password reset link.
*
* #return \Illuminate\Http\Response
*/
public function showLinkRequestForm()
{
return view('auth.passwords.email');
}
/**
* Send a reset link to the given user.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
public function sendResetLinkEmail(Request $request)
{
$this->validateEmail($request);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->broker()->sendResetLink(
$this->credentials($request)
);
return $response == Password::RESET_LINK_SENT
? $this->sendResetLinkResponse($request, $response)
: $this->sendResetLinkFailedResponse($request, $response);
}
/**
* Validate the email for the given request.
*
* #param \Illuminate\Http\Request $request
* #return void
*/
protected function validateEmail(Request $request)
{
$request->validate(['email' => 'required|email'],['StaffID' => 'required']);
}
/**
* Get the needed authentication credentials from the request.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
protected function credentials(Request $request)
{
return $request->only('email', 'StaffID');
}
/**
* Get the response for a successful password reset link.
*
* #param \Illuminate\Http\Request $request
* #param string $response
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetLinkResponse(Request $request, $response)
{
return back()->with('status', trans($response));
}
/**
* Get the response for a failed password reset link.
*
* #param \Illuminate\Http\Request $request
* #param string $response
* #return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetLinkFailedResponse(Request $request, $response)
{
return back()
->withInput($request->only('email','StaffID'))
->withErrors(['email' => 'We cant find a user with that Staff ID and Email']);
}
/**
* Get the broker to be used during password reset.
*
* #return \Illuminate\Contracts\Auth\PasswordBroker
*/
public function broker()
{
return Password::broker();
}
}
Thanks :)
I'm experimenting with laravel and I came across this article.
I followed it exactly but for some reason when sending the mail in the Notification class, he can't find the $user variable I declared in the constructor. When printing it in the constructor it works so the user object is passed correctly, but when I want to access it in the toMail method, it's inexisting for some reason. Anyone know why & how to fix this?
<?php
namespace App\Notifications;
use App\User;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
class UserRegisteredSuccessfully extends Notification
{
use Queueable;
/**
* #var User
*/
protected $user;
/**
* Create a new notification instance.
*
* #param User $user
*/
public function __construct(User $user)
{
$this->$user = $user;
// printing here works
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return ['mail'];
}
/**
* Get the mail representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
// ERROR HERE (Undefined variable: user)
$user = $this->$user;
return (new MailMessage)
->subject('Succesfully created new account')
->greeting(sprintf('Hello %s', $user->username))
->line('You have successfully registered to our system. Please activate your account.')
->action('Click here', route('activate.user', $user->activation_code))
->line('Thank you for using our application!');
}
/**
* Get the array representation of the notification.
*
* #param mixed $notifiable
* #return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}
Register Method:
/**
* Register new user
*
* #param Request $request
* #return User
*/
protected function register(Request $request)
{
$validatedData = $request->validate([
'username' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:6|confirmed',
]);
try {
$validatedData['password'] = Hash::make(array_get($validatedData, 'password'));
$validatedData['activation_code'] = str_random(30).time();
$user = app(User::class)->create($validatedData);
} catch(\Exception $ex) {
logger()->error($ex);
return redirect()->back()->with('message', 'Unable to create new user.');
}
$user->notify(new UserRegisteredSuccessfully($user));
return redirect()->back()->with('message', 'Successfully created a new account. Please check your email and activate your account.');
}
Thanks in Advance!
You made 2 typos:
In your constructor:
$this->$user = $user;
Should be:
$this->user = $user;
And in the toMail() method:
$user = $this->$user;
Should be:
$user = $this->user;
The reason it is not working, is because you are currently using the value of $user as the variable name, and you are not assigning the value of the User object to $this->user.
I want to disable the laravel password bcrypt when I try to log-in like this
Auth::guard('client')->attempt(
'id' => $request['id'],
'password' => $request['password'])
But it seems to be more dificult than I thought, I know I should not do this but I temporally need to work like this, but laravel forces me to use encrypted passwords. I need to be able to use plain passwords on my database.
I been searching on internet but I cant find a solution.
Try extending SessionGuard and overriding function hasValidCredentials()
Create A file by name 'SessionGuardExtended' in App\CoreExtensions
use Illuminate\Auth\SessionGuard;
use Illuminate\Contracts\Auth\Authenticatable;
class SessionGuardExtended extends SessionGuard
{
/**
* Determine if the user matches the credentials.
*
* #param mixed $user
* #param array $credentials
* #return bool
*/
protected function hasValidCredentials($user, $credentials)
{
return ! is_null($user) && $credentials['password'] == $user->getAuthPassword();
}
}
Edit config/auth.php edit the driver and use sessionExtended
'web' => [
'driver' => 'sessionExtended',
'provider' => 'users',
],
In AppServiceProvider Write Code in boot function
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Auth::extend(
'sessionExtended',
function ($app) {
$provider = new EloquentUserProvider($app['hash'], config('auth.providers.users.model'));
return new SessionGuardExtended('sessionExtended', $provider, app()->make('session.store'), request());
}
);
}
Reference: Extending Laravel 5.2 SessionGuard
You can extend Illuminate\Auth\EloquentUserProvider, ie:
<?php
namespace App\Services\Auth;
use Illuminate\Auth\EloquentUserProvider as BaseUserProvider;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
class UserProvider extends BaseUserProvider {
/**
* Create a new database user provider.
*
* #param string $model
*
* #return void
*/
public function __construct($model)
{
$this->model = $model;
}
/**
* Validate a user against the given credentials.
*
* #param \Illuminate\Contracts\Auth\Authenticatable $user
* #param array $credentials
*
* #return bool
*/
public function validateCredentials(UserContract $user, array $credentials)
{
$plain = $credentials['password'];
// $matches = some method of matching $plain with $user->getAuthPassword();
return $matches;
}
}
Then register this in the IoC in a service provider like so:
<?php // ...
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
// ...
$this->app['auth']->extend(
'legacy',
function () {
return new \Illuminate\Auth\Guard(
new \App\Services\Auth\UserProvider(
$this->app['config']['auth.model']
),
$this->app['session.store']
);
}
);
// ...
}
Then set your current driver to legacy in config/auth.php.
PS: You may want to include the classes in the provider,
You can use
Auth::guard('client')->login($user);
In this $user is an instance of Model which is implemented Illuminate\Contracts\Auth\Authenticatable contract.
In user model it is already implemented.
Maybe it will helpful
I know I am not the only person who has come up to this point. Does anyone know how to properly implement a custom grant in Laravel(5.3) Passport?
Or
Have a good link/tutorial to reference how to properly do it?
I know there's this package:
https://github.com/mikemclin/passport-custom-request-grant
But I'm asking for a more "Do it yourself" approach.
Thank you in advance.
namespace App\Providers;
use App\Auth\Grants\FacebookGrant;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Laravel\Passport\Bridge\RefreshTokenRepository;
use Laravel\Passport\Passport;
use League\OAuth2\Server\AuthorizationServer;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* #var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* #return void
*/
public function boot()
{
$this->registerPolicies();
app(AuthorizationServer::class)->enableGrantType(
$this->makeFacebookGrant(), Passport::tokensExpireIn()
);
Passport::routes();
//
}
/**
* Create and configure a Facebook grant instance.
*
* #return FacebookGrant
*/
protected function makeFacebookGrant()
{
$grant = new FacebookGrant(
$this->app->make(RefreshTokenRepository::class)
);
$grant->setRefreshTokenTTL(Passport::refreshTokensExpireIn());
return $grant;
}
}
EDIT:
sorry for only posting this code, i do not know how much this code is going to be useful to you.
Well, here i'll leave my implementation of FacebookGrant, hope this helps someone.
<?php
namespace App\Auth\Grants;
use Illuminate\Http\Request;
use Laravel\Passport\Bridge\User;
use League\OAuth2\Server\Entities\ClientEntityInterface;
use League\OAuth2\Server\Entities\UserEntityInterface;
use League\OAuth2\Server\Exception\OAuthServerException;
use League\OAuth2\Server\Grant\AbstractGrant;
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface;
use League\OAuth2\Server\RequestEvent;
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface;
use Psr\Http\Message\ServerRequestInterface;
use RuntimeException;
class FacebookGrant extends AbstractGrant
{
/**
* #param RefreshTokenRepositoryInterface $refreshTokenRepository
*/
public function __construct(
RefreshTokenRepositoryInterface $refreshTokenRepository
) {
$this->setRefreshTokenRepository($refreshTokenRepository);
$this->refreshTokenTTL = new \DateInterval('P1M');
}
/**
* {#inheritdoc}
*/
public function respondToAccessTokenRequest(
ServerRequestInterface $request,
ResponseTypeInterface $responseType,
\DateInterval $accessTokenTTL
) {
// Validate request
$client = $this->validateClient($request);
$scopes = $this->validateScopes($this->getRequestParameter('scope', $request));
$user = $this->validateUser($request, $client);
// Finalize the requested scopes
$scopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $user->getIdentifier());
// Issue and persist new tokens
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $user->getIdentifier(), $scopes);
$refreshToken = $this->issueRefreshToken($accessToken);
// Inject tokens into response
$responseType->setAccessToken($accessToken);
$responseType->setRefreshToken($refreshToken);
return $responseType;
}
/**
* #param ServerRequestInterface $request
*
* #return UserEntityInterface
* #throws OAuthServerException
*/
protected function validateUser(ServerRequestInterface $request, ClientEntityInterface $client)
{
$facebookId = $this->getRequestParameter('facebook_id', $request);
if (is_null($facebookId)) {
throw OAuthServerException::invalidRequest('facebook_id');
}
$email = $this->getRequestParameter('email', $request);
if (is_null($email)) {
throw OAuthServerException::invalidRequest('email');
}
$user = $this->getUserEntityByUserFacebookId(
$facebookId,
$email,
$this->getIdentifier(),
$client
);
if ($user instanceof UserEntityInterface === false) {
$this->getEmitter()->emit(new RequestEvent(RequestEvent::USER_AUTHENTICATION_FAILED, $request));
throw OAuthServerException::invalidCredentials();
}
return $user;
}
/**
* Retrieve a user by the given Facebook Id.
*
* #param string $facebookId
* #param string $email
* #param string $grantType
* #param \League\OAuth2\Server\Entities\ClientEntityInterface $clientEntity
*
* #return \Laravel\Passport\Bridge\User|null
* #throws \League\OAuth2\Server\Exception\OAuthServerException
*/
private function getUserEntityByUserFacebookId($facebookId, $email, $grantType, ClientEntityInterface $clientEntity)
{
$provider = config('auth.guards.api.provider');
if (is_null($model = config('auth.providers.'.$provider.'.model'))) {
throw new RuntimeException('Unable to determine authentication model from configuration.');
}
$user = (new $model)->where('facebook_id', $facebookId)->first();
if (is_null($user)) {
$user = (new $model)->where('email', $email)->first();
if (is_null($user)) {
return;
}
// Now that we retrieved the user with the email, we need to update it with
// the given facebook id. So the user account will be linked correctly.
$user->facebook_id = $facebookId;
$user->save();
}
return new User($user->getAuthIdentifier());
}
/**
* {#inheritdoc}
*/
public function getIdentifier()
{
return 'facebook';
}
}
I am not sure what do you mean by custom grant but you can use passport service for password grant which can be customized however you like.
Reference: https://laravel.com/docs/5.3/passport#password-grant-tokens
To give more insight, you get a client_id and client_secret which all users of your api will use to get access_token and refresh_token using their password and email as well and then you can customize the process by adding features via middleware.
For example you can check some customized header if present or change database based on request to perform further query and so on.
Maybe I got whole wrong idea about your ques, if that's the case then please specify more about your needs.
I'm having trouble with logging users in, everything appears to be in the right place, I get no errors in the log, but users fail to log in, I am using the correct credentials that are in my database.
Please note I have a different set up to the normal one:
My table is called test_users
My model sits in a separate namespace called Test
Here's my code:
In config>auth I have set:
'model' => '\Test\User',
'table' => 'test_users',
Here is how I call the Auth:
public function logIn()
{
$input = Input::all();
$credentials = array('email' => $input['email'], 'password' => $input['password']);
$input['remember-me'] = isset($input['remember-me']) ? true : false;
if(Auth::attempt($credentials, $input['remember-me']))
{
$this->output['message'] = 'ok';
}
else
{
$this->output['message'] = 'fail';
}
return $this->output;
}
Here's my model:
<?php namespace Test;
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\Reminders\RemindableInterface;
use Eloquent;
class User extends Eloquent implements UserInterface, RemindableInterface {
/**
* The database table used by the model.
*
* #var string
*/
protected $table = 'test_users';
/**
* The attributes excluded from the model's JSON form.
*
* #var array
*/
protected $hidden = array('password');
/**
* Get the unique identifier for the user.
*
* #return mixed
*/
public function getAuthIdentifier()
{
return $this->getKey();
}
/**
* Get the password for the user.
*
* #return string
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* Get the e-mail address where password reminders are sent.
*
* #return string
*/
public function getReminderEmail()
{
return $this->email;
}
}
Auth::attempt checks for a hashed password. It appears you might be trying to set them with plaintext. Try setting your passwords with Hash::make('password') if you aren't already.