From c008d8082b651e38b7975c12f53af9ab4461736a Mon Sep 17 00:00:00 2001 From: Mohammad Hafijul Islam Date: Fri, 29 Sep 2023 04:42:45 +0600 Subject: [PATCH] auth login and logout api working --- config/auth.php | 37 +++++++ lang/.gitkeep | 0 lang/en/messages.php | 28 ++++++ routes/api.php | 19 ++-- src/Enums/UserStatus.php | 12 +++ .../AuthenticatedSessionController.php | 59 +++++++---- src/Http/Requests/LoginRequest.php | 6 +- src/Http/Resources/LoginResource.php | 97 +++++++++++++++++++ src/Models/Profile.php | 61 +++++++++++- src/Models/User.php | 16 ++- src/Repositories/Eloquent/UserRepository.php | 10 +- src/Repositories/Mongodb/UserRepository.php | 4 + test.php | 3 - 13 files changed, 304 insertions(+), 48 deletions(-) delete mode 100644 lang/.gitkeep create mode 100644 lang/en/messages.php create mode 100644 src/Enums/UserStatus.php create mode 100644 src/Http/Resources/LoginResource.php delete mode 100644 test.php diff --git a/config/auth.php b/config/auth.php index 2da4632..fa4706a 100644 --- a/config/auth.php +++ b/config/auth.php @@ -59,4 +59,41 @@ | This value will be used to across system where model is needed */ 'user_profile_model' => \Fintech\Auth\Models\Profile::class, + + /* + |-------------------------------------------------------------------------- + | Login Validation + |-------------------------------------------------------------------------- + | + | This value will be used to across system where model is needed + */ + 'validation' => [ + 'login' => [ + 'login_id' => ['required', 'string'], + 'password' => ['required', 'string', \Illuminate\Validation\Rules\Password::default()], + ] + ], + + /* + |-------------------------------------------------------------------------- + | Lock Up Threshold + |-------------------------------------------------------------------------- + | + | This value will be used to across system where model is needed + */ + 'threshold' => [ + 'password' => 10, + 'pin' => 3, + ], + + 'threshold_notification' => false, + + /* + |-------------------------------------------------------------------------- + | Authentication Middleware + |-------------------------------------------------------------------------- + | + | This value will be used to across system where model is needed + */ + 'middleware' => ['auth:sanctum'] ]; diff --git a/lang/.gitkeep b/lang/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lang/en/messages.php b/lang/en/messages.php new file mode 100644 index 0000000..5c2d766 --- /dev/null +++ b/lang/en/messages.php @@ -0,0 +1,28 @@ + 'Login successful.', + 'logout' => 'Logout successful. Thank you for using our services', + 'failed' => 'These credentials do not match our records.', + 'password' => 'The provided password is incorrect.', + 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + 'Invalid Token' => 'Invalid Token', + 'Your IP :user_ip is blocked. Please contact support.' => 'Your IP :user_ip is blocked. Please contact support.', //don't translate :user_ip + 'This user are not login. Please contact support.' => 'This user are not login. Please contact support.', + 'Sorry, You entered wrong mobile number or invalid password!' => 'Sorry, You entered wrong mobile number or invalid password!', + 'warning' => 'Sorry, You entered wrong credentials! You already attempt :attempt. times out of :threshold', + 'lockup' => 'Sorry, Your Account is has been Locked. Please contact support!', + 'This user are not login' => 'This user are not login', + 'Sorry, You entered wrong mobile number or invalid pin!' => 'Sorry, You entered wrong mobile number or invalid pin!', + 'Sorry, You entered wrong mobile number or pin! You already attempt :wrong_pin_password. times out of :password_retry_limit' + => 'Sorry, You entered wrong mobile number or pin! You already attempt :wrong_pin_password. times out of :password_retry_limit', +]; diff --git a/routes/api.php b/routes/api.php index f76c1cd..00736b3 100644 --- a/routes/api.php +++ b/routes/api.php @@ -46,19 +46,16 @@ ->name('verification.send'); Route::post('/logout', [AuthenticatedSessionController::class, 'destroy']) - ->middleware('auth') + ->middleware(config('fintech.auth.middleware')) ->name('logout'); - Route::apiResource('users', \Fintech\Auth\Http\Controllers\UserController::class); - // Route::apiResource('roles', \Fintech\Auth\Http\Controllers\RoleController::class); - // Route::apiResource('permissions', \Fintech\Auth\Http\Resources\PermissionCollection::class); - // Route::apiResource('teams', \Fintech\Auth\Http\Controllers\TeamController::class); - Route::apiSingleton('users.profile', \Fintech\Auth\Http\Controllers\ProfileController::class); + Route::middleware(config('fintech.auth.middleware'))->group(function () { + Route::apiResource('users', \Fintech\Auth\Http\Controllers\UserController::class); + // Route::apiResource('roles', \Fintech\Auth\Http\Controllers\RoleController::class); + // Route::apiResource('permissions', \Fintech\Auth\Http\Resources\PermissionCollection::class); + // Route::apiResource('teams', \Fintech\Auth\Http\Controllers\TeamController::class); + Route::apiSingleton('users.profile', \Fintech\Auth\Http\Controllers\ProfileController::class); + }); }); }); -Route::prefix('v2')->group(function () { - Route::prefix('auth')->group(function () { - - }); -}); diff --git a/src/Enums/UserStatus.php b/src/Enums/UserStatus.php new file mode 100644 index 0000000..8b1cbdf --- /dev/null +++ b/src/Enums/UserStatus.php @@ -0,0 +1,12 @@ +ensureIsNotRateLimited(); - if (! Auth::attempt($request->only('login_id', 'password'))) { + $attemptUser = \Fintech\Auth\Facades\Auth::user()->list([ + 'login_id' => $request->input('login_id'), + 'paginate' => false + ]); + + if ($attemptUser->isEmpty()) { + + return $this->failed(__('auth::messages.failed')); + } + + $attemptUser = $attemptUser->first(); + + if ($attemptUser->wrong_password > config('fintech.auth.threshold.password', 10)) { + + \Fintech\Auth\Facades\Auth::user()->update($attemptUser->id, [ + 'status' => UserStatus::InActive->value + ]); + + return $this->failed(__('auth::messages.lockup')); + } + + if (!Hash::check($request->input('password'), $attemptUser->password)) { $request->hitRateLimited(); - return $this->failed(__('auth.failed')); + \Fintech\Auth\Facades\Auth::user()->update($attemptUser->id, [ + 'wrong_password' => $attemptUser->wrong_password + 1 + ]); + + return $this->failed(__('auth::messages.failed')); } $request->clearRateLimited(); - /** - * @var User $authUser - */ - $authUser = Auth::user(); + Auth::login($attemptUser); + + Auth::user()->tokens->each(fn($token) => $token->delete()); - $token = $authUser->createToken(config('app.name'))->plainTextToken; + //permission check - return response()->json(['data' => $authUser, 'token' => $token, 'message' => 'Login Successful.'], Response::HTTP_OK); + return new LoginResource(Auth::user()); } /** * Destroy an authenticated session. + * @return JsonResponse */ - public function destroy(Request $request): Response + public function destroy(): JsonResponse { Auth::guard('web')->logout(); - $request->session()->invalidate(); - - $request->session()->regenerateToken(); - - return response()->noContent(); + return $this->deleted(__('auth::messages.logout')); } } diff --git a/src/Http/Requests/LoginRequest.php b/src/Http/Requests/LoginRequest.php index 8858066..63b8f5c 100644 --- a/src/Http/Requests/LoginRequest.php +++ b/src/Http/Requests/LoginRequest.php @@ -25,10 +25,10 @@ public function authorize(): bool */ public function rules(): array { - return [ + return config('fintech.auth.validation.login', [ 'login_id' => ['required', 'string'], - 'password' => ['required', 'string'], - ]; + 'password' => ['required', 'string', \Illuminate\Validation\Rules\Password::default()], + ]); } /** diff --git a/src/Http/Resources/LoginResource.php b/src/Http/Resources/LoginResource.php new file mode 100644 index 0000000..7c38277 --- /dev/null +++ b/src/Http/Resources/LoginResource.php @@ -0,0 +1,97 @@ +resource->load([ + 'profile.country', 'profile.state', 'profile.city', + 'profile.presentCountry', 'profile.presentState', 'profile.presentCity' + ]); + + return [ + 'id' => $this->id ?? null, + 'name' => $this->name ?? null, + 'mobile' => $this->mobile ?? null, + 'email' => $this->email ?? null, + 'login_id' => $this->login_id ?? null, + 'status' => $this->status ?? null, + 'language' => $this->language ?? null, + 'currency' => $this->currency ?? null, + 'app_version' => $this->app_version ?? null, + 'total_balance' => 0, + 'email_verified_at' => $this->email_verified_at ?? null, + 'mobile_verified_at' => $this->mobile_verified_at ?? null, + 'created_at' => $this->created_at ?? null, + 'updated_at' => $this->updated_at ?? null, + 'profile' => (($this->profile != null) + ? [ + 'user_profile_data' => $this->profile->user_profile_data ?? null, + 'id_type' => $this->profile->id_type ?? null, + 'id_no' => $this->profile->id_no ?? null, + 'id_issue_country' => $this->profile->id_issue_country ?? null, + 'id_expired_at' => $this->profile->id_expired_at ?? null, + 'id_issue_at' => $this->profile->id_issue_at ?? null, + 'id_no_duplicate' => $this->profile->id_no_duplicate ?? null, + 'date_of_birth' => $this->profile->date_of_birth ?? null, + 'address' => $this->profile->permanent_address ?? null, + 'city_id' => $this->profile->city_id ?? null, + 'city_name' => $this->profile->city->name ?? null, + 'state_id' => $this->profile->state_id ?? null, + 'state_name' => $this->profile->state->name ?? null, + 'country_id' => $this->profile->country_id ?? null, + 'country_name' => $this->profile->country->name ?? null, + 'post_code' => $this->profile->post_code ?? null, + 'present_address' => $this->profile->present_address ?? null, + 'present_city_id' => $this->profile->present_city_id ?? null, + 'present_city_name' => $this->profile->presentCity->name ?? null, + 'present_state_id' => $this->profile->present_state_id ?? null, + 'present_state_name' => $this->profile->presentState_name ?? null, + 'present_country_id' => $this->profile->present_country_id ?? null, + 'present_country_name' => $this->profile->presentCountry_name ?? null, + 'present_post_code' => $this->profile->present_post_code ?? null, + + 'blacklisted' => $this->profile->blacklisted ?? null, + 'created_at' => $this->profile->created_at ?? null, + 'updated_at' => $this->profile->updated_at ?? null, + ] + : (new \stdClass())) + ]; + } + + /** + * Get additional data that should be returned with the resource array. + * + * @param Request $request + * @return array + */ + public function with(Request $request): array + { + $origin = Str::slug(config('app.name')); + + return [ + 'access' => [ + 'token' => $this->createToken($origin)->plainTextToken, + 'type' => 'bearer', + 'permissions' => [ + 'login', + 'dashboard' + ] + ], + 'message' => trans('auth::messages.success') + ]; + } +} diff --git a/src/Models/Profile.php b/src/Models/Profile.php index 599c535..042a8cc 100644 --- a/src/Models/Profile.php +++ b/src/Models/Profile.php @@ -7,6 +7,11 @@ use Illuminate\Database\Eloquent\SoftDeletes; use OwenIt\Auditing\Contracts\Auditable; +/** + * Class Profile + * @package Fintech\Auth\Models + * + */ class Profile extends Model implements Auditable { use BlameableTrait; @@ -25,9 +30,7 @@ class Profile extends Model implements Auditable protected $guarded = ['id']; - - - protected $casts = []; + protected $casts = ['date_of_birth' => 'datetime', 'user_profile_data' => 'json']; /* |-------------------------------------------------------------------------- @@ -41,6 +44,58 @@ class Profile extends Model implements Auditable |-------------------------------------------------------------------------- */ + /** + * Permanent Address + */ + + public function country() + { + return $this->belongsTo(config('fintech.metadata.country_model')); + } + + public function state() + { + return $this->belongsTo(config('fintech.metadata.state_model')); + } + + public function city() + { + return $this->belongsTo(config('fintech.metadata.city_model')); + } + + /** + * Present Address + */ + + public function presentCountry() + { + return $this->belongsTo(config('fintech.metadata.country_model'), 'present_country_id'); + } + + public function presentState() + { + return $this->belongsTo(config('fintech.metadata.state_model'), 'present_state_id'); + } + + public function presentCity() + { + return $this->belongsTo(config('fintech.metadata.city_model'), 'present_city_id'); + } + + /** + * Parental Access + */ + + public function user() + { + return $this->belongsTo(config('fintech.auth.user_model')); + } + + public function approver() + { + return $this->belongsTo(config('fintech.auth.user_model'), 'approver_id'); + } + /* |-------------------------------------------------------------------------- | SCOPES diff --git a/src/Models/User.php b/src/Models/User.php index 43d3e7c..0da5555 100644 --- a/src/Models/User.php +++ b/src/Models/User.php @@ -8,6 +8,11 @@ use Laravel\Sanctum\HasApiTokens; use OwenIt\Auditing\Contracts\Auditable; +/** + * Class User + * @package Fintech\Auth\Models + * @property-read Profile $profile + */ class User extends Authenticatable implements Auditable { use BlameableTrait; @@ -27,9 +32,10 @@ class User extends Authenticatable implements Auditable protected $guarded = ['id']; - - - protected $casts = []; + protected $casts = [ + 'email_verified_at' => 'datetime', + 'mobile_verified_at' => 'datetime' + ]; /* |-------------------------------------------------------------------------- @@ -43,9 +49,9 @@ class User extends Authenticatable implements Auditable |-------------------------------------------------------------------------- */ - public function userProfile() + public function profile() { - return $this->hasOne(Profile::class); + return $this->hasOne(config('fintech.auth.user_profile_model')); } /* diff --git a/src/Repositories/Eloquent/UserRepository.php b/src/Repositories/Eloquent/UserRepository.php index ab4006b..598d3b0 100644 --- a/src/Repositories/Eloquent/UserRepository.php +++ b/src/Repositories/Eloquent/UserRepository.php @@ -24,7 +24,7 @@ public function __construct() { $model = app()->make(config('fintech.auth.user_model', User::class)); - if (! $model instanceof Model) { + if (!$model instanceof Model) { throw new InvalidArgumentException("Eloquent repository require model class to be `Illuminate\Database\Eloquent\Model` instance."); } @@ -44,7 +44,9 @@ public function list(array $filters = []) //Handle Sorting $query->orderBy($filters['sort'] ?? $this->model->getKeyName(), $filters['direction'] ?? 'asc'); - $query->with(['userProfile']); + if (isset($filters['login_id']) && !empty($filters['login_id'])) { + $query->where('login_id', $filters['login_id'])->limit(1); + } //Prepare Output return (isset($filters['paginate']) && $filters['paginate'] == true) @@ -116,7 +118,7 @@ public function update(int|string $id, array $attributes = []) /** * find and delete a entry from records * - * @param bool $onlyTrashed + * @param bool $onlyTrashed * @return bool|null * * @throws UserRepositoryException @@ -176,7 +178,7 @@ public function delete(int|string $id) */ public function restore(int|string $id) { - if (! method_exists($this->model, 'restore')) { + if (!method_exists($this->model, 'restore')) { throw new InvalidArgumentException('This model does not have `Illuminate\Database\Eloquent\SoftDeletes` trait to perform restoration.'); } diff --git a/src/Repositories/Mongodb/UserRepository.php b/src/Repositories/Mongodb/UserRepository.php index df19cdf..2d33873 100644 --- a/src/Repositories/Mongodb/UserRepository.php +++ b/src/Repositories/Mongodb/UserRepository.php @@ -40,6 +40,10 @@ public function list(array $filters = []) //Handle Sorting $query->orderBy($filters['sort'] ?? $this->model->getKeyName(), $filters['direction'] ?? 'asc'); + if (isset($filters['login_id']) && !empty($filters['login_id'])) { + $query->where('login_id', $filters['login_id'])->limit(1); + } + //Prepare Output return (isset($filters['paginate']) && $filters['paginate'] == true) ? $query->paginate(($filters['per_page'] ?? 20)) diff --git a/test.php b/test.php deleted file mode 100644 index 7bb9188..0000000 --- a/test.php +++ /dev/null @@ -1,3 +0,0 @@ -