|
|
@@ -0,0 +1,99 @@
|
|
|
+#include "key_store.hpp"
|
|
|
+#include <spdlog/spdlog.h>
|
|
|
+#include <ctime>
|
|
|
+
|
|
|
+namespace svapi {
|
|
|
+
|
|
|
+KeyStore::KeyStore(DbGateway& db, std::string collection)
|
|
|
+ : db_(db), coll_(std::move(collection)),
|
|
|
+ snap_(std::make_shared<const std::vector<ApiKey>>()) {}
|
|
|
+
|
|
|
+void KeyStore::reload() {
|
|
|
+ smartbotic::database::Client::QueryOptions opts;
|
|
|
+ opts.limit = 100000;
|
|
|
+ auto docs = db_.client().find(coll_, opts);
|
|
|
+ auto v = std::make_shared<std::vector<ApiKey>>();
|
|
|
+ v->reserve(docs.size());
|
|
|
+ for (const auto& d : docs) v->push_back(ApiKey::fromJson(d));
|
|
|
+ std::lock_guard<std::mutex> lk(m_);
|
|
|
+ snap_ = v;
|
|
|
+}
|
|
|
+
|
|
|
+void KeyStore::bootstrap(const std::string& envAdminKey) {
|
|
|
+ if (!db_.client().getCollectionInfo(coll_).has_value())
|
|
|
+ db_.client().createCollection(coll_, 0, /*encrypted*/ true, 0, 0);
|
|
|
+ reload();
|
|
|
+ if (snap_->empty()) {
|
|
|
+ ApiKey k;
|
|
|
+ k.key = envAdminKey.empty() ? generateApiKey() : envAdminKey;
|
|
|
+ k.label = "bootstrap-admin";
|
|
|
+ k.projects = {"*"};
|
|
|
+ k.admin = true;
|
|
|
+ k.createdAt = static_cast<uint64_t>(std::time(nullptr));
|
|
|
+ db_.client().upsert(coll_, k.toJson(), k.key);
|
|
|
+ reload();
|
|
|
+ if (envAdminKey.empty())
|
|
|
+ spdlog::warn("Generated initial admin API key: {} — store it now; shown only once.", k.key);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+std::optional<ApiKey> KeyStore::lookup(const std::string& key) const {
|
|
|
+ std::lock_guard<std::mutex> lk(m_);
|
|
|
+ for (const auto& k : *snap_)
|
|
|
+ if (k.key == key) return k;
|
|
|
+ return std::nullopt;
|
|
|
+}
|
|
|
+
|
|
|
+std::vector<ApiKey> KeyStore::list() const {
|
|
|
+ std::lock_guard<std::mutex> lk(m_);
|
|
|
+ return *snap_;
|
|
|
+}
|
|
|
+
|
|
|
+ApiKey KeyStore::create(const std::string& label,
|
|
|
+ std::vector<std::string> projects,
|
|
|
+ bool admin) {
|
|
|
+ ApiKey k;
|
|
|
+ k.key = generateApiKey();
|
|
|
+ k.label = label;
|
|
|
+ k.projects = std::move(projects);
|
|
|
+ k.admin = admin;
|
|
|
+ k.createdAt = static_cast<uint64_t>(std::time(nullptr));
|
|
|
+ db_.client().upsert(coll_, k.toJson(), k.key);
|
|
|
+ reload();
|
|
|
+ return k;
|
|
|
+}
|
|
|
+
|
|
|
+bool KeyStore::update(const std::string& key,
|
|
|
+ const std::optional<std::string>& label,
|
|
|
+ const std::optional<std::vector<std::string>>& projects,
|
|
|
+ const std::optional<bool>& admin) {
|
|
|
+ auto doc = db_.client().get(coll_, key);
|
|
|
+ if (!doc) return false;
|
|
|
+ ApiKey k = ApiKey::fromJson(*doc);
|
|
|
+ if (label) k.label = *label;
|
|
|
+ if (projects) k.projects = *projects;
|
|
|
+ if (admin) k.admin = *admin;
|
|
|
+ db_.client().upsert(coll_, k.toJson(), key);
|
|
|
+ reload();
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+bool KeyStore::remove(const std::string& key) {
|
|
|
+ bool ok = db_.client().remove(coll_, key);
|
|
|
+ reload();
|
|
|
+ return ok;
|
|
|
+}
|
|
|
+
|
|
|
+void KeyStore::startWatch() {
|
|
|
+ sub_ = db_.client().subscribe(
|
|
|
+ {coll_},
|
|
|
+ [this](const std::string&, const std::string&,
|
|
|
+ const std::string&, const std::optional<nlohmann::json>&) {
|
|
|
+ try { reload(); }
|
|
|
+ catch (const std::exception& e) {
|
|
|
+ spdlog::warn("key reload failed: {}", e.what());
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|
|
|
+} // namespace svapi
|