PHP Laravel Auth password from users table, id from another table - php

I've been looking around the internet for a while now and I'm not able to find an answer to my question which is why I am asking it here.
I want to authenticate users to login but this must be done with information from two separate tables.
I am sending to values to my controller: badge and password
I am accessing them with
$request->badge (note: This is the account id)
$request->password (note: this is the users password)
Previously I have tried the following:
public function store(Request $request)
{
$request->validate([
'badge' => 'required|int',
'password' => 'required|string',
]);
$account = Account::select('id', 'user_id')->where('id', $request->badge)->first();
if(!$account)
return back()->withInput()->withErrors(['password' => 'Incorrect badge or password!']);
else
{
if(Auth::attempt(['username' => $accounts->user->username, 'password' => $request->password]))
{
return redirect()->route('home');
}
else
{
return back()->withInput()->withErrors(['password' => 'Incorrect badge or password!']);
}
}
}
This will log the user in, however when I use Auth::id() it returns the ID of the user and not of the account.
example: $request->badge is filled with 25 (which is the account id), the user id is 1. Auth::id returns 1 instead of my desired 25.
My table looks like the following:
users
----
id
username
email
password
accounts
-----
id
user_id
name
I have a relationship in accounts and users to link them together
public function accounts()
{
return $this->hasMany(Accounts::class, 'user_id', 'id');
}
public function user()
{
return $this->belongsTo(User::class, 'id', 'user_id');
}
I want auth::id to give me 25 instead of 1.

Since you already have the ID if the correct account it's only a hasOne relationship between the account and the related user.
In your auth config, you need to change the model of the users provider to your actual account model:
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\Account::class,
],
],
In the account model we're then adding a global scope which always fetches the password from the related user entry so auth won't have a problem with it:
class Account extends Model
{
protected $table = 'accounts';
protected static function boot()
{
parent::boot();
static::addGlobalScope('withPassword', function ($builder) {
$builder
->join('users', 'accounts.user_id', 'users.id')
->select(DB::raw('accounts.*, users.password'));
});
}
}
The scope makes sure that the password column is always present if you query an account. You can change it up so that the account always includes certain other columns as well, but for now, this should work as expected.
In chat, we've been discussing the topic of the advantages of manual authentication with either login or loginById in this scenario. A possible solution would be this:
$account = Account::select(
'accounts.id',
DB::raw('(SELECT `password` FROM users WHERE accounts.user_id = users.id) AS `password`'))
)
->where('accounts.id', $request->badge)
->first();
if ($account) {
if (Hash::check($request->password, $account->password)) {
Auth::loginUsingId($account->id);
return redirect()->route('home');
} else {
return back()->withInput()->withErrors(['password' => 'Incorrect badge or password!']);
}
} else {
return back()->withInput()->withErrors(['password' => 'Incorrect badge or password!']);
}

I might be on the totally wrong track here, but what you could do is to apply a global scope to the Users model which automatically joins the accounts table every time you query for a user.
This join automatically replaces the user_id with the account ID given how joins work but you may have to fiddle around with a raw select to get the values how you want them.
class User extends Model
{
/**
* The "booting" method of the model.
*
* #return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope('account', function (Builder $builder) {
$builder->join('accounts', 'users.id', ''accounts.user_id');
});
}
}
To remove the scope from any query you can just use User::withoutGlobalScope('account').
Read more about usage of global scopes here.

Related

Laravel 8: Checking user's data with database table does not work properly

I have created a two factor authentication system, and it redirects user to token.blade.php where he must enters the token that is going to be sent to his phone number and also stored at active_codes table.
Now I want to check if the user has entered the same token code that was stored at active_codes table which looks like this:
And then at the Controller, I tried this:
public function submit(Request $request)
{
$data = $request->validate([
'token' => 'required|min:6'
]);
if($data['token'] === auth()->user()->activeCode()->code){
dd('same');
}
}
But when I enter the same token, I get this error:
ErrorException Undefined property:
Illuminate\Database\Eloquent\Relations\HasMany::$code
so my question is how to check if the requested token code of user, is as same as the token code which is stored on the DB ?
Note: The relationship between User and ActiveCode Models is OneToMany.
User.php:
public function activeCode()
{
return $this->hasMany(ActiveCode::class);
}
ActiveCode.php:
public function user()
{
return $this->belongsTo(User::class);
}
Your solution is pretty easy, you are not doing ->first() or ->get(), so you are trying to access a model property on a HasMany class.
This code should be similar to:
auth()->user()->activeCode()->first()->code
But if you have more than 1 code, then you should do something like:
public function submit(Request $request)
{
$data = $request->validate([
'token' => 'required|min:6'
]);
if(auth()->user()->activeCode()->where('code', $data['token'])->exists()){
dd('same');
}
}

How to create a custom password field for Laravel Auth

I need to login users registered in the table Igrejas that have the fields responsavel_cpf and responsavel_senha, but Laravel in the function validateCredentials expects the name 'password'.
public function validateCredentials(UserContract $user, array $credentials)
{
$plain = $credentials['password'];
}
I tried to login using attempt without success:
$userdata = array(
'responsavel_cpf' => Input::get('email'),
'responsavel_senha' => Input::get('password')
);
if (Auth::guard('igrejas')->attempt($userdata)) {
return Redirect::to('dashboard_paroquia');
} else {
return Redirect::to('login');
}
What do I do to replace the default fields email and password with responsavel_cpf and responsavel_senha?
You can override the password column in your user model like so:
// User.php
public function getAuthPassword()
{
return $this->custom_pw_field;
}
However, if you actually want to pass an array that does not explicitly contain password to Auth::guard('xxx')->attempt($credentials) (for which there is no reason!), you'll probably have to override and add the Illuminate\Auth\EloquentUserProvider manually which seems a lot of work.
So I would suggest to just use the following:
Auth::guard('xxx')->attempt([
'email' => $request->post('email'),
'password' => $request->post('password')
]);
The password key should then validate against the custom_pw_field that you defined.
Explanation
By looking at the source of Illuminate\Auth\EloquentUserProvider and checking the function public function retrieveByCredentials(array $credentials), you can see what it does:
Find the first record in the auth table that matches all the conditions in the $credentials array except for password (so just email in the example above). So you could add for instance another key like is_webmaster like $authGuard->attempt(['email' => $request->post('email'), 'is_webmaster' => 1]) which would then retrieve the first user record that has these properties.
After this record is retrieved, the hash from its password column is then checked against your input.

Field 'user_id' doesn't have a default value

I'm building a web app with laravel.
First Question:
There's a simple form on users dashboard to fill and save.
Here's the model:
class Salon extends Model
{
protected $table = 'salons';
protected $fillable = [
'salonname', 'saloncity', 'salonaddress', 'salontel', 'salonmob', 'salonsite', 'saloncat', 'salonkhadamat', 'salonkhadamatprice', 'salondesc', 'saloninsta', 'salontelegram', 'salontags'
];
public $timestamps = false;
}
and here is the controller :
public function store(Request $request)
{
$user_id = Auth::user()->id;
Salon::create([
'user_id' => $user_id,
'salonname' => $request['salonname'],
'saloncity' => $request['saloncity'],
'salonaddress' => $request['salonaddress'],
'salontel' => $request['salontel'],
'salonmob' => $request['salonmob'],
'salonsite' => $request['salonsite'],
'saloncat' => $request['saloncat'],
'salonkhadamat' => $request['salonkhadamat'],
'salonkhadamatprice' => $request['salonkhadamatprice'],
'salondesc' => $request['salondesc'],
'saloninsta' => $request['saloninsta'],
'salontelegram' => $request['salontelegram'],
'salontags' => $request['salontags']
]);
return 'done!';
}
And the routes:
Route::get('/', function () {
return view('welcome');
});
Auth::routes();
Route::get('/home', 'HomeController#index')->name('home');
Route::get('/salons/add', function () {
return view('add_salon');
})->middleware('auth');
Route::post('salons', 'SalonsController#store');
Route::get('salons', function () {
return 'Hi';
});
When I complete the form and hit send button, it returns this error :
"SQLSTATE[HY000]: General error: 1364 Field 'user_id' doesn't have a default value (SQL: insert into salons (salonname,...
Where am I doing wrong?
I created a table migration as :
public function up()
{
Schema::create('Salons', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users');
$table->string('salonname');
$table->string('saloncity');
$table->string('salonaddress');
$table->integer('salontel');
$table->integer('salonmob');
$table->string('salonsite');
$table->string('saloncat');
$table->string('salonkhadamat');
$table->integer('salonkhadamatprice');
$table->string('salondesc');
$table->string('saloninsta');
$table->string('salontelegram');
$table->string('salontags');
$table->timestamps();
});
}
user_id is using a foreign reference from users table.
let me explain the process, consider we have some users registered on our app, some of them want to add their salons on our website, so we want to use the user_id from the users table on salons table, so we can return salons with the user's data (profile) on our homepage.
Second question:
If a salon have two separate telephone numbers, How can I store them in this table separately? I mean, people can add many telephone-numbers as they want. Or as many addresses as they have, in separate fields.
Third question:
For creating a portfolio section for each salon, Should I create a new table such as attachments to have pictures addresses and salon id to return them on their respective page later?
Add user_id to the fillable array too:
protected $fillable = ['user_id', 'salonname', 'saloncity', 'salonaddress', 'salontel', 'salonmob', 'salonsite', 'saloncat', 'salonkhadamat', 'salonkhadamatprice', 'salondesc', 'saloninsta', 'salontelegram', 'salontags'];
Or use the relationship if it is defined:
$salon = auth()->user()->salons()->create([....
If it's not defined:
public function salons()
{
return $this->hasMany(Salon::class);
}

Laravel 5.2: defined user and get information from other database table

Update I have solved the problem I just forgot the get() here
$userDiplomas = UserDiploma::query()->where( 'user_id', Auth::user()->id );
but still didn't understand why I was not able to use this
Auth::user()->diplomas as $diploma
Here is my case
each user may registered to a diploma or more, now in the user profile I want to display all the diplomas that users registered to.
I made a new table called user_diplomas and here is the table migration
$table->integer( 'user_id' )->unsigned();
$table->foreign( 'user_id' )->references( 'id' )->on( 'users' );
$table->integer( 'diploma_id' )->unsigned();
$table->foreign( 'diploma_id' )->references( 'id' )->on( 'diplomas' );
when user are login and clicks on register now, it inserts 2 values to the user_diplomas table (user_id, diploma_id) this is working fine.
now comes the question
I need to get the user registered diplomas and display them in his profile, please take a look at the User Model and the UserDiploma Model.
User Model
public function diplomas() {
return $this->hasMany( 'App\Models\UserDiploma', 'diploma_id' );
}
UserDiploma Model
public function diplomaId() {
return $this->belongsTo( 'App\Models\Diploma', 'diploma_id' );
}
now trying to fetch the diplomas I try this
#foreach(Auth::user()->diplomas as $diploma)
....
..........
#endforeach
But no results are coming out!
I also try to do something like this in the HomeController
public function index() {
$userDiplomas = UserDiploma::query()->where( 'user_id', Auth::user()->id );
return view( 'trainees.profiles', compact('userDiplomas') );
}
but it didn't work too.
any ideas please how to make it work.
Change method in 'User' model to
public function diplomas() {
return $this->hasMany( 'App\Models\UserDiploma', 'diploma_id','diploma_id' );
}

Laravel - Seeding Relationships

In Laravel, database seeding is generally accomplished through Model factories. So you define a blueprint for your Model using Faker data, and say how many instances you need:
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => bcrypt(str_random(10)),
'remember_token' => str_random(10),
];
});
$user = factory(App\User::class, 50)->create();
However, lets say your User model has a hasMany relationship with many other Models, like a Post model for example:
Post:
id
name
body
user_id
So in this situation, you want to seed your Posts table with actual users that were seeded in your Users table. This doesn't seem to be explicitly discussed, but I did find the following in the Laravel docs:
$users = factory(App\User::class, 3)
->create()
->each(function($u) {
$u->posts()->save(factory(App\Post::class)->make());
});
So in your User factory, you create X number of Posts for each User you create. However, in a large application where maybe 50 - 75 Models share relationships with the User Model, your User Seeder would essentially end up seeding the entire database with all it's relationships.
My question is: Is this the best way to handle this? The only other thing I can think of is to Seed the Users first (without seeding any relations), and then pull random Users from the DB as needed while you are seeding other Models. However, in cases where they need to be unique, you'd have to keep track of which Users had been used. Also, it seems this would add a lot of extra query-bulk to the seeding process.
You can use saveMany as well. For example:
factory(User::class, 10)->create()->each(function ($user) {
$user->posts()->saveMany(factory(Posts::class, 5)->make());
});
You can do this using closures within the ModelFactory as discussed here.
This solution works cleanly and elegantly with seeders as well.
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => bcrypt(str_random(10)),
'remember_token' => str_random(10),
];
});
$factory->define(App\Post::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'body' => $faker->paragraph(1),
'user_id' => function() {
return factory(App\User::class)->create()->id;
},
];
});
For your seeder, use something simple like this:
//create 10 users
factory(User::class, 10)->create()->each(function ($user) {
//create 5 posts for each user
factory(Post::class, 5)->create(['user_id'=>$user->id]);
});
NOTE: This method does not create unneeded entries in the database, instead the passed attributes are assigned BEFORE the creation of associated records.
Personally I think one Seeder class to manage these relations is nicer then separated seeder classes, because you have all the logic in one place, so in one look you can see what is going on. (Anyone that knows a better approach: please share) :)
A solution might be: one DatabaseSeeder and private methods within the class to keep the 'run' method a bit cleaner. I have this example below, which has a User, Link, LinkUser (many-to-many) and a Note (many-to-one).
For the many-to-many relations I first create all the Links, and get the inserted ids. (since the ids are auto-inc I think the ids could be fetched easier (get max), but doesn't matter in this example). Then create the users, and attach some random links to each user (many-to-many). It also creates random notes for each user (many-to-one example). It uses the 'factory' methods.
If you replace the 'Link' for your 'Post' this should work. (You can remove the 'Note' section then...)
(There is also a method to make sure you have 1 valid user with your own login credentials.)
<?php
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
// Create random links
factory(App\Link::class, 100)->create();
// Fetch the link ids
$link_ids = App\Link::all('id')->pluck('id')->toArray();
// Create random users
factory(App\User::class, 50)->create()->each(function ($user) use ($link_ids) {
// Example: Many-to-many relations
$this->attachRandomLinksToUser($user->id, $link_ids);
// Example: Many-to-one relations
$this->createNotesForUserId( $user->id );
});
// Make sure you have a user to login with (your own email, name and password)
$this->updateCredentialsForTestLogin('john#doe.com', 'John Doe', 'my-password');
}
/**
* #param $user_id
* #param $link_ids
* #return void
*/
private function attachRandomLinksToUser($user_id, $link_ids)
{
$amount = random_int( 0, count($link_ids) ); // The amount of links for this user
echo "Attach " . $amount . " link(s) to user " . $user_id . "\n";
if($amount > 0) {
$keys = (array)array_rand($link_ids, $amount); // Random links
foreach($keys as $key) {
DB::table('link_user')->insert([
'link_id' => $link_ids[$key],
'user_id' => $user_id,
]);
}
}
}
/**
* #param $user_id
* #return void
*/
private function createNotesForUserId($user_id)
{
$amount = random_int(10, 50);
factory(App\Note::class, $amount)->create([
'user_id' => $user_id
]);
}
/**
* #param $email
* #param $name
* #param $password
* #return void
*/
private function updateCredentialsForTestLogin($email, $name, $password)
{
$user = App\User::where('email', $email)->first();
if(!$user) {
$user = App\User::find(1);
}
$user->name = $name;
$user->email = $email;
$user->password = bcrypt($password); // Or whatever you use for password encryption
$user->save();
}
}
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'email' => $faker->email,
'password' => bcrypt(str_random(10)),
'remember_token' => str_random(10),
];
});
$factory->define(App\Post::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'body' => $faker->paragraph(1),
'user_id' => factory(App\User::class)->create()->id,
];
});
So now if you do this factory(App\Post::class, 4)->create() it will create 4 different posts and in the process also create 4 different users.
If you want the same user for all the posts what I usually do is:
$user = factory(App\User::class)->create();
$posts = factory(App\Posts::class, 40)->create(['user_id' => $user->id]);
I want to share the approach i've taken for insert many posts to many users:`
factory(App\User::class, 50)->create()
->each(
function ($u) {
factory(App\Post::class, 10)->create()
->each(
function($p) use (&$u) {
$u->posts()->save($p)->make();
}
);
}
);
`
This workaround worked for me after being all day long looking for a way to seed the relationship
this worked for me in laravel v8
for ($i=0; $i<=2; $i++) {
$user = \App\Models\User::factory(1)->create()->first();
$product = \App\Models\Product::factory(1)->create(['user_id' => $user->id])->first();
}
I use a custom made relateOrCreate function that finds a random entry of that model in the database. If none exist, it creates a new one:
function relateOrCreate($class) {
$instances = $class::all();
$instance;
if (count($instances) > 0) {
$randomIndex = rand(0, (count($instances) - 1));
$instance = $instances[$randomIndex];
}
else {
$instance = $class::factory()->create();
}
return $instance;
}
Then I use it like so:
$relatedUser = relateOrCreate(User::class);
return [
'user_id' => $relatedUser->id,
// ...
];

Categories