-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added auth and user scaffolding to skeleton app (#34)
* Updated CI to only run on official branch pushes * Fixed missing PHPDoc * Started the foundation of improved auth examples in demo * Changed some names of auth handlers to be more specific, removed unused code * Added some authorization checks, removed copyrights * Fixed typo * Started implementing some integration tests * Updated to sort elements alphabetically, updated to use latest Aphiria changes * Updated wording about demo code * Switched things over to SQLite to be less hacky, fixed various bugs, still a WIP * Renamed some stuff, cleaned up some tests * Small refactorings * Updated to use principal builder, fixed logic with integration tests, moved DB out of Git * Added some more tests * Updated more code to use PrincipalBuilder, discovered bug that is not enforcing authentication during authorization * Removed deprecrated PHP-CS-Fixer rule * Removed unnecessary comma * Fixed tests * Made cookie the default authentication scheme, removed unnecessary Authenticate attribute, but tests are still broken * Updated to latest Aphiria * Merged origin * Fixed namespaces in .env.dist file * Started adding new classes and interfaces for mocked authenticator. Still a WIP. * Ran linter * Cleaned up some code and comments * Added some more comments * Refactored authenticator in integration tests to implement an interface * Fixed some bugs with mocked auth calls, added auth integration tests * Made var names consistent * Updated integration test to use response parser * Updated to use latest assertions * Fixed Psalm issue * Fixed Psalm config * Fixed Psalm config * Fixed some Psalm errors * Fixed type for name identifier to be int * Updated tests to use in-memory SQLite DB to simplify testing * Moved some integration test methods into traits for better encapsulation * Fixed Psalm error * Simplified logic * Updated to publish code coverage during CI * Added shepherd to Psalm during CI * Simplified mock authenticator because some properties were not being used * Updated to use simpler way of setting auth schemes during mock authentication * Removed unused import * Made it easier to seed DBs from integration tests * Updated to use built-in mock auth in integration tests * Fixed in-memory SQLite usage, code style tweaks * Refactored default user credentials to be read from .env file, added command to generate them * Moved demo code out of demo dir * Fixed Psalm error * Fixed Psalm error * Moved mock auth into a callback for more clarity on the scope of what's being mocked * Updated to use simpler PrincipalBuilder * Minor CS changes
- Loading branch information
1 parent
c4f19ba
commit 52b6555
Showing
52 changed files
with
1,811 additions
and
549 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,11 @@ | ||
APP_BUILDER_API=\Aphiria\Framework\Api\SynchronousApiApplicationBuilder | ||
APP_BUILDER_CONSOLE=\Aphiria\Framework\Console\ConsoleApplicationBuilder | ||
APP_COOKIE_DOMAIN= | ||
APP_COOKIE_SECURE=0 | ||
APP_ENV=development | ||
APP_URL=http://localhost:8080 | ||
DB_DRIVER=postgres | ||
DB_HOST=localhost | ||
DB_USER=myuser | ||
DB_PASSWORD=mypassword | ||
DB_NAME=public | ||
DB_PORT=5432 | ||
DB_PATH=/database/database.sqlite | ||
LOG_LEVEL=debug | ||
USER_DEFAULT_EMAIL=admin@example.com | ||
USER_DEFAULT_PASSWORD=abc123 |
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ | |
/.phpunit.result.cache | ||
/composer.lock | ||
/composer.phar | ||
/database/database.sqlite | ||
/nbproject/ | ||
/phpunit.phar | ||
/vendor/ |
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
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
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
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
Empty file.
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Auth\Api\Controllers; | ||
|
||
use Aphiria\Api\Controllers\Controller; | ||
use Aphiria\Authentication\Attributes\Authenticate; | ||
use Aphiria\Authentication\AuthenticationSchemeNotFoundException; | ||
use Aphiria\Authentication\IAuthenticator; | ||
use Aphiria\Authentication\NotAuthenticatedException; | ||
use Aphiria\Authentication\UnsupportedAuthenticationHandlerException; | ||
use Aphiria\Net\Http\IResponse; | ||
use Aphiria\Net\Http\Response; | ||
use Aphiria\Routing\Attributes\Post; | ||
use Aphiria\Routing\Attributes\RouteGroup; | ||
|
||
/** | ||
* Defines the auth controller | ||
*/ | ||
#[RouteGroup('/auth')] | ||
final class AuthController extends Controller | ||
{ | ||
/** | ||
* @param IAuthenticator $authenticator The authenticator | ||
*/ | ||
public function __construct(private readonly IAuthenticator $authenticator) | ||
{ | ||
} | ||
|
||
/** | ||
* Attempts to log in a user with basic auth and sets an auth token cookie on success | ||
* | ||
* @return IResponse The login attempt response | ||
* @throws NotAuthenticatedException|AuthenticationSchemeNotFoundException|UnsupportedAuthenticationHandlerException Thrown if there was an error with authentication | ||
*/ | ||
#[Post('/login'), Authenticate('basic')] | ||
public function logIn(): IResponse | ||
{ | ||
// We authenticate via basic auth, and then log in using cookies for future requests | ||
$response = new Response(); | ||
/** @psalm-suppress PossiblyNullArgument The user will be set by the basic auth handler */ | ||
$this->authenticator->logIn($this->getUser(), $this->request, $response, 'cookie'); | ||
|
||
return $response; | ||
} | ||
|
||
/** | ||
* Logs out the user | ||
* | ||
* @return IResponse The logout response | ||
* @throws AuthenticationSchemeNotFoundException|UnsupportedAuthenticationHandlerException Thrown if there was an issue with authentication | ||
*/ | ||
#[Post('/logout')] | ||
public function logOut(): IResponse | ||
{ | ||
$response = new Response(); | ||
/** @psalm-suppress PossiblyNullArgument The request will be set */ | ||
$this->authenticator->logOut($this->request, $response, 'cookie'); | ||
|
||
return $response; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Auth; | ||
|
||
use Aphiria\Application\IApplicationBuilder; | ||
use Aphiria\Authentication\AuthenticationScheme; | ||
use Aphiria\Authentication\Schemes\BasicAuthenticationOptions; | ||
use Aphiria\Authentication\Schemes\CookieAuthenticationOptions; | ||
use Aphiria\Authorization\AuthorizationPolicy; | ||
use Aphiria\Authorization\RequirementHandlers\RolesRequirement; | ||
use Aphiria\Authorization\RequirementHandlers\RolesRequirementHandler; | ||
use Aphiria\Framework\Application\AphiriaModule; | ||
use Aphiria\Net\Http\Headers\SameSiteMode; | ||
use Aphiria\Net\Http\HttpStatusCode; | ||
use App\Auth\Binders\AuthServiceBinder; | ||
use App\Database\Components\DatabaseComponents; | ||
|
||
/** | ||
* Defines the auth module | ||
*/ | ||
final class AuthModule extends AphiriaModule | ||
{ | ||
use DatabaseComponents; | ||
|
||
/** | ||
* @inheritdoc | ||
*/ | ||
public function configure(IApplicationBuilder $appBuilder): void | ||
{ | ||
$this->withBinders($appBuilder, new AuthServiceBinder()) | ||
->withDatabaseSeeders($appBuilder, SqlTokenSeeder::class) | ||
// Add our default authentication scheme | ||
->withAuthenticationScheme( | ||
$appBuilder, | ||
new AuthenticationScheme( | ||
'cookie', | ||
CookieAuthenticationHandler::class, | ||
new CookieAuthenticationOptions( | ||
cookieName: 'authToken', | ||
cookieMaxAge: 3600, | ||
cookiePath: '/', | ||
cookieDomain: (string)\getenv('APP_COOKIE_DOMAIN'), | ||
cookieIsSecure: (bool)\getenv('APP_COOKIE_SECURE'), | ||
cookieIsHttpOnly: true, | ||
cookieSameSite: SameSiteMode::Strict, | ||
loginPagePath: '/login', | ||
forbiddenPagePath: '/access-denied', | ||
claimsIssuer: (string)\getenv('APP_COOKIE_DOMAIN') | ||
) | ||
), | ||
true | ||
) | ||
->withAuthenticationScheme( | ||
$appBuilder, | ||
new AuthenticationScheme( | ||
'basic', | ||
BasicAuthenticationHandler::class, | ||
new BasicAuthenticationOptions((string)\getenv('APP_URL')) | ||
) | ||
) | ||
->withAuthorizationRequirementHandler( | ||
$appBuilder, | ||
AuthorizedUserDeleterRequirement::class, | ||
new AuthorizedUserDeleterRequirementHandler() | ||
) | ||
->withAuthorizationRequirementHandler( | ||
$appBuilder, | ||
RolesRequirement::class, | ||
new RolesRequirementHandler() | ||
) | ||
->withAuthorizationPolicy( | ||
$appBuilder, | ||
new AuthorizationPolicy( | ||
'authorized-user-deleter', | ||
new AuthorizedUserDeleterRequirement('admin') | ||
) | ||
) | ||
->withProblemDetails( | ||
$appBuilder, | ||
InvalidCredentialsException::class, | ||
status: HttpStatusCode::BadRequest | ||
); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Auth; | ||
|
||
/** | ||
* Defines the requirement for users to delete other users' accounts | ||
*/ | ||
final readonly class AuthorizedUserDeleterRequirement | ||
{ | ||
/** @var list<string> The list of roles authorized to delete other users' accounts */ | ||
public array $authorizedRoles; | ||
|
||
/** | ||
* @param list<string>|string $authorizedRoles The role or list of roles that are authorized to delete other users' accounts | ||
*/ | ||
public function __construct(array|string $authorizedRoles) | ||
{ | ||
if (!\is_array($authorizedRoles)) { | ||
$authorizedRoles = [$authorizedRoles]; | ||
} | ||
|
||
$this->authorizedRoles = $authorizedRoles; | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Auth; | ||
|
||
use Aphiria\Authorization\AuthorizationContext; | ||
use Aphiria\Authorization\IAuthorizationRequirementHandler; | ||
use Aphiria\Security\ClaimType; | ||
use Aphiria\Security\IPrincipal; | ||
use App\Users\User; | ||
use InvalidArgumentException; | ||
|
||
/** | ||
* Defines the requirement handler that checks if a user is authorized to delete another user's account | ||
* | ||
* @implements IAuthorizationRequirementHandler<AuthorizedUserDeleterRequirement, User> | ||
*/ | ||
final class AuthorizedUserDeleterRequirementHandler implements IAuthorizationRequirementHandler | ||
{ | ||
/** | ||
* @inheritdoc | ||
*/ | ||
public function handle(IPrincipal $user, object $requirement, AuthorizationContext $authorizationContext): void | ||
{ | ||
if (!$requirement instanceof AuthorizedUserDeleterRequirement) { | ||
throw new InvalidArgumentException('Requirement must be of type ' . AuthorizedUserDeleterRequirement::class); | ||
} | ||
|
||
$userToDelete = $authorizationContext->resource; | ||
|
||
if (!$userToDelete instanceof User) { | ||
throw new InvalidArgumentException('Resource must be of type ' . User::class); | ||
} | ||
|
||
if ($userToDelete->id === (int)$user->getPrimaryIdentity()?->getNameIdentifier()) { | ||
// The user being deleted is the current user | ||
$authorizationContext->requirementPassed($requirement); | ||
|
||
return; | ||
} | ||
|
||
foreach ($requirement->authorizedRoles as $authorizedRole) { | ||
if ($user->hasClaim(ClaimType::Role, $authorizedRole)) { | ||
// The user is authorized to delete the user's account | ||
$authorizationContext->requirementPassed($requirement); | ||
|
||
return; | ||
} | ||
} | ||
|
||
$authorizationContext->fail(); | ||
} | ||
} |
Oops, something went wrong.