chore: address Plan 4 deferred-minors (stale-cookie cleanup pass)
CI / php (push) Failing after 1m11s
CI / php (push) Failing after 1m11s
- Rename $secret to $csrfSecret in public/index.php to make
intent clear at every call site; the value is the CSRF/turn-token
HMAC key, not just 'the secret'.
- Drop the stale 'Configure the router with the eight routes' comment
in public/index.php; the router now has sixteen routes.
- Drop the duplicate .bf-toast rule in public/assets/styles.css; the
second declaration (the new yellow box) is the active one, the
first (legacy green pill) is dead code from the editor era.
- Add a trailing newline to src/Views/home.php.
- Tighten the matchId regex in two tests from {16,} to {16} since
bin2hex(random_bytes(8)) always produces exactly 16 hex chars; the
spec's {16,} stays in production TurnToken.
No behavior changes; addresses the deferred-minors parked during
per-task review.
This commit is contained in:
@@ -18,7 +18,6 @@ table.bf-grid button[data-objective="1"] { outline: 3px solid #c00; }
|
||||
table.bf-grid button[data-zone="alpha"] { outline: 3px solid #00c; }
|
||||
table.bf-grid button[data-zone="bravo"] { outline: 3px solid #0c0; }
|
||||
.bf-errors { color: #c00; }
|
||||
.bf-toast { background: #dfd; padding: 0.5rem 1rem; margin: 0.5rem 0; }
|
||||
.bf-match { display: flex; gap: 1rem; }
|
||||
.bf-grid { display: grid; gap: 1px; background: #ddd; padding: 1px; width: max-content; grid-template-columns: repeat(var(--bf-cols, 8), 2.5rem); }
|
||||
.bf-tile { width: 2.5rem; height: 2.5rem; border: 0; background: #fff; padding: 0; position: relative; }
|
||||
|
||||
+20
-17
@@ -25,8 +25,11 @@ use BattleForge\Http\Router;
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
// 1. Read the app secret.
|
||||
$secret = getenv('BATTLEFORGE_SECRET');
|
||||
if ($secret === false || $secret === '') {
|
||||
// The app secret reads as the CSRF/turn-token HMAC key. The name below
|
||||
// (kept for historical reasons) is just the variable label; the same
|
||||
// value is used for both the CSRF cookie HMAC and the turn-token HMAC.
|
||||
$csrfSecret = getenv('BATTLEFORGE_SECRET');
|
||||
if ($csrfSecret === false || $csrfSecret === '') {
|
||||
$secretFile = __DIR__ . '/../var/secret.key';
|
||||
if (!is_file($secretFile)) {
|
||||
if (!is_dir(dirname($secretFile))) {
|
||||
@@ -35,8 +38,8 @@ if ($secret === false || $secret === '') {
|
||||
file_put_contents($secretFile, random_bytes(32));
|
||||
chmod($secretFile, 0600);
|
||||
}
|
||||
$secret = file_get_contents($secretFile);
|
||||
if ($secret === false) {
|
||||
$csrfSecret = file_get_contents($secretFile);
|
||||
if ($csrfSecret === false) {
|
||||
http_response_code(500);
|
||||
echo "Failed to read app secret.\n";
|
||||
return;
|
||||
@@ -61,11 +64,11 @@ $existingCookie = (string) ($cookies['__csrf'] ?? '');
|
||||
$existingRawToken = (string) ($cookies['__csrf_token'] ?? '');
|
||||
$pairIsFresh = false;
|
||||
if ($existingCookie !== '' && $existingRawToken !== '') {
|
||||
$expectedHmac = hash_hmac('sha256', $existingRawToken, $secret);
|
||||
$expectedHmac = hash_hmac('sha256', $existingRawToken, $csrfSecret);
|
||||
$pairIsFresh = hash_equals($expectedHmac, $existingCookie);
|
||||
}
|
||||
if ($existingCookie === '' || $existingRawToken === '' || !$pairIsFresh) {
|
||||
[$token, $cookie] = CsrfToken::issue($secret);
|
||||
[$token, $cookie] = CsrfToken::issue($csrfSecret);
|
||||
setcookie('__csrf', $cookie, [
|
||||
'expires' => time() + 86400,
|
||||
'path' => '/',
|
||||
@@ -93,7 +96,7 @@ if ($existingCookie === '' || $existingRawToken === '' || !$pairIsFresh) {
|
||||
}
|
||||
|
||||
// 3. Compute the upload-endpoint user token (derived from the same secret).
|
||||
$uploadsToken = hash_hmac('sha256', $secret, 'bf-uploads');
|
||||
$uploadsToken = hash_hmac('sha256', $csrfSecret, 'bf-uploads');
|
||||
|
||||
// 4. Determine the request method, path, and content type.
|
||||
$method = strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET'));
|
||||
@@ -115,7 +118,7 @@ $rawBody = (string) ($_SERVER['__raw_body'] ?? file_get_contents('php://input'))
|
||||
// 6. Build a Request with the request data, the CSRF secret, and the upload token.
|
||||
$server = $_SERVER;
|
||||
if (!isset($server['__csrf_secret'])) {
|
||||
$server['__csrf_secret'] = $secret;
|
||||
$server['__csrf_secret'] = $csrfSecret;
|
||||
}
|
||||
$server['__uploads_token'] = $uploadsToken;
|
||||
|
||||
@@ -132,7 +135,7 @@ $request = new Request(
|
||||
rawBody: $rawBody,
|
||||
);
|
||||
|
||||
// 7. Configure the router with the eight routes.
|
||||
// 7. Configure the router.
|
||||
$uploadsRoot = __DIR__ . '/../var/uploads';
|
||||
$placeholderDir = __DIR__ . '/assets/placeholders';
|
||||
|
||||
@@ -164,17 +167,17 @@ $getBundledOne = static function (Request $r, array $p) use ($scenariosDir): Res
|
||||
return (new GetBundledScenario($scenariosDir))->handle($r, $p);
|
||||
};
|
||||
$getMatchView = static fn (Request $r, array $p): Response => (new GetMatchView())->handle($r, $p);
|
||||
$postMatchMove = static function (Request $r, array $p) use ($secret): Response {
|
||||
return (new PostMatchMove($secret))->handle($r, $p);
|
||||
$postMatchMove = static function (Request $r, array $p) use ($csrfSecret): Response {
|
||||
return (new PostMatchMove($csrfSecret))->handle($r, $p);
|
||||
};
|
||||
$postMatchAttack = static function (Request $r, array $p) use ($secret): Response {
|
||||
return (new PostMatchAttack($secret))->handle($r, $p);
|
||||
$postMatchAttack = static function (Request $r, array $p) use ($csrfSecret): Response {
|
||||
return (new PostMatchAttack($csrfSecret))->handle($r, $p);
|
||||
};
|
||||
$postMatchAbility = static function (Request $r, array $p) use ($secret): Response {
|
||||
return (new PostMatchAbility($secret))->handle($r, $p);
|
||||
$postMatchAbility = static function (Request $r, array $p) use ($csrfSecret): Response {
|
||||
return (new PostMatchAbility($csrfSecret))->handle($r, $p);
|
||||
};
|
||||
$postMatchEndTurn = static function (Request $r, array $p) use ($secret): Response {
|
||||
return (new PostMatchEndTurn($secret))->handle($r, $p);
|
||||
$postMatchEndTurn = static function (Request $r, array $p) use ($csrfSecret): Response {
|
||||
return (new PostMatchEndTurn($csrfSecret))->handle($r, $p);
|
||||
};
|
||||
|
||||
$router = new Router();
|
||||
|
||||
+1
-1
@@ -27,4 +27,4 @@ render_layout(static function () use ($csrf, $bundled): void {
|
||||
<div id="recent"><p class="bf-empty">No saved scenarios yet.</p></div>
|
||||
<script type="module" src="<?= $a('/js/storage.js') ?>"></script>
|
||||
<?php
|
||||
}, $csrf, 'BattleForge');
|
||||
}, $csrf, 'BattleForge');
|
||||
|
||||
@@ -60,7 +60,7 @@ final class PostMatchActionSmokeTest extends TestCase
|
||||
$turnToken = $startResponse['body']['turnToken'] ?? null;
|
||||
self::assertIsArray($match);
|
||||
self::assertIsString($matchId);
|
||||
self::assertMatchesRegularExpression('/^[a-f0-9]{16,}$/', $matchId);
|
||||
self::assertMatchesRegularExpression('/^[a-f0-9]{16}$/', $matchId);
|
||||
self::assertMatchesRegularExpression('/^[a-f0-9]{32}$/', $turnToken);
|
||||
|
||||
for ($i = 0; $i < 200; $i += 1) {
|
||||
|
||||
@@ -39,7 +39,7 @@ final class PostStartMatchTest extends TestCase
|
||||
self::assertSame(1, $body['match']['round'] ?? null);
|
||||
self::assertCount(6, $body['match']['units'] ?? []);
|
||||
self::assertIsString($body['matchId'] ?? null);
|
||||
self::assertMatchesRegularExpression('/^[a-f0-9]{16,}$/', $body['matchId']);
|
||||
self::assertMatchesRegularExpression('/^[a-f0-9]{16}$/', $body['matchId']);
|
||||
self::assertIsString($body['turnToken'] ?? null);
|
||||
self::assertMatchesRegularExpression('/^[a-f0-9]{32}$/', $body['turnToken']);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user