Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User auth without using google sso #10

Merged
merged 6 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions api/app/Auth/ThrottlesLogins.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<?php

namespace App\Auth;

use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Cache\RateLimiter;
use Illuminate\Auth\Events\Lockout;
use Illuminate\Support\Facades\Lang;
use Illuminate\Validation\ValidationException;

/**
* @author Taylor Otwell
* @link https://github.com/laravel/ui/blob/2.x/auth-backend/ThrottlesLogins.php
*/
trait ThrottlesLogins
{
/**
* Determine if the user has too many failed login attempts.
*
* @param Request $request
* @return bool
*/
protected function hasTooManyLoginAttempts(Request $request): bool
{
return $this->limiter()->tooManyAttempts(
$this->throttleKey($request),
$this->maxAttempts()
);
}

/**
* Increment the login attempts for the user.
*
* @param Request $request
* @return void
*/
protected function incrementLoginAttempts(Request $request): void
{
$this->limiter()->hit(
$this->throttleKey($request),
$this->decayMinutes() * 60
);
}

/**
* Redirect the user after determining they are locked out.
*
* @param Request $request
* @return void
*
* @throws ValidationException
*/
protected function sendLockoutResponse(Request $request): void
{
$seconds = $this->limiter()->availableIn(
$this->throttleKey($request)
);

throw ValidationException::withMessages([
'email' => [Lang::get('auth.throttle', [
'attempts' => $this->maxAttempts(),
'seconds' => $seconds,
'minutes' => ceil($seconds / 60),
])],
])->status(Response::HTTP_TOO_MANY_REQUESTS);
}

/**
* Clear the login locks for the given user credentials.
*
* @param Request $request
* @return void
*/
protected function clearLoginAttempts(Request $request): void
{
$this->limiter()->clear($this->throttleKey($request));
}

/**
* Fire an event when a lockout occurs.
*
* @param Request $request
* @return void
*/
protected function fireLockoutEvent(Request $request): void
{
event(new Lockout($request));
}

/**
* Get the throttle key for the given request.
*
* @param Request $request
* @return string
*/
protected function throttleKey(Request $request): string
{
return Str::lower($request->input('email')).'|'.$request->ip();
}

/**
* Get the rate limiter instance.
*
* @return RateLimiter
*/
protected function limiter(): RateLimiter
{
return app(RateLimiter::class);
}

/**
* Get the maximum number of attempts to allow.
*
* @return int
*/
public function maxAttempts(): int
{
return property_exists($this, 'maxAttempts') ? $this->maxAttempts : 5;
}

/**
* Get the number of minutes to throttle for.
*
* @return int
*/
public function decayMinutes(): int
{
return property_exists($this, 'decayMinutes') ? $this->decayMinutes : 1;
}
}
17 changes: 14 additions & 3 deletions api/app/Console/Commands/CreateUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
namespace App\Console\Commands;

use App\Models\User;
use App\Support\Enums\Auth;
use App\Support\Enums\Role;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\Rules\RequiredIf;
use Illuminate\Validation\Rules\Unique;
use Illuminate\Validation\ValidationException;
use function Laravel\Prompts\password;

class CreateUser extends Command
{
Expand All @@ -33,9 +37,16 @@ class CreateUser extends Command
*/
public function handle(): int
{
$validator = Validator::make($this->arguments(), [
'email' => ['email'],
'role' => [new Enum(Role::class)]
$password = null;

if (auth_type() == Auth::Form) {
$password = password('User password', required: true);
}

$validator = Validator::make([...$this->arguments(), 'password' => $password], [
'email' => ['email', new Unique('users', 'email')],
'role' => [new Enum(Role::class)],
'password' => ['nullable', new RequiredIf(auth_type() == Auth::Form), 'min:8'],
]);

if ($validator->errors()->count()) {
Expand Down
60 changes: 60 additions & 0 deletions api/app/Http/Controllers/Auth/LoginController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace App\Http\Controllers\Auth;

use Illuminate\Http\Exceptions\HttpResponseException;
use App\Auth\ThrottlesLogins;
use App\Http\Requests\Auth\Login;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Lang;
use App\Http\Resources\User as UserResource;
use Illuminate\Contracts\Auth\Factory as Auth;
use Illuminate\Validation\ValidationException;

class LoginController extends Controller
{
use ThrottlesLogins;

/**
* Max number of login attempts allowed.
*
* @var integer
*/
protected int $maxAttempts = 5;

/**
* Number of minutes login attempts are throttled for.
*
* @var integer
*/
protected int $decayMinutes = 5;

/**
* Handle an authentication attempt.
*
* @param Login $request
* @param Auth $auth
* @return UserResource
*@throws HttpResponseException
*/
public function __invoke(Login $request, Auth $auth)
{
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);

$this->sendLockoutResponse($request);
}

if ($auth->attempt($request->only('email', 'password'))) {
$request->session()->regenerate();

$this->clearLoginAttempts($request);

return new UserResource($auth->user());
}

$this->incrementLoginAttempts($request);

throw ValidationException::withMessages(['email' => [Lang::get('auth.failed')]])->status(422);
}
}
15 changes: 15 additions & 0 deletions api/app/Http/Controllers/SystemConfigController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace App\Http\Controllers;

use Illuminate\Http\JsonResponse;

class SystemConfigController extends Controller
{
public function __invoke()
{
return new JsonResponse([
'auth_type' => auth_type()
]);
}
}
31 changes: 31 additions & 0 deletions api/app/Http/Requests/Auth/Login.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace App\Http\Requests\Auth;

use Illuminate\Foundation\Http\FormRequest;

class Login extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize(): bool
{
return true;
}

/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules(): array
{
return [
'email' => ['required'],
'password' => ['required'],
];
}
}
7 changes: 7 additions & 0 deletions api/app/Http/Requests/StoreUser.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
namespace App\Http\Requests;

use App\Models\User;
use App\Support\Enums\Auth;
use App\Support\Enums\Role;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Enum;
use Illuminate\Validation\Rules\RequiredIf;

class StoreUser extends FormRequest
{
Expand Down Expand Up @@ -38,6 +40,11 @@ public function rules()
'bail',
'required',
new Enum(Role::class),
],
'password' => [
'nullable',
new RequiredIf(auth_type() == Auth::Form),
'confirmed'
]
];
}
Expand Down
6 changes: 6 additions & 0 deletions api/app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Support\Enums\Role;
use App\Support\Traits\Uuids;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
Expand Down Expand Up @@ -43,6 +44,11 @@ class User extends Authenticatable
'role' => Role::class,
];

public function password(): Attribute
{
return new Attribute(set: fn ($value) => $value ? bcrypt($value) : null);
}

public function oauthProviders(): HasMany
{
return $this->hasMany(OAuthProvider::class);
Expand Down
17 changes: 17 additions & 0 deletions api/app/Support/Enums/Auth.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace App\Support\Enums;

enum Auth: string
{
case Form = 'form';
case Google = 'google';

public function title(): string
{
return match ($this) {
self::Form => 'Form',
self::Google => 'Google'
};
}
}
10 changes: 10 additions & 0 deletions api/app/Support/helpers.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use Carbon\Carbon;
use App\Support\Enums\Auth;

function generate_last_handshake_date(?string $string): ?Carbon
{
Expand Down Expand Up @@ -29,3 +30,12 @@ function generate_last_handshake_date(?string $string): ?Carbon

return $currentDate;
}

function auth_type(): Auth
{
if (config('services.google.client_id')) {
return Auth::Google;
}

return Auth::Form;
}
1 change: 1 addition & 0 deletions api/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"guzzlehttp/guzzle": "^7.2",
"laravel/framework": "^10",
"laravel/octane": "^1.3",
"laravel/prompts": "^0.1.13",
"laravel/sanctum": "^3.2",
"laravel/socialite": "^5.5",
"laravel/tinker": "^2.7",
Expand Down
2 changes: 1 addition & 1 deletion api/composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading