@@ -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,
935979struct 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
9591009struct mcp_auth_validation_options {
@@ -1133,7 +1183,7 @@ static void invalidate_cache(mcp_auth_client_t client) {
11331183// Fetch and cache JWKS keys
11341184static 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