Browse Source

Swagger update + moved API user/gpg -> gpgkey/

Roman 1 week ago
parent
commit
d6c6572989

+ 295 - 0
Core/API/GpgKeyAPI.class.php

@@ -0,0 +1,295 @@
+<?php
+
+namespace Core\API {
+
+  use Core\Objects\Context;
+
+  abstract class GpgKeyAPI extends \Core\API\Request {
+    public function __construct(Context $context, bool $externalCall = false, array $params = array()) {
+      parent::__construct($context, $externalCall, $params);
+      $this->loginRequired = true;
+    }
+  }
+
+}
+
+namespace Core\API\GpgKey {
+
+
+  use Core\API\GpgKeyAPI;
+  use Core\API\Parameter\Parameter;
+  use Core\API\Parameter\StringType;
+  use Core\API\Template\Render;
+  use Core\Driver\SQL\Condition\Compare;
+  use Core\Driver\SQL\Query\Insert;
+  use Core\Objects\Context;
+  use Core\Objects\DatabaseEntity\GpgKey;
+  use Core\Objects\DatabaseEntity\User;
+  use Core\Objects\DatabaseEntity\UserToken;
+
+  class Import extends GpgKeyAPI {
+
+    public function __construct(Context $context, bool $externalCall = false) {
+      parent::__construct($context, $externalCall, [
+        "pubkey" => new StringType("pubkey")
+      ]);
+      $this->loginRequired = true;
+      $this->forbidMethod("GET");
+    }
+
+    private function testKey(string $keyString) {
+      $res = GpgKey::getKeyInfo($keyString);
+      if (!$res["success"]) {
+        return $this->createError($res["error"] ?? $res["msg"]);
+      }
+
+      $keyData = $res["data"];
+      $keyType = $keyData["type"];
+      $expires = $keyData["expires"];
+
+      if ($keyType === "sec#") {
+        return self::createError("ATTENTION! It seems like you've imported a PGP PRIVATE KEY instead of a public key. 
+            It is recommended to immediately revoke your private key and create a new key pair.");
+      } else if ($keyType !== "pub") {
+        return self::createError("Unknown key type: $keyType");
+      } else if (isInPast($expires)) {
+        return self::createError("It seems like the gpg key is already expired.");
+      } else {
+        return $keyData;
+      }
+    }
+
+    public function _execute(): bool {
+
+      $currentUser = $this->context->getUser();
+      $gpgKey = $currentUser->getGPG();
+      if ($gpgKey) {
+        return $this->createError("You already added a GPG key to your account.");
+      } else if (!$currentUser->getEmail()) {
+        return $this->createError("You do not have an e-mail address");
+      }
+
+      // fix key first, enforce a newline after
+      $keyString = $this->getParam("pubkey");
+      $keyString = preg_replace("/(-{2,})\n([^\n])/", "$1\n\n$2", $keyString);
+      $keyData = $this->testKey($keyString);
+      if ($keyData === false) {
+        return false;
+      }
+
+      $res = GpgKey::importKey($keyString);
+      if (!$res["success"]) {
+        return $this->createError($res["error"]);
+      }
+
+      $sql = $this->context->getSQL();
+      $gpgKey = new GpgKey($keyData["fingerprint"], $keyData["algorithm"], $keyData["expires"]);
+      if (!$gpgKey->save($sql)) {
+        return $this->createError("Error creating gpg key: " . $sql->getLastError());
+      }
+
+      $token = generateRandomString(36);
+      $userToken = new UserToken($currentUser, $token, UserToken::TYPE_GPG_CONFIRM, 1);
+      if (!$userToken->save($sql)) {
+        return $this->createError("Error saving user token: " . $sql->getLastError());
+      }
+
+      $validHours = 1;
+      $settings = $this->context->getSettings();
+      $baseUrl = $settings->getBaseUrl();
+      $siteName = $settings->getSiteName();
+      $req = new Render($this->context);
+      $this->success = $req->execute([
+        "file" => "mail/gpg_import.twig",
+        "parameters" => [
+          "link" => "$baseUrl/resetPassword?token=$token",
+          "site_name" => $siteName,
+          "base_url" => $baseUrl,
+          "username" => $currentUser->getDisplayName(),
+          "valid_time" => $this->formatDuration($validHours, "hour")
+        ]
+      ]);
+
+      $this->lastError = $req->getLastError();
+
+      if ($this->success) {
+        $messageBody = $req->getResult()["html"];
+        $sendMail = new \Core\API\Mail\Send($this->context);
+        $this->success = $sendMail->execute(array(
+          "to" => $currentUser->getEmail(),
+          "subject" => "[$siteName] Confirm GPG-Key",
+          "body" => $messageBody,
+          "gpgFingerprint" => $gpgKey->getFingerprint()
+        ));
+
+        $this->lastError = $sendMail->getLastError();
+
+        if ($this->success) {
+          $currentUser->gpgKey = $gpgKey;
+          if ($currentUser->save($sql, ["gpgKey"])) {
+            $this->result["gpgKey"] = $gpgKey->jsonSerialize();
+          } else {
+            return $this->createError("Error updating user details: " . $sql->getLastError());
+          }
+        }
+      }
+
+      return $this->success;
+    }
+
+    public static function getDefaultACL(Insert $insert): void {
+      $insert->addRow(self::getEndpoint(), [], "Allows users to import gpg keys for a secure e-mail communication", true);
+    }
+  }
+
+  class Remove extends GpgKeyAPI {
+    public function __construct(Context $context, bool $externalCall = false) {
+      parent::__construct($context, $externalCall, array(
+        "password" => new StringType("password")
+      ));
+      $this->loginRequired = true;
+      $this->forbidMethod("GET");
+    }
+
+    public function _execute(): bool {
+
+      $currentUser = $this->context->getUser();
+      $gpgKey = $currentUser->getGPG();
+      if (!$gpgKey) {
+        return $this->createError("You have not added a GPG public key to your account yet.");
+      }
+
+      $sql = $this->context->getSQL();
+      $password = $this->getParam("password");
+      if (!password_verify($password, $currentUser->password)) {
+        return $this->createError("Incorrect password.");
+      } else if (!$gpgKey->delete($sql)) {
+        return $this->createError("Error deleting gpg key: " . $sql->getLastError());
+      }
+
+      return $this->success;
+    }
+
+    public static function getDefaultACL(Insert $insert): void {
+      $insert->addRow(self::getEndpoint(), [], "Allows users to unlink gpg keys from their profile", true);
+    }
+  }
+
+  class Confirm extends GpgKeyAPI {
+
+    public function __construct(Context $context, bool $externalCall = false) {
+      parent::__construct($context, $externalCall, [
+        "token" => new StringType("token", 36)
+      ]);
+      $this->loginRequired = true;
+    }
+
+    public function _execute(): bool {
+
+      $currentUser = $this->context->getUser();
+      $gpgKey = $currentUser->getGPG();
+      if (!$gpgKey) {
+        return $this->createError("You have not added a GPG key yet.");
+      } else if ($gpgKey->isConfirmed()) {
+        return $this->createError("Your GPG key is already confirmed");
+      }
+
+      $token = $this->getParam("token");
+      $sql = $this->context->getSQL();
+
+      $userToken = UserToken::findBy(UserToken::createBuilder($sql, true)
+        ->whereEq("token", $token)
+        ->where(new Compare("valid_until", $sql->now(), ">="))
+        ->whereEq("user_id", $currentUser->getId())
+        ->whereEq("token_type", UserToken::TYPE_GPG_CONFIRM));
+
+      if ($userToken !== false) {
+        if ($userToken === null) {
+          return $this->createError("Invalid token");
+        } else {
+          if (!$gpgKey->confirm($sql)) {
+            return $this->createError("Error updating gpg key: " . $sql->getLastError());
+          }
+
+          $userToken->invalidate($sql);
+        }
+      } else {
+        return $this->createError("Error validating token: " . $sql->getLastError());
+      }
+
+      return $this->success;
+    }
+  }
+
+  class Download extends GpgKeyAPI {
+    public function __construct(Context $context, bool $externalCall = false) {
+      parent::__construct($context, $externalCall, array(
+        "id" => new Parameter("id", Parameter::TYPE_INT, true, null),
+        "format" => new StringType("format", 16, true, "ascii")
+      ));
+      $this->loginRequired = true;
+      $this->csrfTokenRequired = false;
+    }
+
+    public function _execute(): bool {
+
+      $allowedFormats = ["json", "ascii", "gpg"];
+      $format = $this->getParam("format");
+      if (!in_array($format, $allowedFormats)) {
+        return $this->getParam("Invalid requested format. Allowed formats: " . implode(",", $allowedFormats));
+      }
+
+      $currentUser = $this->context->getUser();
+      $userId = $this->getParam("id");
+      if ($userId === null || $userId == $currentUser->getId()) {
+        $gpgKey = $currentUser->getGPG();
+        if (!$gpgKey) {
+          return $this->createError("You did not add a gpg key yet.");
+        }
+
+        $email = $currentUser->getEmail();
+      } else {
+        $sql = $this->context->getSQL();
+        $user = User::find($sql, $userId, true);
+        if ($user === false) {
+          return $this->createError("Error fetching user details: " . $sql->getLastError());
+        } else if ($user === null) {
+          return $this->createError("User not found");
+        }
+
+        $email = $user->getEmail();
+        $gpgKey = $user->getGPG();
+        if (!$gpgKey || !$gpgKey->isConfirmed()) {
+          return $this->createError("This user has not added a gpg key yet or has not confirmed it yet.");
+        }
+      }
+
+      $res = GpgKey::export($gpgKey->getFingerprint(), $format !== "gpg");
+      if (!$res["success"]) {
+        return $this->createError($res["error"]);
+      }
+
+      $key = $res["data"];
+      if ($format === "json") {
+        $this->result["key"] = $key;
+        return true;
+      } else if ($format === "ascii") {
+        $contentType = "application/pgp-keys";
+        $ext = "asc";
+      } else if ($format === "gpg") {
+        $contentType = "application/octet-stream";
+        $ext = "gpg";
+      } else {
+        die("Invalid format");
+      }
+
+      $fileName = "$email.$ext";
+      header("Content-Type: $contentType");
+      header("Content-Length: " . strlen($key));
+      header("Content-Disposition: attachment; filename=\"$fileName\"");
+      die($key);
+    }
+  }
+
+
+}

+ 8 - 0
Core/API/Request.class.php

@@ -604,4 +604,12 @@ abstract class Request {
     $currentUser = $this->context->getUser();
     return $currentUser ? "userId='" . $currentUser->getId() . "'" : "SYSTEM";
   }
+
+  protected function formatDuration(int $count, string $string): string {
+    if ($count === 1) {
+      return $string;
+    } else {
+      return "the next $count {$string}s";
+    }
+  }
 }

+ 17 - 1
Core/API/Swagger.class.php

@@ -63,6 +63,9 @@ class Swagger extends Request {
 
     $definitions = [];
     $paths = [];
+    $tags = [];
+
+    // TODO: consumes and produces is not always the same, but it's okay for now
     foreach (self::getApiEndpoints() as $endpoint => $apiClass) {
       $body = null;
       $requiredProperties = [];
@@ -72,6 +75,17 @@ class Swagger extends Request {
         continue;
       }
 
+      $tag = null;
+      if ($apiClass->getParentClass()->getName() !== Request::class) {
+        $parentClass = $apiClass->getParentClass()->getShortName();
+        if (endsWith($parentClass, "API")) {
+          $tag = substr($parentClass, 0, strlen($parentClass) - 3);
+          if (!in_array($tag, $tags)) {
+            $tags[] = $tag;
+          }
+        }
+      }
+
       $parameters = $apiObject->getDefaultParams();
       if (!empty($parameters)) {
         $body = [];
@@ -107,6 +121,7 @@ class Swagger extends Request {
 
       $endPointDefinition = [
         "post" => [
+          "tags" => [$tag ?? "Global"],
           "produces" => ["application/json"],
           "responses" => [
             "200" => ["description" => "OK!"],
@@ -123,7 +138,7 @@ class Swagger extends Request {
       }
 
       if ($body) {
-        $endPointDefinition["post"]["consumes"] = ["application/json"];
+        $endPointDefinition["post"]["consumes"] = ["application/json", "application/x-www-form-urlencoded"];
         $endPointDefinition["post"]["parameters"] = [[
           "in" => "body",
           "name" => "body",
@@ -149,6 +164,7 @@ class Swagger extends Request {
       "host" => $domain,
       "basePath" => "/api",
       "schemes" => ["$protocol"],
+      "tags" => $tags,
       "paths" => $paths,
       "definitions" => $definitions
     ];

+ 0 - 272
Core/API/UserAPI.class.php

@@ -98,14 +98,6 @@ namespace Core\API {
       return password_hash($password, PASSWORD_BCRYPT);
     }
 
-    protected function formatDuration(int $count, string $string): string {
-      if ($count === 1) {
-        return $string;
-      } else {
-        return "the next $count {$string}s";
-      }
-    }
-
     protected function checkToken(string $token) : UserToken|bool {
       $sql = $this->context->getSQL();
       $userToken = UserToken::findBy(UserToken::createBuilder($sql, true)
@@ -1244,270 +1236,6 @@ namespace Core\API\User {
     }
   }
 
-  class ImportGPG extends UserAPI {
-
-    public function __construct(Context $context, bool $externalCall = false) {
-      parent::__construct($context, $externalCall, [
-        "pubkey" => new StringType("pubkey")
-      ]);
-      $this->loginRequired = true;
-      $this->forbidMethod("GET");
-    }
-
-    private function testKey(string $keyString) {
-      $res = GpgKey::getKeyInfo($keyString);
-      if (!$res["success"]) {
-        return $this->createError($res["error"] ?? $res["msg"]);
-      }
-
-      $keyData = $res["data"];
-      $keyType = $keyData["type"];
-      $expires = $keyData["expires"];
-
-      if ($keyType === "sec#") {
-        return self::createError("ATTENTION! It seems like you've imported a PGP PRIVATE KEY instead of a public key. 
-            It is recommended to immediately revoke your private key and create a new key pair.");
-      } else if ($keyType !== "pub") {
-        return self::createError("Unknown key type: $keyType");
-      } else if (isInPast($expires)) {
-        return self::createError("It seems like the gpg key is already expired.");
-      } else {
-        return $keyData;
-      }
-    }
-
-    public function _execute(): bool {
-
-      $currentUser = $this->context->getUser();
-      $gpgKey = $currentUser->getGPG();
-      if ($gpgKey) {
-        return $this->createError("You already added a GPG key to your account.");
-      } else if (!$currentUser->getEmail()) {
-        return $this->createError("You do not have an e-mail address");
-      }
-
-      // fix key first, enforce a newline after
-      $keyString = $this->getParam("pubkey");
-      $keyString = preg_replace("/(-{2,})\n([^\n])/", "$1\n\n$2", $keyString);
-      $keyData = $this->testKey($keyString);
-      if ($keyData === false) {
-        return false;
-      }
-
-      $res = GpgKey::importKey($keyString);
-      if (!$res["success"]) {
-        return $this->createError($res["error"]);
-      }
-
-      $sql = $this->context->getSQL();
-      $gpgKey = new GpgKey($keyData["fingerprint"], $keyData["algorithm"], $keyData["expires"]);
-      if (!$gpgKey->save($sql)) {
-        return $this->createError("Error creating gpg key: " . $sql->getLastError());
-      }
-
-      $token = generateRandomString(36);
-      $userToken = new UserToken($currentUser, $token, UserToken::TYPE_GPG_CONFIRM, 1);
-      if (!$userToken->save($sql)) {
-        return $this->createError("Error saving user token: " . $sql->getLastError());
-      }
-
-      $validHours = 1;
-      $settings = $this->context->getSettings();
-      $baseUrl = $settings->getBaseUrl();
-      $siteName = $settings->getSiteName();
-      $req = new Render($this->context);
-      $this->success = $req->execute([
-        "file" => "mail/gpg_import.twig",
-        "parameters" => [
-          "link" => "$baseUrl/resetPassword?token=$token",
-          "site_name" => $siteName,
-          "base_url" => $baseUrl,
-          "username" => $currentUser->getDisplayName(),
-          "valid_time" => $this->formatDuration($validHours, "hour")
-        ]
-      ]);
-
-      $this->lastError = $req->getLastError();
-
-      if ($this->success) {
-        $messageBody = $req->getResult()["html"];
-        $sendMail = new \Core\API\Mail\Send($this->context);
-        $this->success = $sendMail->execute(array(
-          "to" => $currentUser->getEmail(),
-          "subject" => "[$siteName] Confirm GPG-Key",
-          "body" => $messageBody,
-          "gpgFingerprint" => $gpgKey->getFingerprint()
-        ));
-
-        $this->lastError = $sendMail->getLastError();
-
-        if ($this->success) {
-          $currentUser->gpgKey = $gpgKey;
-          if ($currentUser->save($sql, ["gpgKey"])) {
-            $this->result["gpgKey"] = $gpgKey->jsonSerialize();
-          } else {
-            return $this->createError("Error updating user details: " . $sql->getLastError());
-          }
-        }
-      }
-
-      return $this->success;
-    }
-
-    public static function getDefaultACL(Insert $insert): void {
-      $insert->addRow(self::getEndpoint(), [], "Allows users to import gpg keys for a secure e-mail communication", true);
-    }
-  }
-
-  class RemoveGPG extends UserAPI {
-    public function __construct(Context $context, bool $externalCall = false) {
-      parent::__construct($context, $externalCall, array(
-        "password" => new StringType("password")
-      ));
-      $this->loginRequired = true;
-      $this->forbidMethod("GET");
-    }
-
-    public function _execute(): bool {
-
-      $currentUser = $this->context->getUser();
-      $gpgKey = $currentUser->getGPG();
-      if (!$gpgKey) {
-        return $this->createError("You have not added a GPG public key to your account yet.");
-      }
-
-      $sql = $this->context->getSQL();
-      $password = $this->getParam("password");
-      if (!password_verify($password, $currentUser->password)) {
-        return $this->createError("Incorrect password.");
-      } else if (!$gpgKey->delete($sql)) {
-        return $this->createError("Error deleting gpg key: " . $sql->getLastError());
-      }
-
-      return $this->success;
-    }
-
-    public static function getDefaultACL(Insert $insert): void {
-      $insert->addRow(self::getEndpoint(), [], "Allows users to unlink gpg keys from their profile", true);
-    }
-  }
-
-  class ConfirmGPG extends UserAPI {
-
-    public function __construct(Context $context, bool $externalCall = false) {
-      parent::__construct($context, $externalCall, [
-        "token" => new StringType("token", 36)
-      ]);
-      $this->loginRequired = true;
-    }
-
-    public function _execute(): bool {
-
-      $currentUser = $this->context->getUser();
-      $gpgKey = $currentUser->getGPG();
-      if (!$gpgKey) {
-        return $this->createError("You have not added a GPG key yet.");
-      } else if ($gpgKey->isConfirmed()) {
-        return $this->createError("Your GPG key is already confirmed");
-      }
-
-      $token = $this->getParam("token");
-      $sql = $this->context->getSQL();
-
-      $userToken = UserToken::findBy(UserToken::createBuilder($sql, true)
-        ->whereEq("token", $token)
-        ->where(new Compare("valid_until", $sql->now(), ">="))
-        ->whereEq("user_id", $currentUser->getId())
-        ->whereEq("token_type", UserToken::TYPE_GPG_CONFIRM));
-
-      if ($userToken !== false) {
-        if ($userToken === null) {
-          return $this->createError("Invalid token");
-        } else {
-          if (!$gpgKey->confirm($sql)) {
-            return $this->createError("Error updating gpg key: " . $sql->getLastError());
-          }
-
-          $userToken->invalidate($sql);
-        }
-      } else {
-        return $this->createError("Error validating token: " . $sql->getLastError());
-      }
-
-      return $this->success;
-    }
-  }
-
-  class DownloadGPG extends UserAPI {
-    public function __construct(Context $context, bool $externalCall = false) {
-      parent::__construct($context, $externalCall, array(
-        "id" => new Parameter("id", Parameter::TYPE_INT, true, null),
-        "format" => new StringType("format", 16, true, "ascii")
-      ));
-      $this->loginRequired = true;
-      $this->csrfTokenRequired = false;
-    }
-
-    public function _execute(): bool {
-
-      $allowedFormats = ["json", "ascii", "gpg"];
-      $format = $this->getParam("format");
-      if (!in_array($format, $allowedFormats)) {
-        return $this->getParam("Invalid requested format. Allowed formats: " . implode(",", $allowedFormats));
-      }
-
-      $currentUser = $this->context->getUser();
-      $userId = $this->getParam("id");
-      if ($userId === null || $userId == $currentUser->getId()) {
-        $gpgKey = $currentUser->getGPG();
-        if (!$gpgKey) {
-          return $this->createError("You did not add a gpg key yet.");
-        }
-
-        $email = $currentUser->getEmail();
-      } else {
-        $sql = $this->context->getSQL();
-        $user = User::find($sql, $userId, true);
-        if ($user === false) {
-          return $this->createError("Error fetching user details: " . $sql->getLastError());
-        } else if ($user === null) {
-          return $this->createError("User not found");
-        }
-
-        $email = $user->getEmail();
-        $gpgKey = $user->getGPG();
-        if (!$gpgKey || !$gpgKey->isConfirmed()) {
-          return $this->createError("This user has not added a gpg key yet or has not confirmed it yet.");
-        }
-      }
-
-      $res = GpgKey::export($gpgKey->getFingerprint(), $format !== "gpg");
-      if (!$res["success"]) {
-        return $this->createError($res["error"]);
-      }
-
-      $key = $res["data"];
-      if ($format === "json") {
-        $this->result["key"] = $key;
-        return true;
-      } else if ($format === "ascii") {
-        $contentType = "application/pgp-keys";
-        $ext = "asc";
-      } else if ($format === "gpg") {
-        $contentType = "application/octet-stream";
-        $ext = "gpg";
-      } else {
-        die("Invalid format");
-      }
-
-      $fileName = "$email.$ext";
-      header("Content-Type: $contentType");
-      header("Content-Length: " . strlen($key));
-      header("Content-Disposition: attachment; filename=\"$fileName\"");
-      die($key);
-    }
-  }
-
   class UploadPicture extends UserAPI {
     public function __construct(Context $context, bool $externalCall = false) {
       // TODO: we should optimize the process here, we need an offset and size parameter to get a quadratic crop of the uploaded image

+ 1 - 0
Core/Documents/Security.class.php

@@ -45,6 +45,7 @@ class Security extends Document {
         "# This project is based on the open-source framework hosted on https://github.com/rhergenreder/web-base",
         "# Any non site-specific issues can be reported via the github security reporting feature:",
         "# https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability",
+        "# or by contacting me directly: mail(at)romanh(dot)de",
         "",
         "Canonical: $baseUrl/.well-known/security.txt",
         "Preferred-Languages: $languageCodes",

+ 2 - 1
Core/Objects/Router/ApiRoute.class.php

@@ -62,7 +62,7 @@ class ApiRoute extends Route {
             http_response_code(400);
             $response = createError("Invalid Method");
           } else {
-            $request = $apiClass->newInstanceArgs(array($router->getContext(), true));
+            $request = $apiClass->newInstanceArgs([$router->getContext(), true]);
             $success = $request->execute();
             $response = $request->getResult();
             $response["success"] = $success;
@@ -74,6 +74,7 @@ class ApiRoute extends Route {
         }
       } catch (ReflectionException $e) {
         http_response_code(500);
+        $router->getLogger()->error("Error instantiating class: $e");
         $response = createError("Error instantiating class: $e");
       }
     }

+ 2 - 0
Core/Objects/Router/DocumentRoute.class.php

@@ -83,6 +83,7 @@ class DocumentRoute extends Route {
 
     try {
       if (!$this->loadClass()) {
+        $router->getLogger()->warning("Error loading class: $className");
         return $router->returnStatusCode(500, [ "message" =>  "Error loading class: $className"]);
       }
 
@@ -90,6 +91,7 @@ class DocumentRoute extends Route {
       $document = $this->reflectionClass->newInstanceArgs($args);
       return $document->load($params);
     } catch (\ReflectionException $e) {
+      $router->getLogger()->error("Error loading class: $className: " . $e->getMessage());
       return $router->returnStatusCode(500, [ "message" =>  "Error loading class $className: " . $e->getMessage()]);
     }
   }

+ 1 - 1
Core/Templates/account/confirm_gpg.twig

@@ -16,7 +16,7 @@
             let token = jsCore.getParameter("token");
             let confirmStatus = $("#confirm-status");
             if (token) {
-                jsCore.apiCall("/user/confirmGPG", { token: token, csrfToken: '{{ user.session.csrfToken }}' }, (res) => {
+                jsCore.apiCall("/gpgKey/confirm", { token: token, csrfToken: '{{ user.session.csrfToken }}' }, (res) => {
                     confirmStatus.removeClass("alert-info");
                     if (!res.success) {
                         confirmStatus.addClass("alert-danger");

File diff suppressed because it is too large
+ 0 - 1
css/swagger-ui.css


File diff suppressed because it is too large
+ 0 - 0
js/swagger-ui-bundle.js


File diff suppressed because it is too large
+ 0 - 0
js/swagger-ui-standalone-preset.js


+ 3 - 3
react/admin-panel/src/views/profile/gpg-box.js

@@ -126,7 +126,7 @@ export default function GpgBox(props) {
                     <Button startIcon={isGpgKeyRemoving ? <CircularProgress size={12} /> : <Remove />}
                             color="secondary" onClick={onRemoveGpgKey}
                             variant="outlined" size="small"
-                            disabled={isGpgKeyRemoving || !api.hasPermission("user/removeGPG")}>
+                            disabled={isGpgKeyRemoving || !api.hasPermission("gpgKey/remove")}>
                         {isGpgKeyRemoving ? L("general.removing") + "…" : L("general.remove")}
                     </Button>
                 </Box> :
@@ -134,7 +134,7 @@ export default function GpgBox(props) {
                     <SpacedFormGroup>
                         <FormLabel>{L("account.gpg_key")}</FormLabel>
                         <GpgKeyField value={gpgKey} multiline={true} rows={8}
-                                     disabled={isGpgKeyUploading || !api.hasPermission("user/importGPG")}
+                                     disabled={isGpgKeyUploading || !api.hasPermission("gpgKey/import")}
                                      placeholder={L("account.gpg_key_placeholder_text")}
                                      onChange={e => setGpgKey(e.target.value)}
                                      onDrop={e => {
@@ -162,7 +162,7 @@ export default function GpgBox(props) {
                         <Button startIcon={isGpgKeyUploading ? <CircularProgress size={12} /> : <Upload />}
                                 color="primary" onClick={onUploadGPG}
                                 variant="outlined" size="small"
-                                disabled={isGpgKeyUploading || !api.hasPermission("user/importGPG")}>
+                                disabled={isGpgKeyUploading || !api.hasPermission("gpgKey/import")}>
                             {isGpgKeyUploading ? L("general.uploading") + "…" : L("general.upload")}
                         </Button>
                     </ButtonBar>

+ 4 - 4
react/shared/api.js

@@ -387,7 +387,7 @@ export default class API {
 
     /** GPG API **/
     async uploadGPG(pubkey) {
-        let res = await this.apiCall("user/importGPG", { pubkey: pubkey });
+        let res = await this.apiCall("gpgKey/import", { pubkey: pubkey });
         if (res.success) {
             this.user.gpgKey = res.gpgKey;
         }
@@ -396,7 +396,7 @@ export default class API {
     }
 
     async confirmGpgToken(token) {
-        let res = await this.apiCall("user/confirmGPG", { token: token });
+        let res = await this.apiCall("gpgKey/confirm", { token: token });
         if (res.success) {
             this.user.gpgKey.confirmed = true;
         }
@@ -405,7 +405,7 @@ export default class API {
     }
 
     async removeGPG(password) {
-        let res = await this.apiCall("user/removeGPG", { password: password });
+        let res = await this.apiCall("gpgKey/remove", { password: password });
         if (res.success) {
             this.user.gpgKey = null;
         }
@@ -414,7 +414,7 @@ export default class API {
     }
 
     async downloadGPG(userId) {
-        return this.apiCall("user/downloadGPG", { id: userId }, true);
+        return this.apiCall("gpgKey/download", { id: userId }, true);
     }
 
     /** Log API **/

Some files were not shown because too many files changed in this diff