Skip to content

Instantly share code, notes, and snippets.

@trovster
Last active June 2, 2021 14:03
Show Gist options
  • Save trovster/daf4f9f821d8a0a3fb3c1bdc88098c64 to your computer and use it in GitHub Desktop.
Save trovster/daf4f9f821d8a0a3fb3c1bdc88098c64 to your computer and use it in GitHub Desktop.
A custom session guard to check legacy passwords.
<?php
namespace App\Providers;
use App\Auth\SessionGuard;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Auth;
class AuthServiceProvider extends ServiceProvider
{
protected array $policies = [];
public function boot(): void
{
$this->registerPolicies();
$this->registerCustomProvider();
}
protected function registerCustomProvider(): void
{
Auth::extend('session', static function (Application $app, string $name, array $config) {
$provider = Auth::createUserProvider($config['provider']);
$session = $app->get('session.store');
$request = $app->make('request');
return new SessionGuard($name, $provider, $session, $request);
});
}
}
<?php
namespace App\Auth;
use Illuminate\Auth\SessionGuard as BaseGuard;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Hashing\ArgonHasher as CustomHasher;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Hash;
class SessionGuard extends BaseGuard
{
/**
* @param bool $remember
* @return bool
*/
public function attempt(array $credentials = [], $remember = false)
{
$valid = parent::attempt($credentials, $remember);
if ($valid) {
return true;
}
return $this->attemptLegacy($credentials, $remember);
}
public function attemptLegacy(array $credentials = [], bool $remember = false): bool
{
$user = $this->provider->retrieveByCredentials($credentials);
if ($this->hasValidLegacyCredentials($user, $credentials)) {
$this->updateUser($user, $credentials);
$this->login($user, $remember);
return true;
}
return false;
}
protected function hasValidLegacyCredentials(?Authenticatable $user, array $credentials): bool
{
if (is_null($user)) {
return false;
}
$hasher = new CustomHasher();
$password = Arr::get($credentials, 'password');
return $hasher->check($password, $user->getAuthPassword());
}
protected function updateUser(Authenticatable $user, array $credentials): Authenticatable
{
$password = Arr::get($credentials, 'password');
$user->password = Hash::make($password);
$user->save();
return $user;
}
}
<?php
namespace Tests\Unit\Auth;
use App\Auth\SessionGuard as Guard;
use App\Models\User\User;
use Illuminate\Hashing\ArgonHasher as CustomHasher;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Tests\Unit\TestCase;
/** @group auth */
class SessionGuardTest extends TestCase
{
private Guard $guard;
protected string $validPassword = 'Password1!';
protected string $incorrectPassword = 'IncorrectPassword0!';
/** @test */
public function attemptSuccess(): void
{
$email = $this->faker->safeEmail;
$credentials = [
'email' => $email,
'password' => $this->validPassword,
];
User::factory()->create([
'email' => $email,
'password' => Hash::make($this->validPassword),
]);
$result = $this->guard->attempt($credentials);
$this->assertTrue($result);
}
/** @test */
public function attemptFailure(): void
{
$email = $this->faker->safeEmail;
$credentials = [
'email' => $email,
'password' => $this->incorrectPassword,
];
User::factory()->create([
'email' => $email,
'password' => Hash::make($this->validPassword),
]);
$result = $this->guard->attempt($credentials);
$this->assertFalse($result);
}
/** @test */
public function attemptLegacySuccess(): void
{
$email = $this->faker->safeEmail;
$credentials = [
'email' => $email,
'password' => $this->validPassword,
];
$hasher = new CustomHasher();
$currentPassword = $hasher->make($this->validPassword);
$user = User::factory()->create([
'email' => $email,
'password' => $currentPassword,
]);
$this->assertDatabaseHas($user->getTable(), [
'email' => $email,
'password' => $currentPassword,
]);
$result = $this->guard->attemptLegacy($credentials);
$this->assertTrue($result);
$this->assertDatabaseMissing($user->getTable(), [
'email' => $email,
'password' => $currentPassword,
]);
}
/** @test */
public function attemptLegacyFail(): void
{
$email = $this->faker->safeEmail;
$credentials = [
'email' => $email,
'password' => $this->incorrectPassword,
];
$hasher = new CustomHasher();
User::factory()->create([
'email' => $email,
'password' => $hasher->make($this->validPassword),
]);
$result = $this->guard->attemptLegacy($credentials);
$this->assertFalse($result);
}
/** @test */
public function notValidLegacyCredentials(): void
{
$email = $this->faker->safeEmail;
$credentials = [
'email' => $email,
'password' => $this->validPassword,
];
$result = $this->invokeMethod($this->guard, 'hasValidLegacyCredentials', [
null,
$credentials,
]);
$this->assertFalse($result);
}
/** @test */
public function notValidLegacyCredentialsWithUser(): void
{
$email = $this->faker->safeEmail;
$credentials = [
'email' => $email,
'password' => $this->incorrectPassword,
];
$hasher = new CustomHasher();
$user = User::factory()->create([
'email' => $email,
'password' => $hasher->make($this->validPassword),
]);
$result = $this->invokeMethod($this->guard, 'hasValidLegacyCredentials', [
$user,
$credentials,
]);
$this->assertFalse($result);
}
/** @test */
public function validLegacyCredentialsWithUser(): void
{
$email = $this->faker->safeEmail;
$credentials = [
'email' => $email,
'password' => $this->validPassword,
];
$hasher = new CustomHasher();
$currentPassword = $hasher->make($this->validPassword);
$user = User::factory()->create([
'email' => $email,
'password' => $currentPassword,
]);
$result = $this->invokeMethod($this->guard, 'hasValidLegacyCredentials', [
$user,
$credentials,
]);
$this->assertTrue($result);
}
/** @test */
public function updateUser(): void
{
$email = $this->faker->safeEmail;
$credentials = [
'email' => $email,
'password' => $this->validPassword,
];
$hasher = new CustomHasher();
$currentPassword = $hasher->make($this->validPassword);
$user = User::factory()->create([
'email' => $email,
'password' => $currentPassword,
]);
$this->assertDatabaseHas($user->getTable(), [
'email' => $email,
'password' => $currentPassword,
]);
$updatedUser = $this->invokeMethod($this->guard, 'updateUser', [
$user,
$credentials,
]);
$this->assertDatabaseMissing($user->getTable(), [
'email' => $email,
'password' => $currentPassword,
]);
$this->assertDatabaseHas($user->getTable(), [
'email' => $email,
'password' => $updatedUser->password,
]);
}
protected function setUp(): void
{
parent::setUp();
$provider = Auth::createUserProvider('users');
$session = $this->app->get('session.store');
$request = $this->app->make('request');
$this->guard = new Guard('session', $provider, $session, $request);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment