feat(demo): add story 1 — Sorano: Rock and Time
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Grav\Plugin\Console;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\User\Interfaces\UserCollectionInterface;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use Grav\Plugin\Api\Auth\ApiKeyManager;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class KeysGenerateCommand extends ConsoleCommand
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName('keys:generate')
|
||||
->setAliases(['keys:gen', 'keys:create'])
|
||||
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'The username to generate the key for')
|
||||
->addOption('name', 'N', InputOption::VALUE_OPTIONAL, 'A name/label for the API key', 'CLI Generated Key')
|
||||
->addOption('expiry', 'e', InputOption::VALUE_OPTIONAL, 'Key expiry in days (default: never expires)')
|
||||
->setDescription('Generate a new API key for a user')
|
||||
->setHelp('The <info>keys:generate</info> command creates a new API key for the specified user.');
|
||||
}
|
||||
|
||||
protected function serve(): int
|
||||
{
|
||||
include __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$io = new SymfonyStyle($this->input, $this->output);
|
||||
$grav = Grav::instance();
|
||||
|
||||
$this->initializePlugins();
|
||||
|
||||
/** @var UserCollectionInterface $accounts */
|
||||
$accounts = $grav['accounts'];
|
||||
|
||||
// Get username
|
||||
$username = $this->input->getOption('user');
|
||||
if (!$username) {
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new Question('Enter the <yellow>username</yellow>: ');
|
||||
$question->setValidator(function ($value) use ($accounts) {
|
||||
if (!$value) {
|
||||
throw new \RuntimeException('Username is required.');
|
||||
}
|
||||
$user = $accounts->load($value);
|
||||
if (!$user->exists()) {
|
||||
throw new \RuntimeException("User '{$value}' does not exist.");
|
||||
}
|
||||
return $value;
|
||||
});
|
||||
$username = $helper->ask($this->input, $this->output, $question);
|
||||
}
|
||||
|
||||
$user = $accounts->load($username);
|
||||
if (!$user->exists()) {
|
||||
$io->error("User '{$username}' does not exist.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$name = $this->input->getOption('name');
|
||||
$expiryDays = $this->input->getOption('expiry') !== null ? (int) $this->input->getOption('expiry') : null;
|
||||
|
||||
$manager = new ApiKeyManager();
|
||||
$result = $manager->generateKey($user, $name, [], $expiryDays);
|
||||
|
||||
$io->newLine();
|
||||
$io->success("API key generated for user '{$username}'");
|
||||
$io->newLine();
|
||||
|
||||
$io->writeln('<yellow>API Key:</yellow> <cyan>' . $result['key'] . '</cyan>');
|
||||
$io->writeln('<yellow>Key ID:</yellow> ' . $result['id']);
|
||||
if ($expiryDays) {
|
||||
$io->writeln('<yellow>Expires:</yellow> ' . date('Y-m-d H:i', time() + ($expiryDays * 86400)));
|
||||
} else {
|
||||
$io->writeln('<yellow>Expires:</yellow> Never');
|
||||
}
|
||||
$io->newLine();
|
||||
|
||||
$io->warning('Save this key now — it cannot be retrieved later.');
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Grav\Plugin\Console;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\User\Interfaces\UserCollectionInterface;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use Grav\Plugin\Api\Auth\ApiKeyManager;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class KeysListCommand extends ConsoleCommand
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName('keys:list')
|
||||
->setAliases(['keys:ls'])
|
||||
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'The username to list keys for')
|
||||
->setDescription('List API keys for a user')
|
||||
->setHelp('The <info>keys:list</info> command shows all API keys for the specified user.');
|
||||
}
|
||||
|
||||
protected function serve(): int
|
||||
{
|
||||
include __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$io = new SymfonyStyle($this->input, $this->output);
|
||||
$grav = Grav::instance();
|
||||
|
||||
$this->initializePlugins();
|
||||
|
||||
/** @var UserCollectionInterface $accounts */
|
||||
$accounts = $grav['accounts'];
|
||||
|
||||
// Get username
|
||||
$username = $this->input->getOption('user');
|
||||
if (!$username) {
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new Question('Enter the <yellow>username</yellow>: ');
|
||||
$username = $helper->ask($this->input, $this->output, $question);
|
||||
}
|
||||
|
||||
$user = $accounts->load($username);
|
||||
if (!$user->exists()) {
|
||||
$io->error("User '{$username}' does not exist.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$manager = new ApiKeyManager();
|
||||
$keys = $manager->listKeys($user);
|
||||
|
||||
if (empty($keys)) {
|
||||
$io->writeln("No API keys found for user '<cyan>{$username}</cyan>'.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
$io->writeln("API keys for user '<cyan>{$username}</cyan>':");
|
||||
$io->newLine();
|
||||
|
||||
$rows = [];
|
||||
foreach ($keys as $key) {
|
||||
$expires = 'Never';
|
||||
if ($key['expires']) {
|
||||
$expires = $key['expires'] < time()
|
||||
? '<red>Expired ' . date('Y-m-d', $key['expires']) . '</red>'
|
||||
: date('Y-m-d', $key['expires']);
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
$key['id'],
|
||||
$key['name'],
|
||||
$key['prefix'],
|
||||
$key['active'] ? '<green>Active</green>' : '<red>Inactive</red>',
|
||||
$expires,
|
||||
$key['created'] ? date('Y-m-d H:i', $key['created']) : 'N/A',
|
||||
$key['last_used'] ? date('Y-m-d H:i', $key['last_used']) : 'Never',
|
||||
];
|
||||
}
|
||||
|
||||
$io->table(
|
||||
['ID', 'Name', 'Prefix', 'Status', 'Expires', 'Created', 'Last Used'],
|
||||
$rows
|
||||
);
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Grav\Plugin\Console;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use Grav\Plugin\Api\Auth\ApiKeyManager;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class KeysMigrateCommand extends ConsoleCommand
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName('keys:migrate')
|
||||
->setDescription('Migrate API keys from user accounts to centralized storage')
|
||||
->setHelp('Moves API keys from individual user account YAML files to the centralized user/data/api-keys.yaml file.');
|
||||
}
|
||||
|
||||
protected function serve(): int
|
||||
{
|
||||
include __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$io = new SymfonyStyle($this->input, $this->output);
|
||||
$grav = Grav::instance();
|
||||
|
||||
$this->initializePlugins();
|
||||
|
||||
$manager = new ApiKeyManager();
|
||||
$migrated = $manager->migrateFromAccounts();
|
||||
|
||||
if ($migrated > 0) {
|
||||
$io->success("Migrated {$migrated} API key(s) to user/data/api-keys.yaml");
|
||||
} else {
|
||||
$io->writeln('No API keys found in user accounts to migrate.');
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Grav\Plugin\Console;
|
||||
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Common\User\Interfaces\UserCollectionInterface;
|
||||
use Grav\Console\ConsoleCommand;
|
||||
use Grav\Plugin\Api\Auth\ApiKeyManager;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Question\ChoiceQuestion;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
class KeysRevokeCommand extends ConsoleCommand
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setName('keys:revoke')
|
||||
->setAliases(['keys:remove', 'keys:delete', 'keys:rm'])
|
||||
->addOption('user', 'u', InputOption::VALUE_REQUIRED, 'The username')
|
||||
->addArgument('key-id', InputArgument::OPTIONAL, 'The key ID to revoke')
|
||||
->setDescription('Revoke an API key')
|
||||
->setHelp('The <info>keys:revoke</info> command revokes an API key for the specified user. If no key ID is given, you can select from a list.');
|
||||
}
|
||||
|
||||
protected function serve(): int
|
||||
{
|
||||
include __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$io = new SymfonyStyle($this->input, $this->output);
|
||||
$grav = Grav::instance();
|
||||
|
||||
$this->initializePlugins();
|
||||
|
||||
/** @var UserCollectionInterface $accounts */
|
||||
$accounts = $grav['accounts'];
|
||||
$helper = $this->getHelper('question');
|
||||
|
||||
// Get username
|
||||
$username = $this->input->getOption('user');
|
||||
if (!$username) {
|
||||
$question = new Question('Enter the <yellow>username</yellow>: ');
|
||||
$username = $helper->ask($this->input, $this->output, $question);
|
||||
}
|
||||
|
||||
$user = $accounts->load($username);
|
||||
if (!$user->exists()) {
|
||||
$io->error("User '{$username}' does not exist.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
$manager = new ApiKeyManager();
|
||||
$keys = $manager->listKeys($user);
|
||||
|
||||
if (empty($keys)) {
|
||||
$io->writeln("No API keys found for user '<cyan>{$username}</cyan>'.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get key ID
|
||||
$keyId = $this->input->getArgument('key-id');
|
||||
if (!$keyId) {
|
||||
// Let user pick from a list
|
||||
$choices = [];
|
||||
foreach ($keys as $key) {
|
||||
$choices[$key['id']] = sprintf('%s (%s) - %s', $key['name'], $key['prefix'], $key['id']);
|
||||
}
|
||||
|
||||
$question = new ChoiceQuestion(
|
||||
'Select the key to revoke:',
|
||||
$choices
|
||||
);
|
||||
$selected = $helper->ask($this->input, $this->output, $question);
|
||||
|
||||
// Extract the key ID from the selected choice
|
||||
foreach ($keys as $key) {
|
||||
$label = sprintf('%s (%s) - %s', $key['name'], $key['prefix'], $key['id']);
|
||||
if ($label === $selected) {
|
||||
$keyId = $key['id'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$keyId) {
|
||||
$io->error('No key ID provided.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Find key name for confirmation
|
||||
$keyName = $keyId;
|
||||
foreach ($keys as $key) {
|
||||
if ($key['id'] === $keyId) {
|
||||
$keyName = sprintf('%s (%s)', $key['name'], $key['prefix']);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$confirm = new ConfirmationQuestion(
|
||||
"Revoke key <yellow>{$keyName}</yellow> for user <cyan>{$username}</cyan>? [y/N] ",
|
||||
false
|
||||
);
|
||||
|
||||
if (!$helper->ask($this->input, $this->output, $confirm)) {
|
||||
$io->writeln('Cancelled.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$revoked = $manager->revokeKey($user, $keyId);
|
||||
|
||||
if ($revoked) {
|
||||
$io->success("API key '{$keyId}' revoked for user '{$username}'.");
|
||||
} else {
|
||||
$io->error("API key '{$keyId}' not found for user '{$username}'.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user