Skip to content

Commit

Permalink
add middleware and authentication (session)
Browse files Browse the repository at this point in the history
  • Loading branch information
rohsyl committed Dec 20, 2023
1 parent c02f5e7 commit 5e52c0b
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/Http/Controllers/RequestCodeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ public function __invoke() {
}
return request()->has('redirect_url')
? redirect()->to(request()->redirect_url)
: redirect()->back();
: response('', 200);
}
}
28 changes: 28 additions & 0 deletions src/Http/Middlewares/OtcMiddleware.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace rohsyl\LaravelOtc\Http\Middlewares;

use Closure;
use Illuminate\Http\Client\HttpClientException;
use rohsyl\LaravelOtc\Otc;
use Symfony\Component\HttpKernel\Exception\HttpException;

class OtcMiddleware
{

public function handle($request, Closure $next, $inputs = null)
{
if(!Otc::check()) {
abort(401, 'Unauthorized1');
}
$inputs = explode(',', $inputs ?? '');

$authenticatable = $inputs[0] ?? config('otc.default-authenticatable', 'user');
$modelClass = config('otc.authenticatables.'.$authenticatable.'.model');
if(get_class(Otc::user()) !== $modelClass) {
abort(401, 'Unauthorized2');
}

return $next($request);
}
}
67 changes: 63 additions & 4 deletions src/LaravelOtcManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ public function __construct(
*/
public function check()
{
$token = $this->getRequest()->bearerToken() ?? (
$this->getRequest()->has('token') ? $this->getRequest()->token : null
);
$token = $this->getRequest()->bearerToken()
?? ($this->getRequest()->has('token') ? $this->getRequest()->token : null)
?? (session()->has('otc_token') ? session()->get('otc_token') : null);

if(!isset($token)) return false;

Expand All @@ -41,6 +41,44 @@ public function check()
&& $otc->token_valid_until->isAfter(now());
}

/**
* Authenticate using token
* @param $token
* @return bool
*/
public function auth($token): bool {
$otc = $this->findOtcTokenByToken($token);
if(!isset($otc) || $otc->token_valid_until->isBefore(now())) {
return false;
}
session()->regenerate(true);
session()->put('otc_token', $token);
return true;
}

public function logout() {
session()->forget('otc_token');
session()->regenerate(true);
}

/**
* Get the authenticated user
* @return Model|null
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function user(): Model|null {
$token = session()->get('otc_token');
if(!isset($token)) {
return null;
}
$otc = $this->findOtcTokenByToken($token);
if(!isset($otc) || $otc->token_valid_until->isBefore(now())) {
return null;
}
return $otc->related;
}

public function unauthorizedResponse(Model $related) : Response
{
$slug = $this->getModelSlug($related);
Expand Down Expand Up @@ -77,7 +115,7 @@ public function storeCode(Model $related, $code) : OtcToken
]);
}

public function getModel() : Model
public function getModel() : ?Model
{
$slug = $this->getRequest()->type;
$identifier = $this->getRequest()->identifier;
Expand Down Expand Up @@ -124,6 +162,16 @@ public function createToken(OtcToken $token)
public function sendCode(?Model $related = null, ?OtcToken $token = null)
{
$related = $related ?? $this->getModel();

// if we cant find the related model
if(!isset($related)) {
// and the register is not allowed, we abort
if(!$this->isRegisterable()) {
abort(403);
}
}


$token = $token ?? $this->createCode($related);

$notifierClass = config('otc.notifier_class');
Expand Down Expand Up @@ -179,6 +227,17 @@ private function getModelSlug(Model $related) {
return null;
}

private function isRegisterable() {
$slug = $this->getRequest()->type;
return config('otc.authenticatables.' . $slug . '.register');
}

private function register() {
$slug = $this->getRequest()->type;


}

private function getRequest() {
return $this->request ?? request();
}
Expand Down
3 changes: 3 additions & 0 deletions src/LaravelOtcServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Spatie\LaravelPackageTools\Package;
use Spatie\LaravelPackageTools\PackageServiceProvider;
use Spatie\LaravelPackageTools\Commands\InstallCommand;
use rohsyl\LaravelOtc\Http\Middlewares\OtcMiddleware;

class LaravelOtcServiceProvider extends PackageServiceProvider
{
Expand Down Expand Up @@ -50,5 +51,7 @@ public function packageBooted()
RateLimiter::for('laravel-otc', function($request) {
return Limit::perMinute(config('otc.rate-limit.per-minute', 6));
});

$this->app['router']->aliasMiddleware('otc', OtcMiddleware::class);
}
}
2 changes: 2 additions & 0 deletions src/Otc.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
/**
* @method static boolean check()
* @method static Response unauthorizedResponse(Model $related)
* @method static Model|null user()
* @method static bool auth(string $token)
*
* @see LaravelOtcManager
*/
Expand Down
17 changes: 17 additions & 0 deletions tests/ControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,23 @@ public function it_request_code_controller_redirect()
Notification::assertSentTo($this->user, OneTimeCodeNotification::class);
}

/** @test */
public function it_throw_error_when_entity_not_found_and_not_registerable()
{

Notification::fake();

$response = $this->post(
uri: route('laravel-otc.request-code'),
data: [
'type' => 'user',
'identifier' => '[email protected]',
]
);

$response->assertStatus(403);
}

/** @test */
public function it_auth_and_return_token()
{
Expand Down
82 changes: 82 additions & 0 deletions tests/OtcTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,88 @@ public function unauthorized_response_abort_when_unknow_type_test() {

}

/** @test */
public function it_auth_using_token() {

Carbon::setTestNow(Carbon::create(2022, 10, 10, 11, 0, 0));

OtcToken::factory()->create([
'related_type' => User::class,
'related_id' => $this->user->id,
'code' => 12345,
'code_valid_until' => Carbon::create(2022, 10, 10, 10, 15, 0),
'token' => '123456789asdfghjkqwertzuiy<xcvbnm',
'token_valid_until' => Carbon::create(2022, 11, 20, 10, 00, 0),
]);

$this->assertTrue($this->manager->auth('123456789asdfghjkqwertzuiy<xcvbnm'));
}

/** @test */
public function it_doesnt_auth_using_token_when_unvalid() {

Carbon::setTestNow(Carbon::create(2023, 10, 10, 11, 0, 0));

OtcToken::factory()->create([
'related_type' => User::class,
'related_id' => $this->user->id,
'code' => 12345,
'code_valid_until' => Carbon::create(2022, 10, 10, 10, 15, 0),
'token' => '123456789asdfghjkqwertzuiy<xcvbnm',
'token_valid_until' => Carbon::create(2022, 11, 20, 10, 00, 0),
]);

$this->assertFalse($this->manager->auth('123456789asdfghjkqwertzuiy<xcvbnm'));
}

/** @test */
public function it_doesnt_auth_using_token_when_no_token() {

$this->assertFalse($this->manager->auth('123456789asdfghjkqwertzuiy<xcvbnm'));
}

/** @test */
public function it_return_user_when_authenticated() {

Carbon::setTestNow(Carbon::create(2022, 10, 10, 11, 0, 0));

OtcToken::factory()->create([
'related_type' => User::class,
'related_id' => $this->user->id,
'code' => 12345,
'code_valid_until' => Carbon::create(2022, 10, 10, 10, 15, 0),
'token' => '123456789asdfghjkqwertzuiy<xcvbnm',
'token_valid_until' => Carbon::create(2022, 11, 20, 10, 00, 0),
]);

$this->manager->auth('123456789asdfghjkqwertzuiy<xcvbnm');

$this->assertEquals($this->user->id, $this->manager->user()->id);
}

/** @test */
public function it_return_null_when_not_authenticated() {
$this->assertNull($this->manager->user());
}

/** @test */
public function it_return_null_when_unvalid() {
Carbon::setTestNow(Carbon::create(2023, 10, 10, 11, 0, 0));

OtcToken::factory()->create([
'related_type' => User::class,
'related_id' => $this->user->id,
'code' => 12345,
'code_valid_until' => Carbon::create(2022, 10, 10, 10, 15, 0),
'token' => '123456789asdfghjkqwertzuiy<xcvbnm',
'token_valid_until' => Carbon::create(2022, 11, 20, 10, 00, 0),
]);

$this->manager->auth('123456789asdfghjkqwertzuiy<xcvbnm');

$this->assertNull($this->manager->user());
}

/** @test */
public function unauthorized_response_abort_test() {
//$this->expectException(NoMatchingAuthenticatableException::class);
Expand Down

0 comments on commit 5e52c0b

Please sign in to comment.