Skip to content
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ The format is based on Keep a Changelog.
- Added `Response::back()` and the `back()` helper, which redirect to the referrer and fall back to `base_url()` when the request has none, or when the referrer is not on the application origin (same scheme, host and port), which is compared with `base_url()`: behind a proxy that terminates TLS, or where the `Host` header is not pinned, set `app.base_url`. They take the old input to keep for the next request, read with `old()` as after `redirectWith()`, and an optional status code (`back($data, $code)`) (#540)

### Changed
- **BREAKING:** Converted the constant classes `ReservedKeys`, `AuthKeys`, `Pagination` and the database `Type`, `Key` and `Relation` to PHP enums, and moved the table actions of `Table` into the new `TableAction` enum (#612). An enum case is not its value, so code that needs the text or number reads `->value`, and text that comes from outside is turned into a case with `::from()` or `::tryFrom()`:
- `ReservedKeys`, `AuthKeys`, `Pagination`, `Type`, `Key` and `Relation` are string-backed enums with the same case names and values, so `Relation::BELONGS_TO`, `Type::INT` and `Key::INDEX` are written as before. An application that uses a case as an array key, compares it with text, or passes it to a `string` parameter must add `->value`, for example `$this->keyFields[AuthKeys::USERNAME->value]`. Code that reads the constants with `ReflectionClass::getConstants()` uses `AuthKeys::cases()`.
- `Table::addIndex()` takes `Key` and `Table::setAction()` takes `TableAction`, and no longer accept text: `addIndex('name', 'unique')` throws a `TypeError`; write `Key::UNIQUE`. A subclass of `Table` that overrides these methods must declare the enum types.
- `Table::addColumn()`, `Table::modifyColumn()` and `Column::__construct()` accept a `Type` case or the type as text. A text type is used as before, in upper case, so any SQL type name still works (`'integer'`, `'bigint'`, `'mediumtext'`), including those that `Type` has no case for. A subclass that overrides them must declare `Type|string`.
- `Table::CREATE`, `Table::ALTER`, `Table::DROP` and `Table::RENAME` are removed: use `TableAction::CREATE`, `TableAction::ALTER`, `TableAction::DROP` and `TableAction::RENAME`.
- The `type` of a model relation must be a `Relation` case. The text `'hasOne'` is rejected with the message "Relation type `hasOne` is not supported"; write `Relation::HAS_ONE`. `ModelException::unsupportedRelationType()` accepts a `Relation` or text.
- `Pagination` keeps the four names (`PAGINATION_CLASS`, `PAGINATION_CLASS_ACTIVE`, `PER_PAGE`, `PAGE`) as a string-backed enum, read with `->value`. The numbers `FIRST_PAGE_NUMBER`, `MINIMUM_PAGE_ITEMS_COUNT` and `EDGE_PADDING` moved to `PaginationLimit`, an enum that holds them as constants, read without `->value`.
- The adapter types, `StatusCode`, `ContentType`, `Env`, `ExitCode` and the `ExceptionMessages` classes stay constant classes.
- The `DemoWeb` and `Toolkit` module templates redirect back with `back()` instead of `redirect(get_referrer() ?? base_url())` and `redirectWith(get_referrer() ?? base_url(), $data)`, so generated code gets the same-origin check. Modules generated before keep their code (#541)
- Twig is a required dependency again: `twig/twig` `^3.27` is in `require` and no longer only suggested, which reverses the 3.0.3 change that made it optional while the framework supported PHP 8.0 (#532). Twig `^3.27` needs PHP 8.1, the minimum since 3.1.0. An application that pins an older Twig must allow `^3.27`. Removed `RendererException::twigNotInstalled()` and the check in `TwigAdapter` that threw it when Twig was missing, and the Twig tests no longer skip (#626)
- The test suite runs on PHPUnit `^10.5` (a development dependency) and `phpunit.xml` uses its schema. It now sets `error_reporting` to `-1`: it was the text `E_ALL & ~E_DEPRECATED`, which PHP reads as `0`, so every PHP warning was hidden in the tests (#622)
Expand Down
9 changes: 4 additions & 5 deletions src/App/Enums/ReservedKeys.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,18 @@
namespace Quantum\App\Enums;

/**
* Class ReservedKeys
* Enum ReservedKeys
* @package Quantum\App
*/
final class ReservedKeys
enum ReservedKeys: string
{
/**
* Internal response key for rendered view
*/
public const RENDERED_VIEW = '_qt_rendered_view';
case RENDERED_VIEW = '_qt_rendered_view';

/**
* Internal session key for previous request
*/
public const PREV_REQUEST = '_qt_prev_request';

case PREV_REQUEST = '_qt_prev_request';
}
30 changes: 15 additions & 15 deletions src/Auth/Adapters/JwtAuthAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,18 @@ public function signin(string $username, string $password): string|array
*/
public function signout(): bool
{
$refreshToken = request()->getHeader($this->keyFields[AuthKeys::REFRESH_TOKEN]);
$refreshToken = request()->getHeader($this->keyFields[AuthKeys::REFRESH_TOKEN->value]);

$user = $this->authService->get($this->keyFields[AuthKeys::REFRESH_TOKEN], $refreshToken);
$user = $this->authService->get($this->keyFields[AuthKeys::REFRESH_TOKEN->value], $refreshToken);

if ($user) {
$this->authService->update(
$this->keyFields[AuthKeys::REFRESH_TOKEN],
$this->keyFields[AuthKeys::REFRESH_TOKEN->value],
$refreshToken,
array_merge($this->getVisibleFields($user), [$this->keyFields[AuthKeys::REFRESH_TOKEN] => ''])
array_merge($this->getVisibleFields($user), [$this->keyFields[AuthKeys::REFRESH_TOKEN->value] => ''])
);

request()->deleteHeader($this->keyFields[AuthKeys::REFRESH_TOKEN]);
request()->deleteHeader($this->keyFields[AuthKeys::REFRESH_TOKEN->value]);
request()->deleteHeader('Authorization');
response()->delete('tokens');

Expand Down Expand Up @@ -142,8 +142,8 @@ protected function getUpdatedTokens(User $user): array
{

return [
$this->keyFields[AuthKeys::REFRESH_TOKEN] => $this->generateToken(),
$this->keyFields[AuthKeys::ACCESS_TOKEN] => base64_encode($this->jwt->setData($this->getVisibleFields($user))->compose()),
$this->keyFields[AuthKeys::REFRESH_TOKEN->value] => $this->generateToken(),
$this->keyFields[AuthKeys::ACCESS_TOKEN->value] => base64_encode($this->jwt->setData($this->getVisibleFields($user))->compose()),
];
}

Expand All @@ -157,13 +157,13 @@ protected function setUpdatedTokens(User $user): array
$tokens = $this->getUpdatedTokens($user);

$this->authService->update(
$this->keyFields[AuthKeys::USERNAME],
$user->getFieldValue($this->keyFields[AuthKeys::USERNAME]),
array_merge($this->getVisibleFields($user), [$this->keyFields[AuthKeys::REFRESH_TOKEN] => $tokens[$this->keyFields[AuthKeys::REFRESH_TOKEN]]])
$this->keyFields[AuthKeys::USERNAME->value],
$user->getFieldValue($this->keyFields[AuthKeys::USERNAME->value]),
array_merge($this->getVisibleFields($user), [$this->keyFields[AuthKeys::REFRESH_TOKEN->value] => $tokens[$this->keyFields[AuthKeys::REFRESH_TOKEN->value]]])
);

request()->setHeader($this->keyFields[AuthKeys::REFRESH_TOKEN], $tokens[$this->keyFields[AuthKeys::REFRESH_TOKEN]]);
request()->setHeader('Authorization', 'Bearer ' . $tokens[$this->keyFields[AuthKeys::ACCESS_TOKEN]]);
request()->setHeader($this->keyFields[AuthKeys::REFRESH_TOKEN->value], $tokens[$this->keyFields[AuthKeys::REFRESH_TOKEN->value]]);
request()->setHeader('Authorization', 'Bearer ' . $tokens[$this->keyFields[AuthKeys::ACCESS_TOKEN->value]]);
response()->set('tokens', $tokens);

return $tokens;
Expand All @@ -175,8 +175,8 @@ protected function setUpdatedTokens(User $user): array
protected function checkRefreshToken(): ?User
{
return $this->authService->get(
$this->keyFields[AuthKeys::REFRESH_TOKEN],
request()->getHeader($this->keyFields[AuthKeys::REFRESH_TOKEN])
$this->keyFields[AuthKeys::REFRESH_TOKEN->value],
request()->getHeader($this->keyFields[AuthKeys::REFRESH_TOKEN->value])
);
}

Expand All @@ -203,7 +203,7 @@ private function getUserFromAccessToken(): ?User
*/
private function getUserFromRefreshToken(): ?User
{
if (!request()->hasHeader($this->keyFields[AuthKeys::REFRESH_TOKEN])) {
if (!request()->hasHeader($this->keyFields[AuthKeys::REFRESH_TOKEN->value])) {
return null;
}

Expand Down
30 changes: 15 additions & 15 deletions src/Auth/Adapters/SessionAuthAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ public function user(): ?User
return (new User())->setData(session()->get(self::AUTH_USER));
}

if (cookie()->has($this->keyFields[AuthKeys::REMEMBER_TOKEN])) {
if (cookie()->has($this->keyFields[AuthKeys::REMEMBER_TOKEN->value])) {
$user = $this->checkRememberToken();

if ($user) {
Expand Down Expand Up @@ -171,15 +171,15 @@ public function verifyOtp(int $otp, string $otpToken): bool
private function checkRememberToken(): false|\Quantum\Auth\User
{
$user = $this->authService->get(
$this->keyFields[AuthKeys::REMEMBER_TOKEN],
cookie()->get($this->keyFields[AuthKeys::REMEMBER_TOKEN])
$this->keyFields[AuthKeys::REMEMBER_TOKEN->value],
cookie()->get($this->keyFields[AuthKeys::REMEMBER_TOKEN->value])
);

if (!$user) {
return false;
}

if ($this->isTwoFactorEnabled() && !empty($user->getFieldValue($this->keyFields[AuthKeys::OTP_TOKEN]))) {
if ($this->isTwoFactorEnabled() && !empty($user->getFieldValue($this->keyFields[AuthKeys::OTP_TOKEN->value]))) {
return false;
}

Expand All @@ -195,15 +195,15 @@ private function setRememberToken(User $user): void
$rememberToken = $this->generateToken();

$this->authService->update(
$this->keyFields[AuthKeys::USERNAME],
$user->getFieldValue($this->keyFields[AuthKeys::USERNAME]),
[$this->keyFields[AuthKeys::REMEMBER_TOKEN] => $rememberToken]
$this->keyFields[AuthKeys::USERNAME->value],
$user->getFieldValue($this->keyFields[AuthKeys::USERNAME->value]),
[$this->keyFields[AuthKeys::REMEMBER_TOKEN->value] => $rememberToken]
);

$rememberLifetime = (int) ($this->config['session']['remember_lifetime'] ?? self::DEFAULT_REMEMBER_LIFETIME);

cookie()->set(
$this->keyFields[AuthKeys::REMEMBER_TOKEN],
$this->keyFields[AuthKeys::REMEMBER_TOKEN->value],
$rememberToken,
$rememberLifetime,
'/',
Expand All @@ -218,21 +218,21 @@ private function setRememberToken(User $user): void
*/
private function removeRememberToken(): void
{
if (cookie()->has($this->keyFields[AuthKeys::REMEMBER_TOKEN])) {
if (cookie()->has($this->keyFields[AuthKeys::REMEMBER_TOKEN->value])) {
$user = $this->authService->get(
$this->keyFields[AuthKeys::REMEMBER_TOKEN],
cookie()->get($this->keyFields[AuthKeys::REMEMBER_TOKEN])
$this->keyFields[AuthKeys::REMEMBER_TOKEN->value],
cookie()->get($this->keyFields[AuthKeys::REMEMBER_TOKEN->value])
);

if ($user) {
$this->authService->update(
$this->keyFields[AuthKeys::REMEMBER_TOKEN],
$user->getFieldValue($this->keyFields[AuthKeys::REMEMBER_TOKEN]),
[$this->keyFields[AuthKeys::REMEMBER_TOKEN] => '']
$this->keyFields[AuthKeys::REMEMBER_TOKEN->value],
$user->getFieldValue($this->keyFields[AuthKeys::REMEMBER_TOKEN->value]),
[$this->keyFields[AuthKeys::REMEMBER_TOKEN->value] => '']
);
}

cookie()->delete($this->keyFields[AuthKeys::REMEMBER_TOKEN]);
cookie()->delete($this->keyFields[AuthKeys::REMEMBER_TOKEN->value]);
}
}
}
28 changes: 12 additions & 16 deletions src/Auth/Enums/AuthKeys.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,63 +11,59 @@
namespace Quantum\Auth\Enums;

/**
* Class AuthKeys
* Enum AuthKeys
* @package Quantum\Auth
* @codeCoverageIgnore
*/
final class AuthKeys
enum AuthKeys: string
{
/**
* One time password key
*/
public const OTP = 'otp';
case OTP = 'otp';

/**
* One time password expiry key
*/
public const OTP_EXPIRY = 'otpExpiry';
case OTP_EXPIRY = 'otpExpiry';

/**
* One time password token key
*/
public const OTP_TOKEN = 'otpToken';
case OTP_TOKEN = 'otpToken';

/**
* Username key
*/
public const USERNAME = 'username';
case USERNAME = 'username';

/**
* Password key
*/
public const PASSWORD = 'password';
case PASSWORD = 'password';

/**
* Access token key
*/
public const ACCESS_TOKEN = 'accessToken';
case ACCESS_TOKEN = 'accessToken';

/**
* Refresh token key
*/
public const REFRESH_TOKEN = 'refreshToken';
case REFRESH_TOKEN = 'refreshToken';

/**
* Activation token key
*/
public const ACTIVATION_TOKEN = 'activationToken';
case ACTIVATION_TOKEN = 'activationToken';

/**
* Reset token key
*/
public const RESET_TOKEN = 'resetToken';
case RESET_TOKEN = 'resetToken';

/**
* Remember token key
*/
public const REMEMBER_TOKEN = 'rememberToken';

private function __construct()
{
}
case REMEMBER_TOKEN = 'rememberToken';
}
Loading
Loading