| 
<?phpdeclare(strict_types=1);
 
 use GetOpt\{
 GetOpt,
 Option
 };
 use ParagonIE\EasyDB\{
 EasyDB,
 Factory
 };
 use ParagonIE\Chronicle\Chronicle;
 use ParagonIE\Chronicle\Exception\InstanceNotFoundException;
 use ParagonIE\ConstantTime\Base64UrlSafe;
 use ParagonIE\Sapient\CryptographyKeys\SigningPublicKey;
 
 $root = \dirname(__DIR__);
 /** @psalm-suppress UnresolvableInclude */
 require_once $root . '/cli-autoload.php';
 
 if (!\is_readable($root . '/local/settings.json')) {
 echo 'Settings are not loaded.', PHP_EOL;
 exit(1);
 }
 
 /** @var array $settings */
 $settings = \json_decode(
 (string) \file_get_contents($root . '/local/settings.json'),
 true
 );
 /** @var EasyDB $db */
 $db = Factory::create(
 $settings['database']['dsn'],
 $settings['database']['username'] ?? '',
 $settings['database']['password'] ?? '',
 $settings['database']['options'] ?? []
 );
 
 // Pass database instance to Chronicle
 Chronicle::setDatabase($db);
 
 /**
 * @var Getopt $getopt
 *
 * This defines the Command Line options.
 *
 * These two are equivalent:
 *     php create-client.php -p foo
 *     php create-client.php --publickey=foo
 */
 $getopt = new Getopt([
 new Option('p', 'publickey', Getopt::REQUIRED_ARGUMENT),
 new Option('c', 'comment', Getopt::OPTIONAL_ARGUMENT),
 new Option('j', 'json', Getopt::OPTIONAL_ARGUMENT),
 new Option(null, 'administrator', Getopt::OPTIONAL_ARGUMENT),
 new Option('i', 'instance', Getopt::OPTIONAL_ARGUMENT),
 ]);
 $getopt->process();
 
 /** @var string $publicKey */
 $publicKey = $getopt->getOption('publickey');
 /** @var string $comment */
 $comment = $getopt->getOption('comment') ?? '';
 /** @var bool $admin */
 $admin = $getopt->getOption('administrator') ?? false;
 /** @var bool $json */
 $json = $getopt->getOption('json') ?? false;
 /** @var string $instance */
 $instance = $getopt->getOption('instance') ?? '';
 
 try {
 if (!empty($instance)) {
 if (!\array_key_exists($instance, $settings['instances'])) {
 throw new InstanceNotFoundException(
 'Instance ' . $instance . ' not found'
 );
 }
 Chronicle::setTablePrefix($settings['instances'][$instance]);
 }
 } catch (InstanceNotFoundException $ex) {
 echo $ex->getMessage(), PHP_EOL;
 exit(1);
 }
 
 if (empty($publicKey)) {
 echo 'Usage:', PHP_EOL, "\t",
 'php create-client.php -p publickeygoeshere [-c comment] [--administrator]', PHP_EOL, PHP_EOL;
 exit(1);
 }
 
 /** @var SigningPublicKey $publicKeyObj */
 // Make sure it's a valid public key:
 try {
 $publicKeyObj = new SigningPublicKey(
 Base64UrlSafe::decode($publicKey)
 );
 } catch (\Throwable $ex) {
 if ($json) {
 echo json_encode([
 'status' => false,
 'message' => $ex->getMessage(),
 'data' => [
 'trace' => $ex->getTrace()
 ]
 ]), PHP_EOL;
 } else {
 echo $ex->getMessage(), PHP_EOL;
 }
 exit(1);
 }
 
 // Generate a unique ID for the user:
 /** @var string $newPublicId */
 $newPublicId = Base64UrlSafe::encode(\random_bytes(24));
 
 // Disable escaping for SQLite
 /** @var boolean $isSQLite */
 $isSQLite = strpos($settings['database']['dsn'] ?? '', 'sqlite:') !== false;
 
 $db->beginTransaction();
 $db->insert(
 Chronicle::getTableNameUnquoted('clients', $isSQLite),
 [
 'isAdmin' => !empty($admin),
 'publicid' => $newPublicId,
 'publickey' => $publicKey
 ]
 );
 if ($db->commit()) {
 if ($json) {
 echo json_encode([
 'status' => true,
 'message' => 'Client successfully created!',
 'data' => [
 'publicId' => $newPublicId,
 'administrator' => !empty($admin)
 ]
 ]);
 exit(0);
 }
 // Success.
 if (!empty($admin)) {
 echo "\t" . 'Client (' . $newPublicId . ') created successfully with administrative privileges!', PHP_EOL;
 } else {
 echo "\t" . 'Client (' . $newPublicId . ') created successfully!', PHP_EOL;
 }
 } else {
 $db->rollBack();
 /** @var array<int, string> $errorInfo */
 $errorInfo = $db->errorInfo();
 
 if ($json) {
 echo json_encode([
 'status' => false,
 'message' => $errorInfo[0],
 'data' => []
 ]), PHP_EOL;
 } else {
 echo $errorInfo[0], PHP_EOL;
 }
 exit(1);
 }
 
 |