Sync GitHub / sync (push) Successful in 8s
Code quality: - Create AppConstants class with shared constants: - FAR_FUTURE_DATE (replaces duplicated '2999-12-31' strings) - MAX_INPUT_LENGTH (replaces duplicated 128 in Payload and StringTrait) - Extract duplicated 'hi $id' response body into StringTrait::authSuccessResponse() method, used by AcceptListener, AllowListener, and LoginManager - Add missing @throws InvalidArgumentException annotations to MonitorCacheKeys (getItem, hasItem, deleteItem, deleteItems, commit) Configuration: - Add proper env var type casting in services.yaml: - COOKIE_TTL → env(int:) - SUBDOMAIN_REDIRECT → env(bool:) - IP_TTL → env(int:) - TEAPOT → env(bool:) Documentation: - Create CONTRIBUTING.md with development setup, code style, testing guidelines, and PR process
87 lines
2.8 KiB
PHP
87 lines
2.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Data;
|
|
|
|
use App\AppConstants;
|
|
use App\Enum\Scope;
|
|
use Symfony\Component\HttpFoundation\InputBag;
|
|
|
|
/** when scope is IP but ip-access is disabled, scope is to be considered cookie */
|
|
final class Payload
|
|
{
|
|
public string $id; /* session name, identifying who is logging in */
|
|
public string $token; /* TOTP, typically six digits */
|
|
public string $nonce; /* random unique string, to block duplicate submissions */
|
|
public bool $json; /* should we return json (for the login page) */
|
|
public Scope $scope; /* type of access being requested */
|
|
|
|
public static function decode(string $base64url): ?Payload
|
|
{
|
|
/* convert the base64url into json string */
|
|
$base64 = strtr($base64url, '-_', '+/');
|
|
$base64 .= str_repeat('=', (4 - strlen($base64) % 4) % 4);
|
|
$json = base64_decode($base64, true);
|
|
if ($json) {
|
|
/* convert the json string into real data */
|
|
$data = json_decode($json);
|
|
if (is_object($data)) {
|
|
return Payload::create($data);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static function load(InputBag $input): ?Payload
|
|
{
|
|
/* convert form data into real data */
|
|
if ($input->has('username') && $input->has('nonce') && $input->has('totp')) {
|
|
return Payload::create((object)[
|
|
'id' => $input->get('username'),
|
|
'nonce' => $input->get('nonce'),
|
|
'token' => $input->get('totp'),
|
|
'json' => false,
|
|
]);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static function create(object $data): ?Payload
|
|
{
|
|
/* if missing required fields id, nonce, or token */
|
|
if (strlen(trim($data->id ?? '')) < 1 ||
|
|
strlen(trim($data->nonce ?? '')) < 1 ||
|
|
strlen(trim($data->token ?? '')) < 1
|
|
) {
|
|
/* returns null as the input is invalid */
|
|
return null;
|
|
}
|
|
|
|
/* all input is limited */
|
|
$payload = new Payload();
|
|
$payload->id = mb_substr(trim($data->id), 0, AppConstants::MAX_INPUT_LENGTH);
|
|
$payload->nonce = mb_substr(trim($data->nonce), 0, AppConstants::MAX_INPUT_LENGTH);
|
|
$payload->json = ($data->json ?? true);
|
|
$payload->scope = Scope::tryFrom($data->scope ?? '') ?? Scope::Cookie;
|
|
$payload->token = mb_substr(trim($data->token), 0, AppConstants::MAX_INPUT_LENGTH);
|
|
|
|
return Payload::constrict($payload);
|
|
}
|
|
|
|
public function toString(): string
|
|
{
|
|
return json_encode($this);
|
|
}
|
|
|
|
private static function constrict(Payload $payload): Payload
|
|
{
|
|
/* When scope is None, json will be considered false. */
|
|
if ($payload->scope === Scope::None) {
|
|
$payload->json = false;
|
|
}
|
|
|
|
return $payload;
|
|
}
|
|
}
|