|
|
@@ -0,0 +1,68 @@
|
|
|
+// v2.4 — per-listener API-key auth processor implementation.
|
|
|
+
|
|
|
+#include "auth/auth_interceptor.hpp"
|
|
|
+
|
|
|
+#include <cstring>
|
|
|
+#include <string_view>
|
|
|
+
|
|
|
+#include <spdlog/spdlog.h>
|
|
|
+
|
|
|
+namespace smartbotic::database::auth {
|
|
|
+
|
|
|
+namespace {
|
|
|
+
|
|
|
+bool constant_time_eq(std::string_view a, std::string_view b) {
|
|
|
+ unsigned int diff = static_cast<unsigned int>(a.size())
|
|
|
+ ^ static_cast<unsigned int>(b.size());
|
|
|
+ const size_t n = std::min(a.size(), b.size());
|
|
|
+ for (size_t i = 0; i < n; ++i) {
|
|
|
+ diff |= static_cast<unsigned int>(static_cast<unsigned char>(a[i])
|
|
|
+ ^ static_cast<unsigned char>(b[i]));
|
|
|
+ }
|
|
|
+ return diff == 0;
|
|
|
+}
|
|
|
+
|
|
|
+bool starts_with_bearer(std::string_view s) {
|
|
|
+ constexpr std::string_view kPrefix = "Bearer ";
|
|
|
+ return s.size() >= kPrefix.size()
|
|
|
+ && std::memcmp(s.data(), kPrefix.data(), kPrefix.size()) == 0;
|
|
|
+}
|
|
|
+
|
|
|
+} // namespace
|
|
|
+
|
|
|
+grpc::Status BearerAuthProcessor::Process(
|
|
|
+ const InputMetadata& auth_metadata,
|
|
|
+ grpc::AuthContext* /*context*/,
|
|
|
+ OutputMetadata* consumed_auth_metadata,
|
|
|
+ OutputMetadata* /*response_metadata*/) {
|
|
|
+
|
|
|
+ auto it = auth_metadata.find("authorization");
|
|
|
+ if (it == auth_metadata.end()) {
|
|
|
+ spdlog::warn("auth: rejected — no `authorization` metadata");
|
|
|
+ return grpc::Status(grpc::StatusCode::UNAUTHENTICATED,
|
|
|
+ "missing authorization metadata");
|
|
|
+ }
|
|
|
+ std::string_view value(it->second.data(), it->second.size());
|
|
|
+ if (!starts_with_bearer(value)) {
|
|
|
+ spdlog::warn("auth: rejected — `authorization` not 'Bearer <key>'");
|
|
|
+ return grpc::Status(grpc::StatusCode::UNAUTHENTICATED,
|
|
|
+ "authorization must be 'Bearer <key>'");
|
|
|
+ }
|
|
|
+ std::string_view tok = value.substr(7);
|
|
|
+
|
|
|
+ for (const auto& k : keys_) {
|
|
|
+ if (constant_time_eq(tok, k)) {
|
|
|
+ // Mark the metadata as consumed so gRPC doesn't surface it
|
|
|
+ // to handler code.
|
|
|
+ consumed_auth_metadata->insert(
|
|
|
+ std::make_pair(std::string("authorization"),
|
|
|
+ std::string(value)));
|
|
|
+ return grpc::Status::OK;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ spdlog::warn("auth: rejected — token does not match any configured key");
|
|
|
+ return grpc::Status(grpc::StatusCode::UNAUTHENTICATED,
|
|
|
+ "invalid auth token");
|
|
|
+}
|
|
|
+
|
|
|
+} // namespace smartbotic::database::auth
|