Last active
June 2, 2021 14:03
-
-
Save trovster/daf4f9f821d8a0a3fb3c1bdc88098c64 to your computer and use it in GitHub Desktop.
A custom session guard to check legacy passwords.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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); | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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