seedproject-web/api/cli/set-admin-token.php
Carlos Arias b967419717 Phase 1: admin login page + secret-link token auth
- /admin login page (public, noindex): password form + signed-in panel that
  activates the in-page console. Verified in-browser: login -> panel -> the
  astroagent handle appears on other pages.
- Secret-link login: visit /admin/<token> to sign in without a password.
  adminauth token() validates a bcrypt-hashed token, sets the session, 302s to
  /admin. nginx routes the token path to PHP. set-admin-token.php generates it.
- Password login kept as a fallback.

Verified: token link -> session -> console access; bad token -> /admin?e=1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DoFYZY9gkGPNDqZ7NuEa9a
2026-07-23 22:15:50 +00:00

33 lines
1.1 KiB
PHP

<?php
/**
* Generate a secret-link login token for an admin. Run it yourself:
*
* php api/cli/set-admin-token.php <username>
*
* Prints the token and the full login URL ONCE — it is not recoverable
* afterwards (only its hash is stored). Re-run to rotate.
*/
require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/../config.php';
$username = $argv[1] ?? '';
if ($username === '') {
fwrite(STDERR, "usage: php api/cli/set-admin-token.php <username>\n");
exit(1);
}
$id = Db::getValue('SELECT admin_id FROM cja_admin WHERE username = ?', [$username]);
if (!$id) {
fwrite(STDERR, "No admin '{$username}'. Create one first with set-admin-password.php\n");
exit(1);
}
// URL-safe, 43 chars of entropy.
$token = rtrim(strtr(base64_encode(random_bytes(32)), '+/', '-_'), '=');
Db::update('cja_admin', ['token_hash' => password_hash($token, PASSWORD_DEFAULT)], 'admin_id = ?', [$id]);
$base = defined('SITE_URL') ? SITE_URL : 'https://carlosarias.co';
echo "\n Admin login link (save it — shown only once):\n\n";
echo " {$base}/admin/{$token}\n\n";
echo " Rotate any time by re-running this. The old link stops working.\n";