From 16bedfb5194c3b3ef2acbd60265d21eb09e315eb Mon Sep 17 00:00:00 2001 From: Alejandro Valerio Date: Mon, 24 Nov 2025 05:48:02 -0600 Subject: [PATCH 1/8] CMakeList update for JWT library dependency --- CMakeLists.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 754fd25..62ca60e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -75,6 +75,13 @@ FetchContent_Declare( GIT_TAG v1.14.0 ) +# jwt-cpp is a header-only JWT library +FetchContent_Declare( + jwt-cpp + GIT_REPOSITORY https://github.com/Thalhammer/jwt-cpp.git + GIT_TAG v0.7.2 +) + # Fetch dependencies # Disable dependency tests for cppcodec @@ -85,7 +92,7 @@ set(BUILD_TESTING OFF CACHE BOOL "" FORCE) FetchContent_MakeAvailable(cppcodec) set(BUILD_TESTING ON CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(crow httplib nlohmann_json cxxopts googletest) +FetchContent_MakeAvailable(crow httplib nlohmann_json cxxopts googletest jwt-cpp) # Make GoogleTest helper macros available (gtest_discover_tests) include(GoogleTest) @@ -114,6 +121,7 @@ target_link_libraries(dbps_server_lib PUBLIC dbps_common_lib) target_include_directories(dbps_server_lib PUBLIC src/server ${CMAKE_BINARY_DIR}/_deps/cppcodec-src + ${CMAKE_BINARY_DIR}/_deps/jwt-cpp-src/include ) # Client components library (depends on httplib, nlohmann/json, and cppcodec) From 71f4d802d8485e54822a08c1337c6d33121f9015 Mon Sep 17 00:00:00 2001 From: Alejandro Valerio Date: Mon, 24 Nov 2025 07:50:51 -0600 Subject: [PATCH 2/8] Adding API server JWT generation for Auth --- CMakeLists.txt | 20 +++- src/server/auth_utils.cpp | 175 +++++++++++++++++++++++++++++++++ src/server/auth_utils.h | 105 ++++++++++++++++++++ src/server/auth_utils_test.cpp | 87 ++++++++++++++++ src/server/dbps_api_server.cpp | 48 +++++++++ 5 files changed, 434 insertions(+), 1 deletion(-) create mode 100644 src/server/auth_utils.cpp create mode 100644 src/server/auth_utils.h create mode 100644 src/server/auth_utils_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 62ca60e..3aa7dda 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,8 @@ FetchContent_Declare( FetchContent_Declare( jwt-cpp GIT_REPOSITORY https://github.com/Thalhammer/jwt-cpp.git - GIT_TAG v0.7.2 + # ++++ Revise version before merging +++ + GIT_TAG master ) # Fetch dependencies @@ -116,14 +117,20 @@ target_link_libraries(dbps_common_lib PUBLIC tcb_span) # Server components library add_library(dbps_server_lib STATIC src/server/encryption_sequencer.cpp + src/server/auth_utils.cpp ) target_link_libraries(dbps_server_lib PUBLIC dbps_common_lib) target_include_directories(dbps_server_lib PUBLIC src/server ${CMAKE_BINARY_DIR}/_deps/cppcodec-src ${CMAKE_BINARY_DIR}/_deps/jwt-cpp-src/include + ${CMAKE_BINARY_DIR}/_deps/nlohmann_json-src/include ) +# Find and link OpenSSL (required by jwt-cpp) +find_package(OpenSSL REQUIRED) +target_link_libraries(dbps_server_lib PUBLIC OpenSSL::SSL OpenSSL::Crypto) + # Client components library (depends on httplib, nlohmann/json, and cppcodec) add_library(dbps_client_lib STATIC src/client/dbps_api_client.cpp @@ -235,6 +242,15 @@ if(BUILD_TESTS) ) target_include_directories(decoding_utils_test PRIVATE src/server) + # Auth utils tests + add_executable(auth_utils_test src/server/auth_utils_test.cpp) + target_link_libraries(auth_utils_test + dbps_server_lib + dbps_common_lib + gtest_main + ) + target_include_directories(auth_utils_test PRIVATE src/server) + # DBPA interface tests add_executable(dbpa_interface_test src/common/dbpa_interface_test.cpp) target_link_libraries(dbpa_interface_test @@ -395,6 +411,7 @@ if(BUILD_TESTS) enum_utils_test encryption_sequencer_test decoding_utils_test + auth_utils_test dbpa_interface_test dbpa_utils_test dbps_api_client_test @@ -410,6 +427,7 @@ if(BUILD_TESTS) gtest_discover_tests(enum_utils_test) gtest_discover_tests(encryption_sequencer_test) gtest_discover_tests(decoding_utils_test) + gtest_discover_tests(auth_utils_test) gtest_discover_tests(dbpa_interface_test) gtest_discover_tests(dbpa_utils_test) gtest_discover_tests(dbps_api_client_test) diff --git a/src/server/auth_utils.cpp b/src/server/auth_utils.cpp new file mode 100644 index 0000000..cabba7b --- /dev/null +++ b/src/server/auth_utils.cpp @@ -0,0 +1,175 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include "auth_utils.h" +#include +#include +#include +#include +#include + +// Hardcoded JWT secret key for signing tokens +// TODO: Make this configurable (e.g., from environment variable or config file) +static const std::string JWT_SECRET_KEY = "default-secret-key-change-in-production"; + +// JWT expiration time: 4 hours in seconds +static const int JWT_EXPIRATION_SECONDS = 4 * 60 * 60; // 14400 seconds + +// ClientCredentialStore implementation + +// Initialize credential store from a given map. +void ClientCredentialStore::init(const std::map& credentials) { + credentials_.clear(); + credentials_ = credentials; +} + +// Initialize credential store from a JSON file +bool ClientCredentialStore::init(const std::string& file_path) { + try { + // Open and read the JSON file + std::ifstream file(file_path); + if (!file.is_open()) { + std::cerr << "Error: Cannot open credentials file: " << file_path << std::endl; + return false; + } + + // Parse JSON + nlohmann::json json_data; + file >> json_data; + file.close(); + + // Validate that it's an object + if (!json_data.is_object()) { + std::cerr << "Error: Credentials file must contain a JSON object" << std::endl; + return false; + } + + // Clear existing credentials + credentials_.clear(); + + // Load each client_id:api_key pair + for (auto& [client_id, api_key_value] : json_data.items()) { + if (api_key_value.is_string()) { + AddCredential(client_id, api_key_value.get()); + } else { + std::cerr << "Warning: Skipping invalid api_key for client_id: " << client_id << std::endl; + } + } + + return true; + } catch (const nlohmann::json::exception& e) { + std::cerr << "Error parsing JSON credentials file: " << e.what() << std::endl; + return false; + } catch (const std::exception& e) { + std::cerr << "Error loading credentials file: " << e.what() << std::endl; + return false; + } +} + +void ClientCredentialStore::AddCredential(const std::string& client_id, const std::string& api_key) { + credentials_[client_id] = api_key; +} + +bool ClientCredentialStore::ValidateCredential(const std::string& client_id, const std::string& api_key) const { + auto it = credentials_.find(client_id); + if (it == credentials_.end()) { + return false; + } + return it->second == api_key; +} + +bool ClientCredentialStore::HasClientId(const std::string& client_id) const { + return credentials_.find(client_id) != credentials_.end(); +} + +// GenerateJWT implementation +std::optional ClientCredentialStore::GenerateJWT(const std::string& client_id, const std::string& api_key) const { + // Validate that client_id is not empty + if (client_id.empty()) { + std::cerr << "Error generating JWT: client_id cannot be empty" << std::endl; + return std::nullopt; + } + + // Validate credentials before generating JWT + if (!ValidateCredential(client_id, api_key)) { + std::cerr << "Error generating JWT: Invalid credentials for client_id: " << client_id << std::endl; + return std::nullopt; + } + + try { + // Get current time + auto now = std::chrono::system_clock::now(); + auto now_seconds = std::chrono::duration_cast(now.time_since_epoch()).count(); + + // Calculate expiration time (4 hours from now) + auto exp_seconds = now_seconds + JWT_EXPIRATION_SECONDS; + + // Create JWT token with client_id, iat, and exp claims + auto token = jwt::create() + .set_type("JWT") + .set_payload_claim("client_id", jwt::claim(client_id)) + .set_issued_at(std::chrono::system_clock::from_time_t(now_seconds)) + .set_expires_at(std::chrono::system_clock::from_time_t(exp_seconds)) + .sign(jwt::algorithm::hs256{JWT_SECRET_KEY}); + + return token; + } catch (const std::exception& e) { + std::cerr << "Error generating JWT: " << e.what() << std::endl; + return std::nullopt; + } +} + +// ParseAuthRequest implementation +AuthRequest ParseAuthRequest(const std::string& request_body) { + AuthRequest auth_req; + + try { + // Parse JSON request body using nlohmann::json + nlohmann::json json_body = nlohmann::json::parse(request_body); + + // Validate that it's an object + if (!json_body.is_object()) { + auth_req.error_message = "Invalid JSON in request body"; + return auth_req; + } + + // Extract client_id from request + if (json_body.contains("client_id") && json_body["client_id"].is_string()) { + auth_req.client_id = json_body["client_id"].get(); + } else { + auth_req.error_message = "Missing required field: client_id"; + return auth_req; + } + + // Extract api_key from request + if (json_body.contains("api_key") && json_body["api_key"].is_string()) { + auth_req.api_key = json_body["api_key"].get(); + } else { + auth_req.error_message = "Missing required field: api_key"; + return auth_req; + } + + return auth_req; + } catch (const nlohmann::json::exception& e) { + auth_req.error_message = "Invalid JSON in request body: " + std::string(e.what()); + return auth_req; + } catch (const std::exception& e) { + auth_req.error_message = "Error parsing request: " + std::string(e.what()); + return auth_req; + } +} + diff --git a/src/server/auth_utils.h b/src/server/auth_utils.h new file mode 100644 index 0000000..f63f610 --- /dev/null +++ b/src/server/auth_utils.h @@ -0,0 +1,105 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Note for Protegrity integration: +// - This is a simplified Authentication module used to complete the client integration. +// - It is fully functional, but for production deployment a fully pledged Identity Provider can be used instead. +// +// TODO: Expand explanation below. +// - Note that no Arrow codebase changes would be needed to replace this with a fully pledged Identity Provider +// as the connection configuration specified on the application level is passed as-is to the DBPS agents for authentication. + +#pragma once + +#include +#include +#include + +#ifndef DBPS_EXPORT +#define DBPS_EXPORT +#endif + +/** + * ClientCredentialStore manages client_id to api_key mappings for authentication. + * + * - Loads client credentials from a Json file and stores them in-memory. + * - Generates a JWT token for a given client_id. + */ +class DBPS_EXPORT ClientCredentialStore { +public: + ClientCredentialStore() = default; + ~ClientCredentialStore() = default; + + /** + * Initializes the credential store by loading credentials from a JSON file. + * The file should contain a JSON object with client_id as keys and api_key as values. + * Example format: {"client1": "api_key_1", "client2": "api_key_2"} + */ + bool init(const std::string& file_path); + + /** + * Initializes the credential store with a pre-built map of client_id to api_key. + * @param credentials Map of client_id to api_key pairs + */ + void init(const std::map& credentials); + + /** + * Generates a JWT token for the given client_id if the credentials are valid. + * + * The JWT includes: + * - client_id: The client identifier + * - iat: Issued at timestamp + * - exp: Expiration timestamp (4 hours from issue time) + * + * Uses HS256 algorithm with a hardcoded secret key. + * + * @param client_id The client identifier to validate and include in the JWT + * @param api_key The API key to validate against the stored credential + * @return The JWT token as a string if credentials are valid, or std::nullopt on error or invalid credentials + */ + std::optional GenerateJWT(const std::string& client_id, const std::string& api_key) const; + +private: + // Adds a client credential to the in-memory storage. + void AddCredential(const std::string& client_id, const std::string& api_key); + + // Check if a client credential is valid before generating a JWT token. + bool ValidateCredential(const std::string& client_id, const std::string& api_key) const; + + // Check if a a credential for a client_id exists + bool HasClientId(const std::string& client_id) const; + + // In-memory storage: client_id -> api_key + std::map credentials_; +}; + +/** + * Structure to hold parsed authentication request data. + */ +struct DBPS_EXPORT AuthRequest { + std::string client_id; + std::string api_key; + std::optional error_message; // Error message if parsing failed +}; + +/** + * Parses an authentication request from JSON body. + * + * @param request_body The raw JSON request body string + * @return AuthRequest struct with parsed client_id and api_key, or error_message if parsing failed + */ +DBPS_EXPORT AuthRequest ParseAuthRequest(const std::string& request_body); diff --git a/src/server/auth_utils_test.cpp b/src/server/auth_utils_test.cpp new file mode 100644 index 0000000..ed4574f --- /dev/null +++ b/src/server/auth_utils_test.cpp @@ -0,0 +1,87 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include "auth_utils.h" +#include +#include +#include + +// Test ClientCredentialStore initialization with map +TEST(AuthUtilsTest, InitWithMap) { + ClientCredentialStore store; + std::map credentials = { + {"client1", "key1"}, + {"client2", "key2"} + }; + + store.init(credentials); + + // Test GenerateJWT with valid credentials + auto token1 = store.GenerateJWT("client1", "key1"); + EXPECT_TRUE(token1.has_value()); + EXPECT_FALSE(token1.value().empty()); + + auto token2 = store.GenerateJWT("client2", "key2"); + EXPECT_TRUE(token2.has_value()); + EXPECT_FALSE(token2.value().empty()); + + // Test GenerateJWT with invalid credentials + auto token3 = store.GenerateJWT("client1", "wrong_key"); + EXPECT_FALSE(token3.has_value()); + + auto token4 = store.GenerateJWT("nonexistent", "key1"); + EXPECT_FALSE(token4.has_value()); +} + +// Test ParseAuthRequest +TEST(AuthUtilsTest, ParseAuthRequest) { + // Valid request + std::string valid_json = R"({"client_id": "test_client", "api_key": "test_key"})"; + AuthRequest auth_req = ParseAuthRequest(valid_json); + + EXPECT_FALSE(auth_req.error_message.has_value()); + EXPECT_EQ(auth_req.client_id, "test_client"); + EXPECT_EQ(auth_req.api_key, "test_key"); + + // Missing client_id + std::string missing_client_id = R"({"api_key": "test_key"})"; + auth_req = ParseAuthRequest(missing_client_id); + EXPECT_TRUE(auth_req.error_message.has_value()); + EXPECT_TRUE(auth_req.error_message.value().find("client_id") != std::string::npos); + + // Missing api_key + std::string missing_api_key = R"({"client_id": "test_client"})"; + auth_req = ParseAuthRequest(missing_api_key); + EXPECT_TRUE(auth_req.error_message.has_value()); + EXPECT_TRUE(auth_req.error_message.value().find("api_key") != std::string::npos); + + // Invalid JSON + std::string invalid_json = "{invalid json}"; + auth_req = ParseAuthRequest(invalid_json); + EXPECT_TRUE(auth_req.error_message.has_value()); +} + +// Test GenerateJWT with empty client_id +TEST(AuthUtilsTest, GenerateJWTEmptyClientId) { + ClientCredentialStore store; + std::map credentials = {{"client1", "key1"}}; + store.init(credentials); + + auto token = store.GenerateJWT("", "key1"); + EXPECT_FALSE(token.has_value()); +} + diff --git a/src/server/dbps_api_server.cpp b/src/server/dbps_api_server.cpp index bb574ff..37be2af 100644 --- a/src/server/dbps_api_server.cpp +++ b/src/server/dbps_api_server.cpp @@ -17,8 +17,10 @@ #include #include +#include #include "json_request.h" #include "encryption_sequencer.h" +#include "auth_utils.h" // Helper function to create error response crow::response CreateErrorResponse(const std::string& error_msg, int status_code = 400) { @@ -31,6 +33,17 @@ crow::response CreateErrorResponse(const std::string& error_msg, int status_code int main() { crow::SimpleApp app; + // Initialize credential store + // TODO: Make credentials file path configurable. + ClientCredentialStore credential_store; + const std::string credentials_file_path = "credentials.json"; // Default path + if (!credential_store.init(credentials_file_path)) { + std::cout << "Warning: Failed to load credentials file: " << credentials_file_path << std::endl; + std::cout << "Server will continue to run, but credentials will not be validated." << std::endl; + } else { + std::cout << "Credentials loaded successfully from: " << credentials_file_path << std::endl; + } + CROW_ROUTE(app, "/healthz")([] { return "OK"; }); @@ -42,6 +55,41 @@ int main() { return crow::response(200, response); }); + // Authentication endpoint - POST /auth + CROW_ROUTE(app, "/auth").methods("POST"_method)([&credential_store](const crow::request& req) { + // Parse authentication request + AuthRequest auth_req = ParseAuthRequest(req.body); + + // Check if parsing resulted in an error + if (auth_req.error_message.has_value()) { + return CreateErrorResponse(auth_req.error_message.value(), 400); + } + + // Log the request for debugging + std::cout << "=== /auth Request ===" << std::endl; + std::cout << "client_id: " << auth_req.client_id << std::endl; + std::cout << "====================" << std::endl; + + // Generate JWT token (validates credentials internally) + auto token = credential_store.GenerateJWT(auth_req.client_id, auth_req.api_key); + + if (!token.has_value()) { + return CreateErrorResponse("Invalid credentials", 401); + } + + // Create success response + crow::json::wvalue response; + response["token"] = token.value(); + response["token_type"] = "Bearer"; + response["expires_in"] = 14400; // 4 hours in seconds + + std::cout << "=== /auth Response (Success) ===" << std::endl; + std::cout << "Token generated for client_id: " << auth_req.client_id << std::endl; + std::cout << "=================================" << std::endl; + + return crow::response(200, response); + }); + // Encryption endpoint - POST /encrypt CROW_ROUTE(app, "/encrypt").methods("POST"_method)([](const crow::request& req) { // Parse and validate request using our new class From 85aa1f5489adf60a591864539a28ffae0fa5f527 Mon Sep 17 00:00:00 2001 From: Alejandro Valerio Date: Mon, 24 Nov 2025 08:04:56 -0600 Subject: [PATCH 3/8] Removing comment on CMakelist --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3aa7dda..d8225d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,7 +79,6 @@ FetchContent_Declare( FetchContent_Declare( jwt-cpp GIT_REPOSITORY https://github.com/Thalhammer/jwt-cpp.git - # ++++ Revise version before merging +++ GIT_TAG master ) From 8821932a1cf8d928039941a1664001326097570b Mon Sep 17 00:00:00 2001 From: Alejandro Valerio Date: Fri, 28 Nov 2025 13:37:17 -0600 Subject: [PATCH 4/8] - Adding new header file. --- src/server/auth_utils.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/server/auth_utils.h b/src/server/auth_utils.h index f63f610..d29bca7 100644 --- a/src/server/auth_utils.h +++ b/src/server/auth_utils.h @@ -17,10 +17,10 @@ // Note for Protegrity integration: // - This is a simplified Authentication module used to complete the client integration. -// - It is fully functional, but for production deployment a fully pledged Identity Provider can be used instead. +// - It is fully functional, but for production deployment a fully fledged Identity Provider can be used instead. // // TODO: Expand explanation below. -// - Note that no Arrow codebase changes would be needed to replace this with a fully pledged Identity Provider +// - Note that no Arrow codebase changes would be needed to replace this with a fully fledged Identity Provider // as the connection configuration specified on the application level is passed as-is to the DBPS agents for authentication. #pragma once @@ -41,7 +41,7 @@ */ class DBPS_EXPORT ClientCredentialStore { public: - ClientCredentialStore() = default; + ClientCredentialStore() = default; ~ClientCredentialStore() = default; /** From 9262d46f66627e8c72557a36b304609d69f67ad4 Mon Sep 17 00:00:00 2001 From: Alejandro Valerio Date: Sat, 29 Nov 2025 18:11:43 -0600 Subject: [PATCH 5/8] - Added validation of JWT token for protected endpoints. - Added additional tests. --- CMakeLists.txt | 1 + src/server/auth_utils.cpp | 79 ++++++++++++++++++++++++++++++- src/server/auth_utils.h | 22 +++++++++ src/server/auth_utils_test.cpp | 85 ++++++++++++++++++++++++++++++++++ src/server/dbps_api_server.cpp | 71 ++++++++++++++++++++++------ 5 files changed, 242 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fd2d3cf..6b50f72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,6 +191,7 @@ target_link_libraries(dbps_api_server ) target_include_directories(dbps_api_server PRIVATE ${CMAKE_BINARY_DIR}/_deps/crow-src/include + ${CMAKE_BINARY_DIR}/_deps/cxxopts-src/include ) # DBPA Remote Test Script executable diff --git a/src/server/auth_utils.cpp b/src/server/auth_utils.cpp index cabba7b..a139a43 100644 --- a/src/server/auth_utils.cpp +++ b/src/server/auth_utils.cpp @@ -35,6 +35,49 @@ static const int JWT_EXPIRATION_SECONDS = 4 * 60 * 60; // 14400 seconds void ClientCredentialStore::init(const std::map& credentials) { credentials_.clear(); credentials_ = credentials; + skip_credential_check_ = false; // Default: check credentials +} + +// Set the skip_credential_check flag. +void ClientCredentialStore::init(bool skip_credential_check) { + skip_credential_check_ = skip_credential_check; +} + +// Get the skip_credential_check flag. +bool ClientCredentialStore::GetSkipCredentialCheck() const { + return skip_credential_check_; +} + +// Forward declaration +std::optional VerifyJWT(const std::string& token); + +// VerifyJWTForEndpoint implementation +std::optional ClientCredentialStore::VerifyJWTForEndpoint(const std::string& authorization_header) const { + // Skip verification if credential checking is disabled + if (skip_credential_check_) { + return std::nullopt; + } + + // Extract Bearer token from Authorization header + std::optional token = std::nullopt; + const std::string bearer_prefix = "Bearer "; + if (authorization_header.length() > bearer_prefix.length() && + authorization_header.substr(0, bearer_prefix.length()) == bearer_prefix) { + token = authorization_header.substr(bearer_prefix.length()); + } + + // Verify JWT token + if (!token.has_value()) { + return "Unauthorized: JWT token is missing"; + } + + auto client_id = VerifyJWT(token.value()); + if (!client_id.has_value()) { + return "Unauthorized: Invalid JWT token"; + } + + std::cout << "JWT verified for client_id: " << client_id.value() << std::endl; + return std::nullopt; } // Initialize credential store from a JSON file @@ -60,6 +103,7 @@ bool ClientCredentialStore::init(const std::string& file_path) { // Clear existing credentials credentials_.clear(); + skip_credential_check_ = false; // Default: check credentials // Load each client_id:api_key pair for (auto& [client_id, api_key_value] : json_data.items()) { @@ -104,8 +148,10 @@ std::optional ClientCredentialStore::GenerateJWT(const std::string& return std::nullopt; } - // Validate credentials before generating JWT - if (!ValidateCredential(client_id, api_key)) { + // Validate credentials before generating JWT (unless skipping credential check) + if (skip_credential_check_) { + std::cerr << "Warning: Credential checking is skipped. Generating JWT without validation for client_id: " << client_id << std::endl; + } else if (!ValidateCredential(client_id, api_key)) { std::cerr << "Error generating JWT: Invalid credentials for client_id: " << client_id << std::endl; return std::nullopt; } @@ -173,3 +219,32 @@ AuthRequest ParseAuthRequest(const std::string& request_body) { } } +// VerifyJWT implementation +std::optional VerifyJWT(const std::string& token) { + try { + // Decode and verify the JWT token + auto decoded = jwt::decode(token); + + // Verify the signature using the same secret key + auto verifier = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{JWT_SECRET_KEY}); + + verifier.verify(decoded); + + // Extract client_id from the token payload + if (decoded.has_payload_claim("client_id")) { + auto client_id_claim = decoded.get_payload_claim("client_id"); + return client_id_claim.as_string(); + } else { + std::cerr << "Error verifying JWT: Missing client_id claim in token" << std::endl; + return std::nullopt; + } + } catch (const jwt::error::token_verification_exception& e) { + std::cerr << "Error verifying JWT: Token verification failed - " << e.what() << std::endl; + return std::nullopt; + } catch (const std::exception& e) { + std::cerr << "Error verifying JWT: " << e.what() << std::endl; + return std::nullopt; + } +} + diff --git a/src/server/auth_utils.h b/src/server/auth_utils.h index d29bca7..b4db614 100644 --- a/src/server/auth_utils.h +++ b/src/server/auth_utils.h @@ -57,6 +57,25 @@ class DBPS_EXPORT ClientCredentialStore { */ void init(const std::map& credentials); + /** + * Sets the skip_credential_check flag. + * @param skip_credential_check If true, credential validation will be skipped during GenerateJWT + */ + void init(bool skip_credential_check); + + /** + * Gets the skip_credential_check flag. + * @return true if credential validation is skipped, false otherwise + */ + bool GetSkipCredentialCheck() const; + + /** + * Verifies JWT token from Authorization header for protected endpoints. + * @param authorization_header The Authorization header value (e.g., "Bearer ") + * @return Error message if verification fails, or std::nullopt if verification succeeds + */ + std::optional VerifyJWTForEndpoint(const std::string& authorization_header) const; + /** * Generates a JWT token for the given client_id if the credentials are valid. * @@ -85,6 +104,9 @@ class DBPS_EXPORT ClientCredentialStore { // In-memory storage: client_id -> api_key std::map credentials_; + + // Flag to indicate if credential checking should be skipped during GenerateJWT + bool skip_credential_check_ = false; }; /** diff --git a/src/server/auth_utils_test.cpp b/src/server/auth_utils_test.cpp index ed4574f..a3898b4 100644 --- a/src/server/auth_utils_test.cpp +++ b/src/server/auth_utils_test.cpp @@ -85,3 +85,88 @@ TEST(AuthUtilsTest, GenerateJWTEmptyClientId) { EXPECT_FALSE(token.has_value()); } +// Test init with skip_credential_check flag +TEST(AuthUtilsTest, InitWithSkipCredentialCheck) { + ClientCredentialStore store; + std::map credentials = {{"client1", "key1"}}; + + // Initialize with credentials first + store.init(credentials); + + // Test with skip_credential_check = true + store.init(true); + + // Should succeed even with wrong api_key when skipping credential check + auto token1 = store.GenerateJWT("client1", "wrong_key"); + EXPECT_TRUE(token1.has_value()); + EXPECT_FALSE(token1.value().empty()); + + // Should succeed even with nonexistent client_id when skipping credential check + auto token2 = store.GenerateJWT("nonexistent", "any_key"); + EXPECT_TRUE(token2.has_value()); + EXPECT_FALSE(token2.value().empty()); + + // Test with skip_credential_check = false + store.init(false); + + // Should fail with wrong api_key when checking credentials + auto token3 = store.GenerateJWT("client1", "wrong_key"); + EXPECT_FALSE(token3.has_value()); + + // Should succeed with correct credentials + auto token4 = store.GenerateJWT("client1", "key1"); + EXPECT_TRUE(token4.has_value()); + EXPECT_FALSE(token4.value().empty()); +} + +// Test VerifyJWTForEndpoint with skip_credential_check = true +TEST(AuthUtilsTest, VerifyJWTForEndpointSkipCheck) { + ClientCredentialStore store; + store.init(true); // Skip credential checking + + // Should succeed (return nullopt) regardless of header when skipping check + auto result1 = store.VerifyJWTForEndpoint(""); + EXPECT_FALSE(result1.has_value()); + + auto result2 = store.VerifyJWTForEndpoint("Invalid header"); + EXPECT_FALSE(result2.has_value()); + + auto result3 = store.VerifyJWTForEndpoint("Bearer invalid_token"); + EXPECT_FALSE(result3.has_value()); +} + +// Test VerifyJWTForEndpoint with skip_credential_check = false +TEST(AuthUtilsTest, VerifyJWTForEndpointWithCheck) { + ClientCredentialStore store; + std::map credentials = {{"clientAAAA", "keyAAAA"}}; + store.init(credentials); // This sets skip_credential_check_ to false + + // Test with missing/empty Authorization header + auto result1 = store.VerifyJWTForEndpoint(""); + EXPECT_TRUE(result1.has_value()); + EXPECT_TRUE(result1.value().find("Unauthorized") != std::string::npos); + + // Test with invalid Bearer format (no "Bearer " prefix) + auto result2 = store.VerifyJWTForEndpoint("invalid_token"); + EXPECT_TRUE(result2.has_value()); + EXPECT_TRUE(result2.value().find("Unauthorized") != std::string::npos); + + // Test with invalid JWT token + auto result3 = store.VerifyJWTForEndpoint("Bearer invalid.jwt.token"); + EXPECT_TRUE(result3.has_value()); + EXPECT_TRUE(result3.value().find("Unauthorized") != std::string::npos); + + // Test with valid JWT token + auto token = store.GenerateJWT("clientAAAA", "keyAAAA"); + ASSERT_TRUE(token.has_value()); + + std::string bearer_token = "Bearer " + token.value(); + auto result4 = store.VerifyJWTForEndpoint(bearer_token); + EXPECT_FALSE(result4.has_value()); // Should succeed (return nullopt) + + // Test with valid JWT token but wrong format (missing space after Bearer) + std::string invalid_bearer = "Bearer" + token.value(); + auto result5 = store.VerifyJWTForEndpoint(invalid_bearer); + EXPECT_TRUE(result5.has_value()); + EXPECT_TRUE(result5.value().find("Unauthorized") != std::string::npos); +} diff --git a/src/server/dbps_api_server.cpp b/src/server/dbps_api_server.cpp index 8e0e2fa..1377944 100644 --- a/src/server/dbps_api_server.cpp +++ b/src/server/dbps_api_server.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "json_request.h" #include "encryption_sequencer.h" #include "auth_utils.h" @@ -30,28 +31,58 @@ crow::response CreateErrorResponse(const std::string& error_msg, int status_code return crow::response(status_code, error_response); } -int main() { - crow::SimpleApp app; +// Helper function to verify JWT token from request +// Returns error message if verification fails, or nullopt if verification succeeds +std::optional VerifyJWTFromRequest(const crow::request& req, const ClientCredentialStore& credential_store) { + std::string auth_header = ""; + auto auth_header_it = req.headers.find("Authorization"); + if (auth_header_it != req.headers.end()) { + auth_header = auth_header_it->second; + } + return credential_store.VerifyJWTForEndpoint(auth_header); +} +int main(int argc, char* argv[]) { + // Initialize credentials file path with parsed command line options + std::optional credentials_file_path = std::nullopt; + try { + cxxopts::Options options("dbps_api_server", "Data Batch Protection Service API Server"); + options.add_options() + ("c,credentials", "Path to credentials JSON file", cxxopts::value()); + auto result = options.parse(argc, argv); + if (result.count("credentials")) { + credentials_file_path = result["credentials"].as(); + } + } catch (const std::exception& e) { + std::cerr << "Error parsing command line options: " << e.what() << std::endl; + return 1; + } + // Initialize credential store - // TODO: Make credentials file path configurable. ClientCredentialStore credential_store; - const std::string credentials_file_path = "credentials.json"; // Default path - if (!credential_store.init(credentials_file_path)) { - std::cout << "Warning: Failed to load credentials file: " << credentials_file_path << std::endl; - std::cout << "Server will continue to run, but credentials will not be validated." << std::endl; + if (credentials_file_path.has_value()) { + // Load credentials from file + if (!credential_store.init(credentials_file_path.value())) { + std::cerr << "Error: Failed to load credentials file: " << credentials_file_path.value() << std::endl; + return 1; + } + std::cout << "Credentials loaded successfully from: " << credentials_file_path.value() << std::endl; } else { - std::cout << "Credentials loaded successfully from: " << credentials_file_path << std::endl; + // No credentials file provided, skip credential checking + credential_store.init(true); + std::cout << "No credentials file provided. Credential checking will be skipped." << std::endl; } + // Initialize API server + crow::SimpleApp app; + CROW_ROUTE(app, "/healthz")([] { - return "OK"; + return crow::response(200, "OK"); }); - CROW_ROUTE(app, "/statusz")([]{ + CROW_ROUTE(app, "/statusz")([&credential_store]{ crow::json::wvalue response; - response["status"] = "chill"; - response["system_settings"] = "set to thrill!"; + response["skip_credential_check"] = credential_store.GetSkipCredentialCheck(); return crow::response(200, response); }); @@ -91,7 +122,13 @@ int main() { }); // Encryption endpoint - POST /encrypt - CROW_ROUTE(app, "/encrypt").methods("POST"_method)([](const crow::request& req) { + CROW_ROUTE(app, "/encrypt").methods("POST"_method)([&credential_store](const crow::request& req) { + // Verify JWT token + auto auth_error = VerifyJWTFromRequest(req, credential_store); + if (auth_error.has_value()) { + return CreateErrorResponse(auth_error.value(), 401); + } + // Parse and validate request using our new class EncryptJsonRequest request; request.Parse(req.body); @@ -155,7 +192,13 @@ int main() { }); // Decryption endpoint - POST /decrypt - CROW_ROUTE(app, "/decrypt").methods("POST"_method)([](const crow::request& req) { + CROW_ROUTE(app, "/decrypt").methods("POST"_method)([&credential_store](const crow::request& req) { + // Verify JWT token + auto auth_error = VerifyJWTFromRequest(req, credential_store); + if (auth_error.has_value()) { + return CreateErrorResponse(auth_error.value(), 401); + } + // Parse and validate request using our new class DecryptJsonRequest request; request.Parse(req.body); From 77eefcc05c5e68bd48e0f5215bbfe86460dd61aa Mon Sep 17 00:00:00 2001 From: Alejandro Valerio Date: Sat, 29 Nov 2025 18:22:40 -0600 Subject: [PATCH 6/8] - Renamed /auth endpoint to /token. --- src/server/dbps_api_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/dbps_api_server.cpp b/src/server/dbps_api_server.cpp index 1377944..f2b347c 100644 --- a/src/server/dbps_api_server.cpp +++ b/src/server/dbps_api_server.cpp @@ -87,7 +87,7 @@ int main(int argc, char* argv[]) { }); // Authentication endpoint - POST /auth - CROW_ROUTE(app, "/auth").methods("POST"_method)([&credential_store](const crow::request& req) { + CROW_ROUTE(app, "/token").methods("POST"_method)([&credential_store](const crow::request& req) { // Parse authentication request AuthRequest auth_req = ParseAuthRequest(req.body); From 1727883f8b8b50494c2bea755a77cb893ea8f19b Mon Sep 17 00:00:00 2001 From: Alejandro Valerio Date: Sun, 30 Nov 2025 08:43:25 -0600 Subject: [PATCH 7/8] - Make details of authentication parameters transparent to the library users. - Added more tests. --- src/server/auth_utils.cpp | 165 ++++++++++++++++++++------------- src/server/auth_utils.h | 74 +++++++++------ src/server/auth_utils_test.cpp | 152 ++++++++++++++++++------------ src/server/dbps_api_server.cpp | 32 ++----- test_credentials.json | 5 + 5 files changed, 250 insertions(+), 178 deletions(-) create mode 100644 test_credentials.json diff --git a/src/server/auth_utils.cpp b/src/server/auth_utils.cpp index a139a43..6544773 100644 --- a/src/server/auth_utils.cpp +++ b/src/server/auth_utils.cpp @@ -43,43 +43,6 @@ void ClientCredentialStore::init(bool skip_credential_check) { skip_credential_check_ = skip_credential_check; } -// Get the skip_credential_check flag. -bool ClientCredentialStore::GetSkipCredentialCheck() const { - return skip_credential_check_; -} - -// Forward declaration -std::optional VerifyJWT(const std::string& token); - -// VerifyJWTForEndpoint implementation -std::optional ClientCredentialStore::VerifyJWTForEndpoint(const std::string& authorization_header) const { - // Skip verification if credential checking is disabled - if (skip_credential_check_) { - return std::nullopt; - } - - // Extract Bearer token from Authorization header - std::optional token = std::nullopt; - const std::string bearer_prefix = "Bearer "; - if (authorization_header.length() > bearer_prefix.length() && - authorization_header.substr(0, bearer_prefix.length()) == bearer_prefix) { - token = authorization_header.substr(bearer_prefix.length()); - } - - // Verify JWT token - if (!token.has_value()) { - return "Unauthorized: JWT token is missing"; - } - - auto client_id = VerifyJWT(token.value()); - if (!client_id.has_value()) { - return "Unauthorized: Invalid JWT token"; - } - - std::cout << "JWT verified for client_id: " << client_id.value() << std::endl; - return std::nullopt; -} - // Initialize credential store from a JSON file bool ClientCredentialStore::init(const std::string& file_path) { try { @@ -124,6 +87,11 @@ bool ClientCredentialStore::init(const std::string& file_path) { } } +// Get the skip_credential_check flag. +bool ClientCredentialStore::GetSkipCredentialCheck() const { + return skip_credential_check_; +} + void ClientCredentialStore::AddCredential(const std::string& client_id, const std::string& api_key) { credentials_[client_id] = api_key; } @@ -142,18 +110,21 @@ bool ClientCredentialStore::HasClientId(const std::string& client_id) const { // GenerateJWT implementation std::optional ClientCredentialStore::GenerateJWT(const std::string& client_id, const std::string& api_key) const { - // Validate that client_id is not empty - if (client_id.empty()) { - std::cerr << "Error generating JWT: client_id cannot be empty" << std::endl; - return std::nullopt; - } - - // Validate credentials before generating JWT (unless skipping credential check) + // Skip credential validation if flag is set if (skip_credential_check_) { - std::cerr << "Warning: Credential checking is skipped. Generating JWT without validation for client_id: " << client_id << std::endl; - } else if (!ValidateCredential(client_id, api_key)) { - std::cerr << "Error generating JWT: Invalid credentials for client_id: " << client_id << std::endl; - return std::nullopt; + std::cout << "Warning: Credential checking is skipped. Generating JWT without validation for client_id: " << client_id << std::endl; + } else { + // Validate that client_id is not empty + if (client_id.empty()) { + std::cout << "Error generating JWT: client_id cannot be empty" << std::endl; + return std::nullopt; + } + + // Validate credentials before generating JWT + if (!ValidateCredential(client_id, api_key)) { + std::cout << "Error generating JWT: Invalid credentials for client_id: " << client_id << std::endl; + return std::nullopt; + } } try { @@ -179,9 +150,9 @@ std::optional ClientCredentialStore::GenerateJWT(const std::string& } } -// ParseAuthRequest implementation -AuthRequest ParseAuthRequest(const std::string& request_body) { - AuthRequest auth_req; +// ParseTokenRequest implementation +TokenRequest ParseTokenRequest(const std::string& request_body) { + TokenRequest token_req; try { // Parse JSON request body using nlohmann::json @@ -189,34 +160,71 @@ AuthRequest ParseAuthRequest(const std::string& request_body) { // Validate that it's an object if (!json_body.is_object()) { - auth_req.error_message = "Invalid JSON in request body"; - return auth_req; + token_req.error_message = "Invalid JSON in request body"; + return token_req; } // Extract client_id from request if (json_body.contains("client_id") && json_body["client_id"].is_string()) { - auth_req.client_id = json_body["client_id"].get(); + token_req.client_id = json_body["client_id"].get(); } else { - auth_req.error_message = "Missing required field: client_id"; - return auth_req; + token_req.error_message = "Missing required field: client_id"; + return token_req; } // Extract api_key from request if (json_body.contains("api_key") && json_body["api_key"].is_string()) { - auth_req.api_key = json_body["api_key"].get(); + token_req.api_key = json_body["api_key"].get(); } else { - auth_req.error_message = "Missing required field: api_key"; - return auth_req; + token_req.error_message = "Missing required field: api_key"; + return token_req; } - return auth_req; + return token_req; } catch (const nlohmann::json::exception& e) { - auth_req.error_message = "Invalid JSON in request body: " + std::string(e.what()); - return auth_req; + token_req.error_message = "Invalid JSON in request body: " + std::string(e.what()); + return token_req; } catch (const std::exception& e) { - auth_req.error_message = "Error parsing request: " + std::string(e.what()); - return auth_req; + token_req.error_message = "Error parsing request: " + std::string(e.what()); + return token_req; + } +} + +// ProcessTokenRequest implementation +TokenResponse ClientCredentialStore::ProcessTokenRequest(const std::string& request_body) const { + TokenResponse response; + + // Parse token request + TokenRequest token_req = ParseTokenRequest(request_body); + + // Check if parsing resulted in an error + if (token_req.error_message.has_value()) { + response.error_message = token_req.error_message.value(); + response.error_status_code = 400; + return response; + } + + // Log the request for debugging + std::cout << "=== ProcessTokenRequest ===" << std::endl; + std::cout << "client_id: " << token_req.client_id << std::endl; + std::cout << "====================" << std::endl; + + // Generate JWT token (validates credentials internally) + auto token = GenerateJWT(token_req.client_id, token_req.api_key); + + if (!token.has_value()) { + response.error_message = "Invalid credentials"; + response.error_status_code = 401; + return response; } + + response.token = token.value(); + + std::cout << "=== ProcessTokenRequest (Success) ===" << std::endl; + std::cout << "Token generated for client_id: " << token_req.client_id << std::endl; + std::cout << "=================================" << std::endl; + + return response; } // VerifyJWT implementation @@ -248,3 +256,32 @@ std::optional VerifyJWT(const std::string& token) { } } +// VerifyTokenForEndpoint implementation +std::optional ClientCredentialStore::VerifyTokenForEndpoint(const std::string& authorization_header) const { + // Skip verification if credential checking is disabled + if (skip_credential_check_) { + return std::nullopt; + } + + // Extract Bearer token from Authorization header + std::optional token = std::nullopt; + const std::string bearer_prefix = "Bearer "; + if (authorization_header.length() > bearer_prefix.length() && + authorization_header.substr(0, bearer_prefix.length()) == bearer_prefix) { + token = authorization_header.substr(bearer_prefix.length()); + } + + // Verify JWT token + if (!token.has_value()) { + return "Unauthorized: JWT token is missing"; + } + + auto client_id = VerifyJWT(token.value()); + if (!client_id.has_value()) { + return "Unauthorized: Invalid JWT token"; + } + + std::cout << "JWT verified for client_id: " << client_id.value() << std::endl; + return std::nullopt; +} + diff --git a/src/server/auth_utils.h b/src/server/auth_utils.h index b4db614..cf47679 100644 --- a/src/server/auth_utils.h +++ b/src/server/auth_utils.h @@ -33,6 +33,29 @@ #define DBPS_EXPORT #endif +/** + * Structure to hold parsed token request data. + * + * Integration point for Protegrity: + * - This request can be updated with the production configuration for authentication or credentials checking. + * - The specific fields are transparent to the library users. The API call payload of the token request is passed as-is to the module, + * so library users don't parse the request payload. + */ + struct DBPS_EXPORT TokenRequest { + std::string client_id; + std::string api_key; + std::optional error_message; // Error message if parsing failed +}; + +/** + * Structure to hold token generation result. + */ +struct DBPS_EXPORT TokenResponse { + std::optional token; + std::optional error_message; + int error_status_code = 400; // HTTP status code for error response +}; + /** * ClientCredentialStore manages client_id to api_key mappings for authentication. * @@ -70,12 +93,32 @@ class DBPS_EXPORT ClientCredentialStore { bool GetSkipCredentialCheck() const; /** + * Processes a token request from JSON body and generates a JWT token. + * This method encapsulates all token request processing logic, hiding the details + * of TokenRequest structure from the caller. + * + * @param request_body The raw JSON request body string + * @return TokenResponse with token if successful, or error_message and error_status_code if failed + */ + TokenResponse ProcessTokenRequest(const std::string& request_body) const; + + /** * Verifies JWT token from Authorization header for protected endpoints. * @param authorization_header The Authorization header value (e.g., "Bearer ") * @return Error message if verification fails, or std::nullopt if verification succeeds */ - std::optional VerifyJWTForEndpoint(const std::string& authorization_header) const; + std::optional VerifyTokenForEndpoint(const std::string& authorization_header) const; + +private: + // Adds a client credential to the in-memory storage. + void AddCredential(const std::string& client_id, const std::string& api_key); + // Check if a client credential is valid before generating a JWT token. + bool ValidateCredential(const std::string& client_id, const std::string& api_key) const; + + // Check if a a credential for a client_id exists + bool HasClientId(const std::string& client_id) const; + /** * Generates a JWT token for the given client_id if the credentials are valid. * @@ -90,17 +133,7 @@ class DBPS_EXPORT ClientCredentialStore { * @param api_key The API key to validate against the stored credential * @return The JWT token as a string if credentials are valid, or std::nullopt on error or invalid credentials */ - std::optional GenerateJWT(const std::string& client_id, const std::string& api_key) const; - -private: - // Adds a client credential to the in-memory storage. - void AddCredential(const std::string& client_id, const std::string& api_key); - - // Check if a client credential is valid before generating a JWT token. - bool ValidateCredential(const std::string& client_id, const std::string& api_key) const; - - // Check if a a credential for a client_id exists - bool HasClientId(const std::string& client_id) const; + std::optional GenerateJWT(const std::string& client_id, const std::string& api_key) const; // In-memory storage: client_id -> api_key std::map credentials_; @@ -108,20 +141,3 @@ class DBPS_EXPORT ClientCredentialStore { // Flag to indicate if credential checking should be skipped during GenerateJWT bool skip_credential_check_ = false; }; - -/** - * Structure to hold parsed authentication request data. - */ -struct DBPS_EXPORT AuthRequest { - std::string client_id; - std::string api_key; - std::optional error_message; // Error message if parsing failed -}; - -/** - * Parses an authentication request from JSON body. - * - * @param request_body The raw JSON request body string - * @return AuthRequest struct with parsed client_id and api_key, or error_message if parsing failed - */ -DBPS_EXPORT AuthRequest ParseAuthRequest(const std::string& request_body); diff --git a/src/server/auth_utils_test.cpp b/src/server/auth_utils_test.cpp index a3898b4..bab5d8a 100644 --- a/src/server/auth_utils_test.cpp +++ b/src/server/auth_utils_test.cpp @@ -30,59 +30,80 @@ TEST(AuthUtilsTest, InitWithMap) { store.init(credentials); - // Test GenerateJWT with valid credentials - auto token1 = store.GenerateJWT("client1", "key1"); - EXPECT_TRUE(token1.has_value()); - EXPECT_FALSE(token1.value().empty()); - - auto token2 = store.GenerateJWT("client2", "key2"); - EXPECT_TRUE(token2.has_value()); - EXPECT_FALSE(token2.value().empty()); - - // Test GenerateJWT with invalid credentials - auto token3 = store.GenerateJWT("client1", "wrong_key"); - EXPECT_FALSE(token3.has_value()); - - auto token4 = store.GenerateJWT("nonexistent", "key1"); - EXPECT_FALSE(token4.has_value()); + // Test ProcessTokenRequest with valid credentials + std::string valid_json1 = R"({"client_id": "client1", "api_key": "key1"})"; + auto response1 = store.ProcessTokenRequest(valid_json1); + EXPECT_TRUE(response1.token.has_value()); + EXPECT_FALSE(response1.token.value().empty()); + EXPECT_FALSE(response1.error_message.has_value()); + + std::string valid_json2 = R"({"client_id": "client2", "api_key": "key2"})"; + auto response2 = store.ProcessTokenRequest(valid_json2); + EXPECT_TRUE(response2.token.has_value()); + EXPECT_FALSE(response2.token.value().empty()); + EXPECT_FALSE(response2.error_message.has_value()); + + // Test ProcessTokenRequest with invalid credentials + std::string invalid_json1 = R"({"client_id": "client1", "api_key": "wrong_key"})"; + auto response3 = store.ProcessTokenRequest(invalid_json1); + EXPECT_FALSE(response3.token.has_value()); + EXPECT_TRUE(response3.error_message.has_value()); + EXPECT_EQ(response3.error_status_code, 401); + + std::string invalid_json2 = R"({"client_id": "nonexistent", "api_key": "key1"})"; + auto response4 = store.ProcessTokenRequest(invalid_json2); + EXPECT_FALSE(response4.token.has_value()); + EXPECT_TRUE(response4.error_message.has_value()); + EXPECT_EQ(response4.error_status_code, 401); } -// Test ParseAuthRequest -TEST(AuthUtilsTest, ParseAuthRequest) { +// Test ProcessTokenRequest parsing and validation +TEST(AuthUtilsTest, ProcessTokenRequestParsing) { + ClientCredentialStore store; + std::map credentials = {{"test_client", "test_key"}}; + store.init(credentials); + // Valid request std::string valid_json = R"({"client_id": "test_client", "api_key": "test_key"})"; - AuthRequest auth_req = ParseAuthRequest(valid_json); - - EXPECT_FALSE(auth_req.error_message.has_value()); - EXPECT_EQ(auth_req.client_id, "test_client"); - EXPECT_EQ(auth_req.api_key, "test_key"); + auto response = store.ProcessTokenRequest(valid_json); + EXPECT_TRUE(response.token.has_value()); + EXPECT_FALSE(response.error_message.has_value()); // Missing client_id std::string missing_client_id = R"({"api_key": "test_key"})"; - auth_req = ParseAuthRequest(missing_client_id); - EXPECT_TRUE(auth_req.error_message.has_value()); - EXPECT_TRUE(auth_req.error_message.value().find("client_id") != std::string::npos); + response = store.ProcessTokenRequest(missing_client_id); + EXPECT_FALSE(response.token.has_value()); + EXPECT_TRUE(response.error_message.has_value()); + EXPECT_TRUE(response.error_message.value().find("client_id") != std::string::npos); + EXPECT_EQ(response.error_status_code, 400); // Missing api_key std::string missing_api_key = R"({"client_id": "test_client"})"; - auth_req = ParseAuthRequest(missing_api_key); - EXPECT_TRUE(auth_req.error_message.has_value()); - EXPECT_TRUE(auth_req.error_message.value().find("api_key") != std::string::npos); + response = store.ProcessTokenRequest(missing_api_key); + EXPECT_FALSE(response.token.has_value()); + EXPECT_TRUE(response.error_message.has_value()); + EXPECT_TRUE(response.error_message.value().find("api_key") != std::string::npos); + EXPECT_EQ(response.error_status_code, 400); // Invalid JSON std::string invalid_json = "{invalid json}"; - auth_req = ParseAuthRequest(invalid_json); - EXPECT_TRUE(auth_req.error_message.has_value()); + response = store.ProcessTokenRequest(invalid_json); + EXPECT_FALSE(response.token.has_value()); + EXPECT_TRUE(response.error_message.has_value()); + EXPECT_EQ(response.error_status_code, 400); } -// Test GenerateJWT with empty client_id -TEST(AuthUtilsTest, GenerateJWTEmptyClientId) { +// Test ProcessTokenRequest with empty client_id +TEST(AuthUtilsTest, ProcessTokenRequestEmptyClientId) { ClientCredentialStore store; std::map credentials = {{"client1", "key1"}}; store.init(credentials); - auto token = store.GenerateJWT("", "key1"); - EXPECT_FALSE(token.has_value()); + std::string empty_client_id_json = R"({"client_id": "", "api_key": "key1"})"; + auto response = store.ProcessTokenRequest(empty_client_id_json); + EXPECT_FALSE(response.token.has_value()); + EXPECT_TRUE(response.error_message.has_value()); + EXPECT_EQ(response.error_status_code, 401); } // Test init with skip_credential_check flag @@ -97,76 +118,85 @@ TEST(AuthUtilsTest, InitWithSkipCredentialCheck) { store.init(true); // Should succeed even with wrong api_key when skipping credential check - auto token1 = store.GenerateJWT("client1", "wrong_key"); - EXPECT_TRUE(token1.has_value()); - EXPECT_FALSE(token1.value().empty()); + std::string wrong_key_json = R"({"client_id": "client1", "api_key": "wrong_key"})"; + auto response1 = store.ProcessTokenRequest(wrong_key_json); + EXPECT_TRUE(response1.token.has_value()); + EXPECT_FALSE(response1.token.value().empty()); + EXPECT_FALSE(response1.error_message.has_value()); // Should succeed even with nonexistent client_id when skipping credential check - auto token2 = store.GenerateJWT("nonexistent", "any_key"); - EXPECT_TRUE(token2.has_value()); - EXPECT_FALSE(token2.value().empty()); + std::string nonexistent_json = R"({"client_id": "nonexistent", "api_key": "any_key"})"; + auto response2 = store.ProcessTokenRequest(nonexistent_json); + EXPECT_TRUE(response2.token.has_value()); + EXPECT_FALSE(response2.token.value().empty()); + EXPECT_FALSE(response2.error_message.has_value()); // Test with skip_credential_check = false store.init(false); // Should fail with wrong api_key when checking credentials - auto token3 = store.GenerateJWT("client1", "wrong_key"); - EXPECT_FALSE(token3.has_value()); + auto response3 = store.ProcessTokenRequest(wrong_key_json); + EXPECT_FALSE(response3.token.has_value()); + EXPECT_TRUE(response3.error_message.has_value()); + EXPECT_EQ(response3.error_status_code, 401); // Should succeed with correct credentials - auto token4 = store.GenerateJWT("client1", "key1"); - EXPECT_TRUE(token4.has_value()); - EXPECT_FALSE(token4.value().empty()); + std::string correct_json = R"({"client_id": "client1", "api_key": "key1"})"; + auto response4 = store.ProcessTokenRequest(correct_json); + EXPECT_TRUE(response4.token.has_value()); + EXPECT_FALSE(response4.token.value().empty()); + EXPECT_FALSE(response4.error_message.has_value()); } -// Test VerifyJWTForEndpoint with skip_credential_check = true -TEST(AuthUtilsTest, VerifyJWTForEndpointSkipCheck) { +// Test VerifyTokenForEndpoint with skip_credential_check = true +TEST(AuthUtilsTest, VerifyTokenForEndpointSkipCheck) { ClientCredentialStore store; store.init(true); // Skip credential checking // Should succeed (return nullopt) regardless of header when skipping check - auto result1 = store.VerifyJWTForEndpoint(""); + auto result1 = store.VerifyTokenForEndpoint(""); EXPECT_FALSE(result1.has_value()); - auto result2 = store.VerifyJWTForEndpoint("Invalid header"); + auto result2 = store.VerifyTokenForEndpoint("Invalid header"); EXPECT_FALSE(result2.has_value()); - auto result3 = store.VerifyJWTForEndpoint("Bearer invalid_token"); + auto result3 = store.VerifyTokenForEndpoint("Bearer invalid_token"); EXPECT_FALSE(result3.has_value()); } -// Test VerifyJWTForEndpoint with skip_credential_check = false -TEST(AuthUtilsTest, VerifyJWTForEndpointWithCheck) { +// Test VerifyTokenForEndpoint with skip_credential_check = false +TEST(AuthUtilsTest, VerifyTokenForEndpointWithCheck) { ClientCredentialStore store; std::map credentials = {{"clientAAAA", "keyAAAA"}}; store.init(credentials); // This sets skip_credential_check_ to false // Test with missing/empty Authorization header - auto result1 = store.VerifyJWTForEndpoint(""); + auto result1 = store.VerifyTokenForEndpoint(""); EXPECT_TRUE(result1.has_value()); EXPECT_TRUE(result1.value().find("Unauthorized") != std::string::npos); // Test with invalid Bearer format (no "Bearer " prefix) - auto result2 = store.VerifyJWTForEndpoint("invalid_token"); + auto result2 = store.VerifyTokenForEndpoint("invalid_token"); EXPECT_TRUE(result2.has_value()); EXPECT_TRUE(result2.value().find("Unauthorized") != std::string::npos); // Test with invalid JWT token - auto result3 = store.VerifyJWTForEndpoint("Bearer invalid.jwt.token"); + auto result3 = store.VerifyTokenForEndpoint("Bearer invalid.jwt.token"); EXPECT_TRUE(result3.has_value()); EXPECT_TRUE(result3.value().find("Unauthorized") != std::string::npos); // Test with valid JWT token - auto token = store.GenerateJWT("clientAAAA", "keyAAAA"); - ASSERT_TRUE(token.has_value()); + std::string valid_token_json = R"({"client_id": "clientAAAA", "api_key": "keyAAAA"})"; + auto token_response = store.ProcessTokenRequest(valid_token_json); + ASSERT_TRUE(token_response.token.has_value()); - std::string bearer_token = "Bearer " + token.value(); - auto result4 = store.VerifyJWTForEndpoint(bearer_token); + std::string bearer_token = "Bearer " + token_response.token.value(); + auto result4 = store.VerifyTokenForEndpoint(bearer_token); EXPECT_FALSE(result4.has_value()); // Should succeed (return nullopt) // Test with valid JWT token but wrong format (missing space after Bearer) - std::string invalid_bearer = "Bearer" + token.value(); - auto result5 = store.VerifyJWTForEndpoint(invalid_bearer); + std::string invalid_bearer = "Bearer" + token_response.token.value(); + auto result5 = store.VerifyTokenForEndpoint(invalid_bearer); EXPECT_TRUE(result5.has_value()); EXPECT_TRUE(result5.value().find("Unauthorized") != std::string::npos); } diff --git a/src/server/dbps_api_server.cpp b/src/server/dbps_api_server.cpp index f2b347c..96950b1 100644 --- a/src/server/dbps_api_server.cpp +++ b/src/server/dbps_api_server.cpp @@ -39,7 +39,7 @@ std::optional VerifyJWTFromRequest(const crow::request& req, const if (auth_header_it != req.headers.end()) { auth_header = auth_header_it->second; } - return credential_store.VerifyJWTForEndpoint(auth_header); + return credential_store.VerifyTokenForEndpoint(auth_header); } int main(int argc, char* argv[]) { @@ -86,38 +86,22 @@ int main(int argc, char* argv[]) { return crow::response(200, response); }); - // Authentication endpoint - POST /auth + // Token authentication endpoint - POST /token CROW_ROUTE(app, "/token").methods("POST"_method)([&credential_store](const crow::request& req) { - // Parse authentication request - AuthRequest auth_req = ParseAuthRequest(req.body); + // Process token request + TokenResponse token_response = credential_store.ProcessTokenRequest(req.body); - // Check if parsing resulted in an error - if (auth_req.error_message.has_value()) { - return CreateErrorResponse(auth_req.error_message.value(), 400); - } - - // Log the request for debugging - std::cout << "=== /auth Request ===" << std::endl; - std::cout << "client_id: " << auth_req.client_id << std::endl; - std::cout << "====================" << std::endl; - - // Generate JWT token (validates credentials internally) - auto token = credential_store.GenerateJWT(auth_req.client_id, auth_req.api_key); - - if (!token.has_value()) { - return CreateErrorResponse("Invalid credentials", 401); + // Check if processing resulted in an error + if (token_response.error_message.has_value()) { + return CreateErrorResponse(token_response.error_message.value(), token_response.error_status_code); } // Create success response crow::json::wvalue response; - response["token"] = token.value(); + response["token"] = token_response.token.value(); response["token_type"] = "Bearer"; response["expires_in"] = 14400; // 4 hours in seconds - std::cout << "=== /auth Response (Success) ===" << std::endl; - std::cout << "Token generated for client_id: " << auth_req.client_id << std::endl; - std::cout << "=================================" << std::endl; - return crow::response(200, response); }); diff --git a/test_credentials.json b/test_credentials.json new file mode 100644 index 0000000..54cfa50 --- /dev/null +++ b/test_credentials.json @@ -0,0 +1,5 @@ +{ + "client1": "api_key_1", + "client2": "api_key_2", + "test_client": "test_api_key" +} From f8e45c5d7cc520a24077e8caed4cfdcaf45562bc Mon Sep 17 00:00:00 2001 From: Alejandro Valerio Date: Sun, 30 Nov 2025 12:58:36 -0600 Subject: [PATCH 8/8] - Remove test_credentials.json file. - Add JWT secret key to command line options. --- src/server/auth_utils.cpp | 65 +++++++++++++++++----------------- src/server/auth_utils.h | 32 ++++++++++------- src/server/auth_utils_test.cpp | 32 ++++++++--------- src/server/dbps_api_server.cpp | 27 +++++++++----- test_credentials.json | 5 --- 5 files changed, 86 insertions(+), 75 deletions(-) delete mode 100644 test_credentials.json diff --git a/src/server/auth_utils.cpp b/src/server/auth_utils.cpp index 6544773..6a3bab8 100644 --- a/src/server/auth_utils.cpp +++ b/src/server/auth_utils.cpp @@ -22,25 +22,26 @@ #include #include -// Hardcoded JWT secret key for signing tokens -// TODO: Make this configurable (e.g., from environment variable or config file) -static const std::string JWT_SECRET_KEY = "default-secret-key-change-in-production"; - // JWT expiration time: 4 hours in seconds static const int JWT_EXPIRATION_SECONDS = 4 * 60 * 60; // 14400 seconds // ClientCredentialStore implementation +// Constructor +ClientCredentialStore::ClientCredentialStore(const std::string& jwt_secret_key) + : jwt_secret_key_(jwt_secret_key) { +} + // Initialize credential store from a given map. void ClientCredentialStore::init(const std::map& credentials) { credentials_.clear(); credentials_ = credentials; - skip_credential_check_ = false; // Default: check credentials + enable_credential_check_ = true; // Default: enable credential checking } -// Set the skip_credential_check flag. -void ClientCredentialStore::init(bool skip_credential_check) { - skip_credential_check_ = skip_credential_check; +// Set the enable_credential_check flag. +void ClientCredentialStore::init(bool enable_credential_check) { + enable_credential_check_ = enable_credential_check; } // Initialize credential store from a JSON file @@ -66,7 +67,7 @@ bool ClientCredentialStore::init(const std::string& file_path) { // Clear existing credentials credentials_.clear(); - skip_credential_check_ = false; // Default: check credentials + enable_credential_check_ = true; // Default: enable credential checking // Load each client_id:api_key pair for (auto& [client_id, api_key_value] : json_data.items()) { @@ -87,9 +88,9 @@ bool ClientCredentialStore::init(const std::string& file_path) { } } -// Get the skip_credential_check flag. -bool ClientCredentialStore::GetSkipCredentialCheck() const { - return skip_credential_check_; +// Get the enable_credential_check flag. +bool ClientCredentialStore::GetEnableCredentialCheck() const { + return enable_credential_check_; } void ClientCredentialStore::AddCredential(const std::string& client_id, const std::string& api_key) { @@ -110,21 +111,20 @@ bool ClientCredentialStore::HasClientId(const std::string& client_id) const { // GenerateJWT implementation std::optional ClientCredentialStore::GenerateJWT(const std::string& client_id, const std::string& api_key) const { - // Skip credential validation if flag is set - if (skip_credential_check_) { + // Validate that client_id is not empty + if (client_id.empty()) { + std::cout << "Error generating JWT: client_id cannot be empty" << std::endl; + return std::nullopt; + } + + // Skip credential validation if flag is not set + if (!enable_credential_check_) { std::cout << "Warning: Credential checking is skipped. Generating JWT without validation for client_id: " << client_id << std::endl; - } else { - // Validate that client_id is not empty - if (client_id.empty()) { - std::cout << "Error generating JWT: client_id cannot be empty" << std::endl; - return std::nullopt; - } - - // Validate credentials before generating JWT - if (!ValidateCredential(client_id, api_key)) { - std::cout << "Error generating JWT: Invalid credentials for client_id: " << client_id << std::endl; - return std::nullopt; - } + } + // Validate credentials before generating JWT + else if (!ValidateCredential(client_id, api_key)) { + std::cout << "Error generating JWT: Invalid credentials for client_id: " << client_id << std::endl; + return std::nullopt; } try { @@ -141,7 +141,7 @@ std::optional ClientCredentialStore::GenerateJWT(const std::string& .set_payload_claim("client_id", jwt::claim(client_id)) .set_issued_at(std::chrono::system_clock::from_time_t(now_seconds)) .set_expires_at(std::chrono::system_clock::from_time_t(exp_seconds)) - .sign(jwt::algorithm::hs256{JWT_SECRET_KEY}); + .sign(jwt::algorithm::hs256{jwt_secret_key_}); return token; } catch (const std::exception& e) { @@ -160,7 +160,7 @@ TokenRequest ParseTokenRequest(const std::string& request_body) { // Validate that it's an object if (!json_body.is_object()) { - token_req.error_message = "Invalid JSON in request body"; + token_req.error_message = "Invalid JSON in request body. Invalid format."; return token_req; } @@ -228,14 +228,14 @@ TokenResponse ClientCredentialStore::ProcessTokenRequest(const std::string& requ } // VerifyJWT implementation -std::optional VerifyJWT(const std::string& token) { +std::optional VerifyJWT(const std::string& token, const std::string& jwt_secret_key) { try { // Decode and verify the JWT token auto decoded = jwt::decode(token); // Verify the signature using the same secret key auto verifier = jwt::verify() - .allow_algorithm(jwt::algorithm::hs256{JWT_SECRET_KEY}); + .allow_algorithm(jwt::algorithm::hs256{jwt_secret_key}); verifier.verify(decoded); @@ -259,7 +259,7 @@ std::optional VerifyJWT(const std::string& token) { // VerifyTokenForEndpoint implementation std::optional ClientCredentialStore::VerifyTokenForEndpoint(const std::string& authorization_header) const { // Skip verification if credential checking is disabled - if (skip_credential_check_) { + if (!enable_credential_check_) { return std::nullopt; } @@ -276,7 +276,7 @@ std::optional ClientCredentialStore::VerifyTokenForEndpoint(const s return "Unauthorized: JWT token is missing"; } - auto client_id = VerifyJWT(token.value()); + auto client_id = VerifyJWT(token.value(), jwt_secret_key_); if (!client_id.has_value()) { return "Unauthorized: Invalid JWT token"; } @@ -284,4 +284,3 @@ std::optional ClientCredentialStore::VerifyTokenForEndpoint(const s std::cout << "JWT verified for client_id: " << client_id.value() << std::endl; return std::nullopt; } - diff --git a/src/server/auth_utils.h b/src/server/auth_utils.h index cf47679..c8334cb 100644 --- a/src/server/auth_utils.h +++ b/src/server/auth_utils.h @@ -17,10 +17,9 @@ // Note for Protegrity integration: // - This is a simplified Authentication module used to complete the client integration. -// - It is fully functional, but for production deployment a fully fledged Identity Provider can be used instead. +// - It is fully functional, but for production deployment a fully fledged Certificate Authority or Identity Provider can be used instead. // -// TODO: Expand explanation below. -// - Note that no Arrow codebase changes would be needed to replace this with a fully fledged Identity Provider +// - Note that no Arrow codebase changes would be needed to replace this with a fully fledged Certificate Authority (CA) or Identity Provider // as the connection configuration specified on the application level is passed as-is to the DBPS agents for authentication. #pragma once @@ -37,7 +36,7 @@ * Structure to hold parsed token request data. * * Integration point for Protegrity: - * - This request can be updated with the production configuration for authentication or credentials checking. + * - This request can be updated with a production configuration for authentication or credentials checking. * - The specific fields are transparent to the library users. The API call payload of the token request is passed as-is to the module, * so library users don't parse the request payload. */ @@ -64,7 +63,11 @@ struct DBPS_EXPORT TokenResponse { */ class DBPS_EXPORT ClientCredentialStore { public: - ClientCredentialStore() = default; + /** + * Constructor for ClientCredentialStore. + * @param jwt_secret_key The secret key used for signing and verifying JWT tokens + */ + explicit ClientCredentialStore(const std::string& jwt_secret_key); ~ClientCredentialStore() = default; /** @@ -81,16 +84,16 @@ class DBPS_EXPORT ClientCredentialStore { void init(const std::map& credentials); /** - * Sets the skip_credential_check flag. - * @param skip_credential_check If true, credential validation will be skipped during GenerateJWT + * Sets the enable_credential_check flag. + * @param enable_credential_check If true, credential validation will be enabled during GenerateJWT */ - void init(bool skip_credential_check); + void init(bool enable_credential_check); /** - * Gets the skip_credential_check flag. - * @return true if credential validation is skipped, false otherwise + * Gets the enable_credential_check flag. + * @return true if credential validation is enabled, false otherwise */ - bool GetSkipCredentialCheck() const; + bool GetEnableCredentialCheck() const; /** * Processes a token request from JSON body and generates a JWT token. @@ -138,6 +141,9 @@ class DBPS_EXPORT ClientCredentialStore { // In-memory storage: client_id -> api_key std::map credentials_; - // Flag to indicate if credential checking should be skipped during GenerateJWT - bool skip_credential_check_ = false; + // Flag to indicate if credential checking is enabled during GenerateJWT + bool enable_credential_check_ = true; + + // JWT secret key for signing and verifying tokens + std::string jwt_secret_key_; }; diff --git a/src/server/auth_utils_test.cpp b/src/server/auth_utils_test.cpp index bab5d8a..f8f44d4 100644 --- a/src/server/auth_utils_test.cpp +++ b/src/server/auth_utils_test.cpp @@ -22,7 +22,7 @@ // Test ClientCredentialStore initialization with map TEST(AuthUtilsTest, InitWithMap) { - ClientCredentialStore store; + ClientCredentialStore store("test-secret-key"); std::map credentials = { {"client1", "key1"}, {"client2", "key2"} @@ -59,7 +59,7 @@ TEST(AuthUtilsTest, InitWithMap) { // Test ProcessTokenRequest parsing and validation TEST(AuthUtilsTest, ProcessTokenRequestParsing) { - ClientCredentialStore store; + ClientCredentialStore store("test-secret-key"); std::map credentials = {{"test_client", "test_key"}}; store.init(credentials); @@ -95,7 +95,7 @@ TEST(AuthUtilsTest, ProcessTokenRequestParsing) { // Test ProcessTokenRequest with empty client_id TEST(AuthUtilsTest, ProcessTokenRequestEmptyClientId) { - ClientCredentialStore store; + ClientCredentialStore store("test-secret-key"); std::map credentials = {{"client1", "key1"}}; store.init(credentials); @@ -106,16 +106,16 @@ TEST(AuthUtilsTest, ProcessTokenRequestEmptyClientId) { EXPECT_EQ(response.error_status_code, 401); } -// Test init with skip_credential_check flag -TEST(AuthUtilsTest, InitWithSkipCredentialCheck) { - ClientCredentialStore store; +// Test init with enable_credential_check flag +TEST(AuthUtilsTest, InitWithEnableCredentialCheck) { + ClientCredentialStore store("test-secret-key"); std::map credentials = {{"client1", "key1"}}; // Initialize with credentials first store.init(credentials); - // Test with skip_credential_check = true - store.init(true); + // Test with enable_credential_check = false (skip checking) + store.init(false); // Should succeed even with wrong api_key when skipping credential check std::string wrong_key_json = R"({"client_id": "client1", "api_key": "wrong_key"})"; @@ -131,8 +131,8 @@ TEST(AuthUtilsTest, InitWithSkipCredentialCheck) { EXPECT_FALSE(response2.token.value().empty()); EXPECT_FALSE(response2.error_message.has_value()); - // Test with skip_credential_check = false - store.init(false); + // Test with enable_credential_check = true (enable checking) + store.init(true); // Should fail with wrong api_key when checking credentials auto response3 = store.ProcessTokenRequest(wrong_key_json); @@ -148,10 +148,10 @@ TEST(AuthUtilsTest, InitWithSkipCredentialCheck) { EXPECT_FALSE(response4.error_message.has_value()); } -// Test VerifyTokenForEndpoint with skip_credential_check = true +// Test VerifyTokenForEndpoint with enable_credential_check = false TEST(AuthUtilsTest, VerifyTokenForEndpointSkipCheck) { - ClientCredentialStore store; - store.init(true); // Skip credential checking + ClientCredentialStore store("test-secret-key"); + store.init(false); // Disable credential checking // Should succeed (return nullopt) regardless of header when skipping check auto result1 = store.VerifyTokenForEndpoint(""); @@ -164,11 +164,11 @@ TEST(AuthUtilsTest, VerifyTokenForEndpointSkipCheck) { EXPECT_FALSE(result3.has_value()); } -// Test VerifyTokenForEndpoint with skip_credential_check = false +// Test VerifyTokenForEndpoint with enable_credential_check = true TEST(AuthUtilsTest, VerifyTokenForEndpointWithCheck) { - ClientCredentialStore store; + ClientCredentialStore store("test-secret-key"); std::map credentials = {{"clientAAAA", "keyAAAA"}}; - store.init(credentials); // This sets skip_credential_check_ to false + store.init(credentials); // This sets enable_credential_check_ to true // Test with missing/empty Authorization header auto result1 = store.VerifyTokenForEndpoint(""); diff --git a/src/server/dbps_api_server.cpp b/src/server/dbps_api_server.cpp index 96950b1..7ce3c4d 100644 --- a/src/server/dbps_api_server.cpp +++ b/src/server/dbps_api_server.cpp @@ -43,23 +43,28 @@ std::optional VerifyJWTFromRequest(const crow::request& req, const } int main(int argc, char* argv[]) { - // Initialize credentials file path with parsed command line options + // Initialize credentials file path and JWT secret key with parsed command line options std::optional credentials_file_path = std::nullopt; + std::string jwt_secret_key = "default-secret-key-overwritten-by-command-line"; try { cxxopts::Options options("dbps_api_server", "Data Batch Protection Service API Server"); options.add_options() - ("c,credentials", "Path to credentials JSON file", cxxopts::value()); + ("c,credentials", "Path to credentials JSON file", cxxopts::value()) + ("j,jwt-secret", "JWT secret key for signing and verifying tokens", cxxopts::value()); auto result = options.parse(argc, argv); if (result.count("credentials")) { credentials_file_path = result["credentials"].as(); } + if (result.count("jwt-secret")) { + jwt_secret_key = result["jwt-secret"].as(); + } } catch (const std::exception& e) { std::cerr << "Error parsing command line options: " << e.what() << std::endl; return 1; } - // Initialize credential store - ClientCredentialStore credential_store; + // Initialize credential store with JWT secret key + ClientCredentialStore credential_store(jwt_secret_key); if (credentials_file_path.has_value()) { // Load credentials from file if (!credential_store.init(credentials_file_path.value())) { @@ -68,8 +73,8 @@ int main(int argc, char* argv[]) { } std::cout << "Credentials loaded successfully from: " << credentials_file_path.value() << std::endl; } else { - // No credentials file provided, skip credential checking - credential_store.init(true); + // No credentials file provided, disable credential checking + credential_store.init(false); std::cout << "No credentials file provided. Credential checking will be skipped." << std::endl; } @@ -80,9 +85,15 @@ int main(int argc, char* argv[]) { return crow::response(200, "OK"); }); - CROW_ROUTE(app, "/statusz")([&credential_store]{ + CROW_ROUTE(app, "/statusz")([&credential_store](const crow::request& req){ + // Verify JWT token + auto auth_error = VerifyJWTFromRequest(req, credential_store); + if (auth_error.has_value()) { + return CreateErrorResponse(auth_error.value(), 401); + } + crow::json::wvalue response; - response["skip_credential_check"] = credential_store.GetSkipCredentialCheck(); + response["enable_credential_check"] = credential_store.GetEnableCredentialCheck(); return crow::response(200, response); }); diff --git a/test_credentials.json b/test_credentials.json deleted file mode 100644 index 54cfa50..0000000 --- a/test_credentials.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "client1": "api_key_1", - "client2": "api_key_2", - "test_client": "test_api_key" -}