Skip to content

Commit

Permalink
feat(User): Add ability to auto login a user via Auto-Login header in…
Browse files Browse the repository at this point in the history
… local environment

1. Use `LaraStrict\User\Http\Middlewares\Authenticate` middleware
2. Implement `GetUserForAutoLoginActionContract` (add it to App/User/Actions)
3. Bind your implementation in service provider (your UserServiceProvider or AppServiceProvider)

```php
class UserServiceProvider extends ServiceProvider {
	public array $bindings = [
		GetUserForAutoLoginActionContract::class => GetUserForAutoLoginAction::class,
	];
}
```
  • Loading branch information
pionl committed Nov 29, 2022
1 parent 60827da commit 0ddebb8
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 0 deletions.
12 changes: 12 additions & 0 deletions src/User/Contracts/GetUserForAutoLoginActionContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace LaraStrict\User\Contracts;

use Illuminate\Contracts\Auth\Authenticatable;

interface GetUserForAutoLoginActionContract
{
public function execute(string $autoLogin): ?Authenticatable;
}
73 changes: 73 additions & 0 deletions src/User/Http/Middlewares/Authenticate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

declare(strict_types=1);

namespace LaraStrict\User\Http\Middlewares;

use Illuminate\Auth\Middleware\Authenticate as Middleware;
use Illuminate\Contracts\Auth\Factory as Auth;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Request;
use LaraStrict\Enums\EnvironmentType;
use LaraStrict\User\Contracts\GetUserForAutoLoginActionContract;

class Authenticate extends Middleware
{
public function __construct(
Auth $auth,
private readonly Application $application,
private readonly GetUserForAutoLoginActionContract $getUserForAutoLoginActionContract,
) {
parent::__construct($auth);
}

protected function authenticate($request, array $guards): void
{
if (empty($guards)) {
$guards = [null];
}

$this->autoLoginFirstUserOnLocalIfNeeded($request, $guards);

parent::authenticate($request, $guards);
}

/**
* Get the path the user should be redirected to when they are not authenticated.
*
* @param Request $request
*
* @return string|null
*/
protected function redirectTo($request)
{
if ($request->expectsJson() === false) {
return route('login');
}
}

protected function autoLoginFirstUserOnLocalIfNeeded(Request $request, array $guards): void
{
$autoLogin = $request->header('Auto-Login');
if ($this->application->environment([EnvironmentType::Local->value]) === false ||
$autoLogin === null) {
return;
}

foreach ($guards as $guard) {
$guardInstance = $this->auth->guard($guard);

if ($guardInstance->check()) {
break;
}

$user = $this->getUserForAutoLoginActionContract->execute($autoLogin);

if ($user !== null) {
$guardInstance->setUser($user);
}

break;
}
}
}
102 changes: 102 additions & 0 deletions tests/Feature/User/Http/Middlewares/AuthenticateTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<?php

declare(strict_types=1);

namespace Tests\LaraStrict\Feature\User\Http\Middlewares;

use Illuminate\Auth\AuthenticationException;
use LaraStrict\Enums\EnvironmentType;
use LaraStrict\Testing\Concerns\CreateRequest;
use LaraStrict\Testing\Concerns\TestData;
use LaraStrict\User\Contracts\GetUserForAutoLoginActionContract;
use LaraStrict\User\Http\Middlewares\Authenticate;
use Tests\LaraStrict\Feature\TestCase;
use Tests\LaraStrict\Feature\Testing\Concerns\TestRequest;
use const true;

class AuthenticateTest extends TestCase
{
use CreateRequest;
use TestData;

public function data(): array
{
return [
'empty string' => [
static fn (self $self) => $self->assert(value: '', expectedValue: '',),
],
'true' => [
static fn (self $self) => $self->assert(value: true, expectedValue: '1',),
],
'false' => [
static fn (self $self) => $self->assert(value: false, expectedValue: '',),
],
'true string' => [
static fn (self $self) => $self->assert(value: 'true', expectedValue: 'true',),
],
'false string' => [
static fn (self $self) => $self->assert(value: 'false', expectedValue: 'false',),
],
'null' => [
static fn (self $self) => $self->assert(value: null, expectedValue: null,),
],
'valid but different env - testing' => [
static fn (self $self) => $self->assert(
value: '1',
expectedValue: null,
environment: EnvironmentType::Testing,
),
],
'valid but different env - production' => [
static fn (self $self) => $self->assert(
value: '1',
expectedValue: null,
environment: EnvironmentType::Production,
),
],
];
}

public function assert(
string|bool|null $value,
string|null $expectedValue,
EnvironmentType $environment = EnvironmentType::Local,
): void {
$this->app()
->detectEnvironment(static fn () => $environment->value);

$request = $this->createPostRequest(
application: $this->app(),
requestClass: TestRequest::class,
data: [
'test' => 'test',
],
server: $value === null ? [] : [
'HTTP_Auto-Login' => $value,
]
);

$this->app()
->bind(
GetUserForAutoLoginActionContract::class,
static fn () => new GetUserForAutoLoginTestAction($expectedValue)
);

/** @var Authenticate $middleware */
$middleware = $this->app()
->make(Authenticate::class);

if ($expectedValue === null) {
$this->expectException(AuthenticationException::class);
$middleware->handle($request, static function () {
});
return;
}

$called = false;
$middleware->handle($request, static function () use (&$called) {
$called = true;
});
$this->assertEquals(true, $called);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Tests\LaraStrict\Feature\User\Http\Middlewares;

use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Foundation\Auth\User;
use LaraStrict\User\Contracts\GetUserForAutoLoginActionContract;
use PHPUnit\Framework\Assert;

class GetUserForAutoLoginTestAction implements GetUserForAutoLoginActionContract
{
public function __construct(
private readonly ?string $expectedAutoLogin,
) {
}

public function execute(string $autoLogin): ?Authenticatable
{
Assert::assertEquals($this->expectedAutoLogin, $autoLogin, 'Auto login value');
return new User();
}
}

0 comments on commit 0ddebb8

Please sign in to comment.