-
Notifications
You must be signed in to change notification settings - Fork 14
Add new Authenticator service to decouple authentication from panel
#846
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
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
cd01715
Add new `Authenticator` service to decouple authentication from panel
giuscris 9d8abcd
Fix configuration default limits
giuscris 40e1a0e
Fix handling of deprecated config
giuscris c69908b
Regenerate session on login/logout to avoid session fixation
giuscris 37615a3
Update documentation
giuscris 83b5175
Do not register further attempts if rate limit is reached
giuscris b327953
Reset attempts on successful login
giuscris aac97eb
Remove after session regeneration to ensure no logged user key persists
giuscris cacb26f
Ensure rate limiter is reset even on user update failure
giuscris File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or 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
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| <?php | ||
|
|
||
| namespace Formwork\Authentication; | ||
|
|
||
| use Formwork\Authentication\Exceptions\AuthenticationFailedException; | ||
| use Formwork\Authentication\Exceptions\RateLimitExceededException; | ||
| use Formwork\Authentication\Exceptions\UserNotLoggedException; | ||
| use Formwork\Http\Session\Session; | ||
| use Formwork\Users\User; | ||
| use Formwork\Users\Users; | ||
| use SensitiveParameter; | ||
|
|
||
| class Authenticator | ||
| { | ||
| public const string SESSION_LOGGED_USER_KEY = '_formwork_logged_user'; | ||
|
|
||
| public function __construct( | ||
| protected Users $users, | ||
| protected Session $session, | ||
| protected RateLimiter $rateLimiter | ||
| ) {} | ||
|
|
||
| /** | ||
| * Login a user with given credentials | ||
| * | ||
| * @throws AuthenticationFailedException If authentication fails | ||
| * @throws RateLimitExceededException If rate limit is exceeded | ||
| */ | ||
| public function login( | ||
| string $login, | ||
| #[SensitiveParameter] | ||
| string $password | ||
| ): User { | ||
| try { | ||
| $this->rateLimiter->assertAllowed(); | ||
|
|
||
| /** @var ?User */ | ||
| $user = $this->users->find(fn(User $user) => $user->username() === $login || $user->email() === $login); | ||
|
|
||
| if (!$user?->verifyPassword($password)) { | ||
giuscris marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| throw new AuthenticationFailedException(sprintf('Authentication failed for "%s"', $login)); | ||
| } | ||
| } catch (RateLimitExceededException|AuthenticationFailedException $e) { | ||
| // Delay processing for 0.5-1s | ||
| usleep(random_int(500, 1000) * 1000); | ||
|
||
|
|
||
| throw $e; | ||
| } | ||
|
|
||
| $this->rateLimiter->resetAttempts(); | ||
|
|
||
| $this->session->regenerate(); | ||
| $this->session->set(self::SESSION_LOGGED_USER_KEY, $user->username()); | ||
|
|
||
| $user->set('lastAccess', time()); | ||
| $user->save(); | ||
|
|
||
giuscris marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return $user; | ||
giuscris marked this conversation as resolved.
Show resolved
Hide resolved
giuscris marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /** | ||
| * Return whether a user is logged in | ||
| */ | ||
| public function isLoggedIn(): bool | ||
| { | ||
| return $this->session->has(self::SESSION_LOGGED_USER_KEY); | ||
| } | ||
|
|
||
| /** | ||
| * Logout currently logged in user | ||
| */ | ||
| public function logout(): void | ||
| { | ||
| if (!$this->isLoggedIn()) { | ||
| throw new UserNotLoggedException('Cannot logout, no user is logged in'); | ||
| } | ||
| $this->session->regenerate(); | ||
| $this->session->remove(self::SESSION_LOGGED_USER_KEY); | ||
giuscris marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| /** | ||
| * Get currently logged in user if any | ||
| */ | ||
| public function getUser(): ?User | ||
| { | ||
| $username = $this->session->get(self::SESSION_LOGGED_USER_KEY); | ||
giuscris marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return $this->users->find(fn(User $user) => $user->username() === $username); | ||
giuscris marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
giuscris marked this conversation as resolved.
Show resolved
Hide resolved
giuscris marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
2 changes: 1 addition & 1 deletion
2
...eptions/AuthenticationFailedException.php → ...eptions/AuthenticationFailedException.php
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| <?php | ||
|
|
||
| namespace Formwork\Users\Exceptions; | ||
| namespace Formwork\Authentication\Exceptions; | ||
|
|
||
| use RuntimeException; | ||
|
|
||
|
|
||
31 changes: 31 additions & 0 deletions
31
formwork/src/Authentication/Exceptions/RateLimitExceededException.php
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| <?php | ||
|
|
||
| namespace Formwork\Authentication\Exceptions; | ||
|
|
||
| use RuntimeException; | ||
| use Throwable; | ||
|
|
||
| class RateLimitExceededException extends RuntimeException | ||
| { | ||
| /** | ||
| * @param string $message Exception message | ||
| * @param int $resetTime Time (in seconds) until the rate limit is reset | ||
| * @param int $code Exception code | ||
| */ | ||
| public function __construct( | ||
| string $message = '', | ||
| protected int $resetTime = 0, | ||
| int $code = 0, | ||
| ?Throwable $previous = null | ||
| ) { | ||
| parent::__construct($message, $code, $previous); | ||
| } | ||
|
|
||
| /** | ||
| * Get reset time | ||
| */ | ||
| public function getResetTime(): int | ||
| { | ||
| return $this->resetTime; | ||
| } | ||
| } |
2 changes: 1 addition & 1 deletion
2
...ers/Exceptions/UserNotLoggedException.php → ...ion/Exceptions/UserNotLoggedException.php
This file contains hidden or 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,6 @@ | ||
| <?php | ||
|
|
||
| namespace Formwork\Users\Exceptions; | ||
| namespace Formwork\Authentication\Exceptions; | ||
|
|
||
| use RuntimeException; | ||
|
|
||
|
|
||
This file contains hidden or 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
This file contains hidden or 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
This file contains hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.