|
|
@@ -2,6 +2,26 @@
|
|
|
#include "json_http.hpp"
|
|
|
#include "server.hpp"
|
|
|
namespace svapi {
|
|
|
+
|
|
|
+static std::optional<KeyScope> parseScope(const nlohmann::json& body, bool admin) {
|
|
|
+ if (!body.contains("scope")) return std::nullopt;
|
|
|
+ if (admin) throw ApiError(ErrCode::Unprocessable, "validation", "admin keys cannot be scoped");
|
|
|
+ const auto& sj = body["scope"];
|
|
|
+ if (!sj.is_object()) throw ApiError(ErrCode::Unprocessable, "validation", "scope must be an object");
|
|
|
+ if (!sj.contains("rules") || !sj["rules"].is_array() || sj["rules"].empty())
|
|
|
+ throw ApiError(ErrCode::Unprocessable, "validation", "scope.rules must be a non-empty array");
|
|
|
+ for (const auto& r : sj["rules"]) {
|
|
|
+ if (!r.is_object() || r.value("collection", std::string()).empty())
|
|
|
+ throw ApiError(ErrCode::Unprocessable, "validation", "each rule needs a collection");
|
|
|
+ if (!r.contains("ops") || !r["ops"].is_array() || r["ops"].empty())
|
|
|
+ throw ApiError(ErrCode::Unprocessable, "validation", "each rule needs ops");
|
|
|
+ for (const auto& o : r["ops"])
|
|
|
+ if (!o.is_string() || !keyOpFromString(o.get<std::string>()))
|
|
|
+ throw ApiError(ErrCode::Unprocessable, "validation", "unknown op in scope");
|
|
|
+ }
|
|
|
+ return KeyScope::fromJson(sj);
|
|
|
+}
|
|
|
+
|
|
|
void registerKeyRoutes(ApiServer& s) {
|
|
|
auto& svr = s.raw(); ServerDeps* d = &s.deps();
|
|
|
|
|
|
@@ -20,7 +40,8 @@ void registerKeyRoutes(ApiServer& s) {
|
|
|
if (body.contains("projects") && body["projects"].is_array())
|
|
|
projects = body["projects"].get<std::vector<std::string>>();
|
|
|
bool admin = body.value("admin", false);
|
|
|
- ApiKey made = d->keys.create(label, projects, admin);
|
|
|
+ auto scope = parseScope(body, admin);
|
|
|
+ ApiKey made = d->keys.create(label, projects, admin, std::move(scope));
|
|
|
nlohmann::json out = made.toPublicJson();
|
|
|
out["key"] = made.key; // returned ONCE, in full, on creation
|
|
|
sendJson(res, 201, out);
|
|
|
@@ -46,7 +67,20 @@ void registerKeyRoutes(ApiServer& s) {
|
|
|
if (adminCount <= 1) throw ApiError(ErrCode::Unprocessable, "last_admin", "cannot remove the last admin key");
|
|
|
}
|
|
|
}
|
|
|
- if (!d->keys.update(id, label, projects, admin))
|
|
|
+ std::optional<std::optional<KeyScope>> scopePatch;
|
|
|
+ if (body.contains("scope")) {
|
|
|
+ // Determine effective admin value: patched value if present, else existing key's admin
|
|
|
+ bool effectiveAdmin = false;
|
|
|
+ if (admin.has_value()) {
|
|
|
+ effectiveAdmin = *admin;
|
|
|
+ } else {
|
|
|
+ for (const auto& k : d->keys.list()) {
|
|
|
+ if (k.id == id) { effectiveAdmin = k.admin; break; }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ scopePatch = std::optional<std::optional<KeyScope>>{ parseScope(body, effectiveAdmin) };
|
|
|
+ }
|
|
|
+ if (!d->keys.update(id, label, projects, admin, scopePatch))
|
|
|
throw ApiError(ErrCode::NotFound, "not_found", "no such key");
|
|
|
sendJson(res, 200, {{"updated", true}});
|
|
|
});
|