On one of the systems that I take care of, some times some jobs don't get dispatched due to a connection problem with Redis and this ends up returning an error to the user, on our side we can ignore this error and just miss this job, I looked for how to deal with it on Google and I didn't find anything about it.
public function sendMessage(Request $request, Model $model)
{
// Do the necessary stuff
ResolveMessageBilling::dispatch($model, $request->all());
return response()->json([
'message' => 'The message was succesfully sent'
], 200);
}
This is the error we are getting: RedisException - socket error on read socket
How to ignore the error if it occurs? A simple try/catch can resolve the issue?
public function sendMessage(Request $request, Model $model)
{
// Do the necessary stuff
try {
ResolveMessageBilling::dispatch($model, $request->all());
} catch(\Exception $e) {}
return response()->json([
'message' => 'The message was succesfully sent'
], 200);
}
If you want to bypass ANY error, you should use \Throwable instead of \Exception
public function sendMessage(Request $request, Model $model)
{
// Do the necessary stuff
try {
ResolveMessageBilling::dispatch($model, $request->all());
} catch(\Throwable $e) {}
return response()->json([
'message' => 'The message was succesfully sent'
], 200);
}
see Error Hierarchy: https://www.php.net/manual/en/language.errors.php7.php
If you want to bypass only the \RedisException, you should be able to use:
public function sendMessage(Request $request, Model $model)
{
// Do the necessary stuff
try {
ResolveMessageBilling::dispatch($model, $request->all());
} catch(\RedisException $e) {}
return response()->json([
'message' => 'The message was succesfully sent'
], 200);
}
If you don't want to setup Redis just want to fixed/remove errors only, follow this article: https://laravel.com/docs/7.x/errors
IF you want to Setup Redis(config -> detabase.php) properly, follow few step like this:
'redis' => [
'client' => 'predis',
// Keep Default as is you want to use both redis and sentinel for different service(cache, queue)'
'default' => [
'host' => env('REDIS_HOST', '127.0.0.1'),
'password' => env('REDIS_PASSWORD', null),
'port' => env('REDIS_PORT', 6379),
'database' => 0,
],
// Create a custom connection to use redis sentinel
'cache_sentinel' => [
// Set the Sentinel Host from Environment (optinal you can hardcode if want to use in prod only)
env('CACHE_REDIS_SENTINEL_1'),
env('CACHE_REDIS_SENTINEL_2'),
env('CACHE_REDIS_SENTINEL_3'),
'options' => [
'replication' => 'sentinel',
'service' => 'cachemaster'),
'parameters' => [
'password' => env('REDIS_PASSWORD', null),
'database' => 0,
],
],
],
],
if you needs to Redis sentinal cache, can create new cache connection to use the above sentinal connection like this:
'stores' = [
//Default config
'redis' => [
'driver' => 'redis',
'connection' => 'default',
],
// Custom cache connection(according to you)
'sentinel_redis' => [
'driver' => 'redis',
'connection' => 'cache_sentinel',
],
In laravel app, you can easily use via cache facade:
Cache::store('sentinel_redis')->get('key');
After config Redis properly test again with the clear server cache
Related
I am using Laravel + Breeze. My App will have two access sites:
Admins can login related to the users table (working)
Clients can login related to the clients table (to be discussed)
I don't know, if this is a good idea, but I want to share the already implemented Auth Service with another Model: clients.
What I already implemented is:
Logged in user can create a client
Client receives a email verification email from an event
Client can confirm email address and will be redirect to the login view
Now it becomes tricky for me. The AuthenticatedSessionController::store() method is checking for the $request->authenticate() and this runs agains the users table and fails for the clients table.
I slightly modified the method to redirect based on the type of login candidate:
public function store(LoginRequest $request)
{
$request->authenticate();
$request->session()->regenerate();
$user = User::where('id', Auth::id())->first();
if ( $user ) {
return redirect()->intended(RouteServiceProvider::HOME);
}
$client = Client::where('id', Auth::id())->findOrFail();
if ( $client ) {
return redirect()->to('portal.show', $client->id);
}
}
but this works for users only as $request->authenticate() will not let clients in.
Now I need support and from here on I'm just guessing that the next step is the update I did in config/auth.php:
return [
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'clients' => [
'driver' => 'eloquent',
'model' => App\Models\Client::class,
],
],
'passwords' => [
'users' => [
'provider' => 'users',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
'clients' => [
'provider' => 'clients',
'table' => 'password_resets',
'expire' => 60,
'throttle' => 60,
],
],
'password_timeout' => 10800,
];
You will need to look into & modify authenticate method of App\Http\Requests\Auth\LoginRequest class.
One way of doing it could be to check for presence of email received via request against users and clients tables.
Then if the email belongs to a client - implement custom logic to validate credentials against clients table records and login the client
And if the email belongs to user - then let the request pass through as normal
Eg:
public function authenticate()
{
$type = DB::table('clients')->where('email', $this->email)->exists() ? 'client' : 'user';
if($type === 'user') {
return $this->authenticateUser();
}
//Validate credentials against `clients` table
// If valid retrieve the client and login the client
//Else throw ValidationException
}
//Copy the content of original authenticate to this method
protected function authenticateUser()
{
$this->ensureIsNotRateLimited();
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => trans('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
}
NOTE With this approach you are not doing any rate-limiting/throttling nor firing attempt events
For complete robust authentication system for clients, I guess it will be required to define a separate guard, user provider and then re-implement SessionGuard functionality all over again.
Another wild thought which comes to mind is, why not have Client be represented as a record on users table just for authentication purpose and may be have a one-to-one relation between them.
Hello Laravel Developers,
Today i'm facing some seriously but not at all issue related to testing in Laravel Framework.
See the following code example:
<?php
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\TokenRequest;
use App\Repositories\ExceptionRepository;
use GuzzleHttp\Client;
use Illuminate\Http\JsonResponse;
class TokenController extends Controller {
public function __invoke(TokenRequest $request) : JsonResponse
{
$http = new Client;
try {
$response = $http->post(env('OAUTH_DOMAIN', 'https://oauth.application.com') . '/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => env('PASSPORT_PASSWORD_CLIENT_ID'),
'client_secret' => env('PASSPORT_PASSWORD_CLIENT_SECRET'),
'username' => $request->input('email'),
'password' => $request->input('password'),
'scope' => '*',
],
]);
$token = json_decode((string) $response->getBody(), true);
return response()->json([
'message' => 'Token retrieved successfully.',
'token' => $token,
]);
} catch (Throwable $throwable) {
ExceptionRepository::emergency('Something went wrong.', $throwable);
return response()->json([
'message' => 'Token can\' be retrieved.',
'error' => 'Internal server error'
], 500);
}
}
}
This code seems fine but when you're testing you always get internal server error. The TestCase is the following:
<?php
use App\Models\User;
public function test_token()
{
$user = User::factory()->create()->first();
$response = $this->json('POST', '/api/token', [
'email' => $user->email,
'password' => 'password',
]);
$response->assertStatus(500);
}
My ideas about why this happens is because the enviroment variable OAUTH_DOMAIN is different of the right application URL when the code is running via PHPUnit.
In other hand when i try this controller i just need start two instances of artisan serve because when the framework do a internal request it keep frozen and stuck for infinite time. The OAUTH_DOMAIN variable is always the second port of the artisan instance.
Someone know how to face this issue?
Imagine you have this inside .env file:
DB_CONNECTION=global
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=global
DB_USERNAME=dbuser
DB_PASSWORD=password
And you have one more connection inside config/database.php called tenant.
Now when I try to log in I switch the connection to tenant and I use the username and password from that database, I can log in, log out and all works fine.
But now I want to be able to log in with a user that doesn't exist in this tenant DB.
I started to overwrite the login() function like this:
Tip: remember that at this point the connection is set to tenant!
public function login(Request $request)
{
$parts = explode(':', $request['username']);
if ($parts[0] === 'global') {
\DB::purge(\DB::getDatabaseName());
\DB::reconnect('global');
\Schema::connection('global')->getConnection()->reconnect();
}
::::::::::::::::::
But this doesn't work... It doesn't switch the connection
I want to log in with a user from global DB and continue to work with tenant DB...
Is this even possible?
Update:
I already solved the part of how to switch the DB connection...
The problem is only authentication to a different one!
To avoid having to play with the default database connection, you should create two different model per database authenticatable user with each one its own $connection declared on the model
class UserGlobal extends User
{
protected $connection = 'global';
}
class UserTennant extends User
{
protected $connection = 'tennant';
}
You will need to do multi-authentification middlewares or use laravel's auth using this guide laravel simple multi-auth to have an idea.
// config/auth.php
<?php
[...]
'guards' => [
[...]
'global' => [
'driver' => 'session',
'provider' => 'globals',
],
'tennant' => [
'driver' => 'session',
'provider' => 'tennants',
],
],
[...]
'providers' => [
[...]
'globals' => [
'driver' => 'eloquent',
'model' => App\UserGlobal::class,
],
'tennants' => [
'driver' => 'eloquent',
'model' => App\UserTennant::class,
],
],
You need also to declare both database connection in config/database.php
'connections' => [
'global' => [
'driver' => 'mysql',
'host' => env('DB_HOST_GLOBAL', '127.0.0.1'),
...
],
'tennant' => [
'driver' => 'mysql',
'host' => env('DB_HOST_TENNANT', '127.0.0.1'),
...
],
]
What login would look like
public function login(Request $request)
{
$tennant= $this->loginGuard($request->get('email'), $request->get('password'), auth('tennant'));
$global= $this->loginGuard($request->get('email'), $request->get('password'), auth('global'));
if (!$tennant && !$global) {
return 'wrong credential';
}
return 'welcome';
}
private function loginGuard($email, $password, $guard)
{
$token = $guard->attempt(['email' => $email, 'password' => $password]);
if (!$token || !$guard->user()->isLoggingIn()) {
return null;
}
return $guard->user();
}
I want to create JWT using client model. All the login credentials are saved in the clients table. Here in my Laravel 5.4 application I dont want to have users model. My piece of code is being showing. Now when I am trying to login laravel querying from users table which I don't. I want it from clients table. All the required namespaces I have added top in my controller file. Need help to get a solution.
\Config::set('jwt.user', 'App\Client');
\Config::set('auth.providers.users.model', \App\Client::class);
$credentials = ["username"=>$user_name,"password"=>$password];
$token = null;
try {
if (!$token = JWTAuth::attempt($credentials)) {
return response()->json([
'response' => 'error',
'message' => 'invalid_email_or_password',
]);
}
} catch (JWTAuthException $e) {
return response()->json([
'response' => 'error',
'message' => 'failed_to_create_token',
]);
}
return response()->json([
'response' => 'success',
'result' => [
'token' => $token,
'message' => 'I am front user',
],
]);
I think you need to change the providers => users => model => to your custom namespace in config/auth.php
Example
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class, <= change this to your custom namespace
],
],
I have two laravel instances connected to an API. Default "password reset" functionality was created for the #1 website but now, I want to add new email templates for the users which are trying to reset their password from the second website.
public function emailResetLink(CanResetPasswordContract $user, $token, Closure $callback = null)
{
$view = $this->emailView;
return $this->mailer->send($view, compact('token', 'user'), function ($m) use ($user, $token, $callback) {
$m->to($user->getEmailForPasswordReset());
if (! is_null($callback)) {
call_user_func($callback, $m, $user, $token);
}
});
}
$view logs auth.emails.password from auth.php (email template for #1 website)
'passwords' => [
'users' => [
'provider' => 'users',
'email' => 'auth.emails.password',
'table' => 'password_resets',
'expire' => 60,
],
],
I've added a hidden input to differentiate the users but I don't know how to use that in order to send another email template.. Any idea would be much appreciated!