Skip to content

Commit b4a1f8b

Browse files
RahulHereRahulHere
authored andcommitted
Implement Client Configuration (#130)
- Added URL validation functions to check format and structure - Implemented URL normalization to remove trailing slashes - Enhanced client structure with request_timeout configuration - Validated JWKS URI and issuer URLs on client creation - Added support for cache_duration configuration option with validation - Added support for auto_refresh option with flexible boolean parsing - Added support for request_timeout option with range validation - Enhanced error reporting with detailed context for invalid configurations - Applied default values for optional parameters - Used configured timeout in JWKS fetching operations
1 parent 97f9722 commit b4a1f8b

File tree

1 file changed

+113
-15
lines changed

1 file changed

+113
-15
lines changed

src/c_api/mcp_c_auth_api.cc

Lines changed: 113 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,50 @@ static void clear_error() {
5757
g_last_error_context.clear();
5858
}
5959

60+
// ========================================================================
61+
// Configuration Validation Utilities
62+
// ========================================================================
63+
64+
// Validate URL format
65+
static bool is_valid_url(const std::string& url) {
66+
if (url.empty()) {
67+
return false;
68+
}
69+
70+
// Check for protocol
71+
if (url.find("http://") != 0 && url.find("https://") != 0) {
72+
return false;
73+
}
74+
75+
// Check for minimum URL structure (protocol://host)
76+
size_t protocol_end = url.find("://");
77+
if (protocol_end == std::string::npos) {
78+
return false;
79+
}
80+
81+
// Check there's something after protocol
82+
if (url.length() <= protocol_end + 3) {
83+
return false;
84+
}
85+
86+
// Check for valid host part
87+
std::string host_part = url.substr(protocol_end + 3);
88+
if (host_part.empty() || host_part[0] == '/' || host_part[0] == ':') {
89+
return false;
90+
}
91+
92+
return true;
93+
}
94+
95+
// Normalize URL (remove trailing slash)
96+
static std::string normalize_url(const std::string& url) {
97+
std::string normalized = url;
98+
while (!normalized.empty() && normalized.back() == '/') {
99+
normalized.pop_back();
100+
}
101+
return normalized;
102+
}
103+
60104
// ========================================================================
61105
// Memory Management Utilities
62106
// ========================================================================
@@ -652,10 +696,10 @@ static bool http_get_with_retry(const std::string& url,
652696
}
653697

654698
// Fetch JWKS from the specified URI with retry
655-
static bool fetch_jwks_json(const std::string& uri, std::string& response) {
699+
static bool fetch_jwks_json(const std::string& uri, std::string& response, int64_t timeout_seconds = 10) {
656700
http_client_config config;
657-
config.timeout = 10L;
658-
config.connect_timeout = 5L;
701+
config.timeout = timeout_seconds;
702+
config.connect_timeout = (timeout_seconds / 2 < 5) ? timeout_seconds / 2 : 5;
659703
config.verify_ssl = true;
660704

661705
http_retry_config retry;
@@ -935,8 +979,9 @@ static bool split_jwt(const std::string& token,
935979
struct mcp_auth_client {
936980
std::string jwks_uri;
937981
std::string issuer;
938-
int64_t cache_duration = 3600;
939-
bool auto_refresh = true;
982+
int64_t cache_duration = 3600; // Default: 1 hour
983+
bool auto_refresh = true; // Default: enabled
984+
int64_t request_timeout = 10; // Default: 10 seconds
940985

941986
// Cached JWT header info for last validated token
942987
std::string last_alg;
@@ -952,8 +997,13 @@ struct mcp_auth_client {
952997
std::mutex cache_mutex;
953998

954999
mcp_auth_client(const char* uri, const char* iss)
955-
: jwks_uri(uri ? uri : "")
956-
, issuer(iss ? iss : "") {}
1000+
: jwks_uri(uri ? normalize_url(uri) : "")
1001+
, issuer(iss ? normalize_url(iss) : "") {
1002+
// Apply configuration defaults
1003+
cache_duration = 3600; // 1 hour default
1004+
auto_refresh = true; // Auto-refresh enabled by default
1005+
request_timeout = 10; // 10 seconds default
1006+
}
9571007
};
9581008

9591009
struct mcp_auth_validation_options {
@@ -1133,7 +1183,7 @@ static void invalidate_cache(mcp_auth_client_t client) {
11331183
// Fetch and cache JWKS keys
11341184
static bool fetch_and_cache_jwks(mcp_auth_client_t client) {
11351185
std::string jwks_json;
1136-
if (!fetch_jwks_json(client->jwks_uri, jwks_json)) {
1186+
if (!fetch_jwks_json(client->jwks_uri, jwks_json, client->request_timeout)) {
11371187
set_client_error(client, MCP_AUTH_ERROR_JWKS_FETCH_FAILED,
11381188
"Failed to fetch JWKS",
11391189
"URI: " + client->jwks_uri + ", Error: " + g_last_error);
@@ -1329,6 +1379,22 @@ mcp_auth_error_t mcp_auth_client_create(
13291379
return MCP_AUTH_ERROR_INVALID_PARAMETER;
13301380
}
13311381

1382+
// Validate JWKS URI format
1383+
if (!is_valid_url(jwks_uri)) {
1384+
set_error_with_context(MCP_AUTH_ERROR_INVALID_CONFIG,
1385+
"Invalid JWKS URI format",
1386+
std::string("URI: ") + jwks_uri);
1387+
return MCP_AUTH_ERROR_INVALID_CONFIG;
1388+
}
1389+
1390+
// Validate issuer URL format
1391+
if (!is_valid_url(issuer)) {
1392+
set_error_with_context(MCP_AUTH_ERROR_INVALID_CONFIG,
1393+
"Invalid issuer URL format",
1394+
std::string("Issuer: ") + issuer);
1395+
return MCP_AUTH_ERROR_INVALID_CONFIG;
1396+
}
1397+
13321398
clear_error();
13331399

13341400
try {
@@ -1406,13 +1472,45 @@ mcp_auth_error_t mcp_auth_client_set_option(
14061472
clear_error();
14071473

14081474
std::string opt(option);
1409-
if (opt == "cache_duration") {
1410-
client->cache_duration = std::stoll(value);
1411-
} else if (opt == "auto_refresh") {
1412-
client->auto_refresh = (std::string(value) == "true");
1413-
} else {
1414-
set_error(MCP_AUTH_ERROR_INVALID_PARAMETER, "Unknown option: " + opt);
1415-
return MCP_AUTH_ERROR_INVALID_PARAMETER;
1475+
std::string val(value);
1476+
1477+
try {
1478+
if (opt == "cache_duration") {
1479+
int64_t duration = std::stoll(val);
1480+
if (duration < 0) {
1481+
set_error_with_context(MCP_AUTH_ERROR_INVALID_CONFIG,
1482+
"Invalid cache duration",
1483+
"Duration must be non-negative: " + val);
1484+
return MCP_AUTH_ERROR_INVALID_CONFIG;
1485+
}
1486+
client->cache_duration = duration;
1487+
1488+
} else if (opt == "auto_refresh") {
1489+
// Accept various boolean representations
1490+
std::transform(val.begin(), val.end(), val.begin(), ::tolower);
1491+
client->auto_refresh = (val == "true" || val == "1" || val == "yes" || val == "on");
1492+
1493+
} else if (opt == "request_timeout") {
1494+
int64_t timeout = std::stoll(val);
1495+
if (timeout <= 0 || timeout > 300) { // Max 5 minutes
1496+
set_error_with_context(MCP_AUTH_ERROR_INVALID_CONFIG,
1497+
"Invalid request timeout",
1498+
"Timeout must be between 1-300 seconds: " + val);
1499+
return MCP_AUTH_ERROR_INVALID_CONFIG;
1500+
}
1501+
client->request_timeout = timeout;
1502+
1503+
} else {
1504+
set_error_with_context(MCP_AUTH_ERROR_INVALID_PARAMETER,
1505+
"Unknown configuration option",
1506+
"Option: " + opt);
1507+
return MCP_AUTH_ERROR_INVALID_PARAMETER;
1508+
}
1509+
} catch (const std::exception& e) {
1510+
set_error_with_context(MCP_AUTH_ERROR_INVALID_CONFIG,
1511+
"Failed to parse option value",
1512+
"Option: " + opt + ", Value: " + val + ", Error: " + e.what());
1513+
return MCP_AUTH_ERROR_INVALID_CONFIG;
14161514
}
14171515

14181516
return MCP_AUTH_SUCCESS;

0 commit comments

Comments
 (0)