diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7ac861393..da1bbefb3 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -347,7 +347,7 @@ jobs: - name: Install vcpkg dependencies run: | - ${{ github.workspace }}\vcpkg\vcpkg install cpprestsdk:x64-windows openssl:x64-windows boost-system:x64-windows boost-date-time:x64-windows boost-regex:x64-windows + ${{ github.workspace }}\vcpkg\vcpkg install cpp-httplib:x64-windows nlohmann-json:x64-windows openssl:x64-windows shell: cmd env: VCPKG_BINARY_SOURCES: 'clear;files,${{ github.workspace }}/vcpkg-binary-cache,readwrite' diff --git a/auth0_flutter/EXAMPLES.md b/auth0_flutter/EXAMPLES.md index 90b00dd43..9fb6099a0 100644 --- a/auth0_flutter/EXAMPLES.md +++ b/auth0_flutter/EXAMPLES.md @@ -503,7 +503,7 @@ cd C:\vcpkg setx VCPKG_ROOT "C:\vcpkg" ``` -The plugin's `vcpkg.json` manifest automatically pulls the required packages (`cpprestsdk`, `openssl`, `boost-system`, `boost-date-time`, `boost-regex`) at build time — no manual `vcpkg install` is needed. +The plugin's `vcpkg.json` manifest automatically pulls the required packages (`cpp-httplib`, `nlohmann-json`, `openssl`) at build time — no manual `vcpkg install` is needed. #### 2. Configure your app's CMakeLists.txt @@ -525,7 +525,7 @@ project(your_app LANGUAGES CXX) # ... rest of your CMakeLists.txt ... ``` -> ⚠️ The `CMAKE_TOOLCHAIN_FILE` line **must** appear before `project()`. If it appears after, CMake will have already configured the compiler and vcpkg packages will not be found, resulting in build errors like `Could not find a package configuration file provided by "cpprestsdk"`. +> ⚠️ The `CMAKE_TOOLCHAIN_FILE` line **must** appear before `project()`. If it appears after, CMake will have already configured the compiler and vcpkg packages will not be found, resulting in build errors like `Could not find a package configuration file provided by "httplib"`. #### 3. Register the custom URL scheme (protocol handler) diff --git a/auth0_flutter/windows/CMakeLists.txt b/auth0_flutter/windows/CMakeLists.txt index 3818a72af..b1f50ae19 100644 --- a/auth0_flutter/windows/CMakeLists.txt +++ b/auth0_flutter/windows/CMakeLists.txt @@ -4,12 +4,6 @@ # customers of the plugin. cmake_minimum_required(VERSION 3.14) -# CMP0167 (FindBoost deprecation) was introduced in CMake 3.27. -# Guard it so the file stays compatible with older CMake versions. -if(POLICY CMP0167) - cmake_policy(SET CMP0167 NEW) -endif() - #if (DEFINED ENV{VCPKG_ROOT} AND EXISTS "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake") # set(CMAKE_TOOLCHAIN_FILE "$ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake" # CACHE STRING "Vcpkg toolchain file") @@ -40,10 +34,10 @@ list(APPEND PLUGIN_SOURCES ) # === vcpkg dependencies === -# These are resolved via vcpkg.json automatically (cpprestsdk, boost) -find_package(cpprestsdk CONFIG REQUIRED) +# These are resolved via vcpkg.json automatically (cpp-httplib, nlohmann-json, openssl) +find_package(httplib CONFIG REQUIRED) +find_package(nlohmann_json CONFIG REQUIRED) find_package(OpenSSL REQUIRED) -find_package(Boost REQUIRED COMPONENTS system date_time regex) # Define the plugin library target only if flutter targets are available if(TARGET flutter) @@ -89,6 +83,7 @@ if(TARGET flutter) target_compile_definitions(${PLUGIN_NAME} PRIVATE _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING + CPPHTTPLIB_OPENSSL_SUPPORT ) # Source include directories and library dependencies. target_include_directories(${PLUGIN_NAME} INTERFACE @@ -100,12 +95,10 @@ if(TARGET flutter) target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin - cpprestsdk::cpprest + httplib::httplib + nlohmann_json::nlohmann_json OpenSSL::SSL OpenSSL::Crypto - Boost::system - Boost::date_time - Boost::regex ) # List of absolute paths to libraries that should be bundled with the plugin. @@ -187,17 +180,16 @@ if (AUTH0_FLUTTER_ENABLE_TESTS) target_compile_definitions(${TEST_RUNNER} PRIVATE _SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING + CPPHTTPLIB_OPENSSL_SUPPORT ) target_link_libraries(${TEST_RUNNER} PRIVATE gtest_main gmock - cpprestsdk::cpprest + httplib::httplib + nlohmann_json::nlohmann_json OpenSSL::SSL OpenSSL::Crypto - Boost::system - Boost::date_time - Boost::regex ) # Link flutter_wrapper_plugin if available (when building as part of Flutter app) diff --git a/auth0_flutter/windows/auth0_api_client.cpp b/auth0_flutter/windows/auth0_api_client.cpp index ac5c0668e..df6835d1a 100644 --- a/auth0_flutter/windows/auth0_api_client.cpp +++ b/auth0_flutter/windows/auth0_api_client.cpp @@ -1,12 +1,10 @@ #include "auth0_api_client.h" -#include +#include #include #include -using namespace web; - namespace auth0_flutter { @@ -65,15 +63,15 @@ static std::string GetWindowsVersion() std::string BuildAuth0ClientHeader(const std::string &name, const std::string &version) { - json::value env; - env[U("Windows")] = json::value::string(utility::conversions::to_string_t(GetWindowsVersion())); + nlohmann::json env; + env["Windows"] = GetWindowsVersion(); - json::value payload; - payload[U("name")] = json::value::string(utility::conversions::to_string_t(name)); - payload[U("version")] = json::value::string(utility::conversions::to_string_t(version)); - payload[U("env")] = env; + nlohmann::json payload; + payload["name"] = name; + payload["version"] = version; + payload["env"] = env; - return Base64UrlEncode(utility::conversions::to_utf8string(payload.serialize())); + return Base64UrlEncode(payload.dump()); } // --------------------------------------------------------------------------- diff --git a/auth0_flutter/windows/auth0_flutter_plugin.cpp b/auth0_flutter/windows/auth0_flutter_plugin.cpp index cc9db1b74..c9857fee4 100644 --- a/auth0_flutter/windows/auth0_flutter_plugin.cpp +++ b/auth0_flutter/windows/auth0_flutter_plugin.cpp @@ -61,7 +61,7 @@ namespace auth0_flutter // Pass a direct-call task runner. All operations posted through it // (ShellExecuteW, window focus, MethodResult callbacks) are safe to - // invoke from a pplx background thread, so no UI-thread dispatch is + // invoke from a PPL background thread, so no UI-thread dispatch is // required. This avoids depending on flutter::TaskRunner / GetTaskRunner() // which was only introduced in Flutter 3.7 and may not exist on all // build environments. diff --git a/auth0_flutter/windows/authentication_api_client.cpp b/auth0_flutter/windows/authentication_api_client.cpp index 49526c397..c57111046 100644 --- a/auth0_flutter/windows/authentication_api_client.cpp +++ b/auth0_flutter/windows/authentication_api_client.cpp @@ -1,12 +1,10 @@ #include "authentication_api_client.h" -#include +#include #include "token_decoder.h" #include "authentication_error.h" -using namespace web; - namespace auth0_flutter { @@ -15,12 +13,12 @@ Credentials AuthenticationApiClient::ExchangeCodeForTokens( const std::string &code, const std::string &codeVerifier) { - json::value body; - body[U("grant_type")] = json::value::string(U("authorization_code")); - body[U("client_id")] = json::value::string(utility::conversions::to_string_t(clientId())); - body[U("code")] = json::value::string(utility::conversions::to_string_t(code)); - body[U("redirect_uri")] = json::value::string(utility::conversions::to_string_t(redirectUri)); - body[U("code_verifier")] = json::value::string(utility::conversions::to_string_t(codeVerifier)); + nlohmann::json body; + body["grant_type"] = "authorization_code"; + body["client_id"] = clientId(); + body["code"] = code; + body["redirect_uri"] = redirectUri; + body["code_verifier"] = codeVerifier; try { diff --git a/auth0_flutter/windows/authentication_error.h b/auth0_flutter/windows/authentication_error.h index 8872f81fc..41d62f6af 100644 --- a/auth0_flutter/windows/authentication_error.h +++ b/auth0_flutter/windows/authentication_error.h @@ -10,7 +10,7 @@ #include #include #include -#include +#include namespace auth0_flutter { @@ -46,20 +46,20 @@ namespace auth0_flutter * - Legacy format: { "code": "...", "description": "..." } */ AuthenticationError( - const web::json::value &errorJson, + const nlohmann::json &errorJson, int statusCode) : std::runtime_error("An error occurred when trying to authenticate with the server."), statusCode_(statusCode) { // Try modern format first: "error" and "error_description" - if (errorJson.has_field(U("error"))) + if (errorJson.contains("error")) { - code_ = GetJsonString(errorJson, U("error")); + code_ = GetJsonString(errorJson, "error"); } - else if (errorJson.has_field(U("code"))) + else if (errorJson.contains("code")) { // Fallback to legacy format - code_ = GetJsonString(errorJson, U("code")); + code_ = GetJsonString(errorJson, "code"); } else { @@ -67,13 +67,13 @@ namespace auth0_flutter } // Try "error_description" first, then "description" - if (errorJson.has_field(U("error_description"))) + if (errorJson.contains("error_description")) { - description_ = GetJsonString(errorJson, U("error_description")); + description_ = GetJsonString(errorJson, "error_description"); } - else if (errorJson.has_field(U("description"))) + else if (errorJson.contains("description")) { - description_ = GetJsonString(errorJson, U("description")); + description_ = GetJsonString(errorJson, "description"); } else { @@ -110,10 +110,9 @@ namespace auth0_flutter */ std::string GetValue(const std::string &key) const { - utility::string_t wkey = utility::conversions::to_string_t(key); - if (errorJson_.has_field(wkey)) + if (errorJson_.contains(key)) { - return GetJsonString(errorJson_, wkey); + return GetJsonString(errorJson_, key); } return std::string(); } @@ -255,15 +254,15 @@ namespace auth0_flutter std::string code_; std::string description_; int statusCode_; - web::json::value errorJson_; + nlohmann::json errorJson_; static std::string GetJsonString( - const web::json::value &json, - const utility::string_t &key) + const nlohmann::json &json, + const std::string &key) { - if (json.has_field(key) && json.at(key).is_string()) + if (json.contains(key) && json.at(key).is_string()) { - return utility::conversions::to_utf8string(json.at(key).as_string()); + return json.at(key).get(); } return std::string(); } diff --git a/auth0_flutter/windows/id_token_signature_validator.cpp b/auth0_flutter/windows/id_token_signature_validator.cpp index 7ff51c9ad..483bcac1b 100644 --- a/auth0_flutter/windows/id_token_signature_validator.cpp +++ b/auth0_flutter/windows/id_token_signature_validator.cpp @@ -6,6 +6,12 @@ #include "id_token_signature_validator.h" #include "jwt_util.h" +// httplib.h must come before windows.h: it defines WIN32_LEAN_AND_MEAN and +// pulls in winsock2.h, which conflicts with the legacy winsock.h that +// windows.h otherwise includes. +#include +#include + #include #include #pragma comment(lib, "Crypt32.lib") @@ -15,15 +21,13 @@ #include #include -#include -#include - #include #include #include #include #include #include +#include #include namespace auth0_flutter @@ -37,7 +41,7 @@ namespace auth0_flutter { struct JwksCacheEntry { - web::json::value jwks; + nlohmann::json jwks; std::chrono::steady_clock::time_point fetchedAt; }; @@ -92,26 +96,64 @@ namespace auth0_flutter // JWKS fetching // ------------------------------------------------------------------------- - static web::json::value FetchJwksFromNetwork(const std::string &jwksUri) + // httplib::Client is constructed against a fixed scheme+host and takes a + // bare path per request, unlike cpprestsdk's http_client which accepted + // the full URL directly — split jwksUri into the two parts it needs. + static std::pair SplitJwksUri(const std::string &uri) + { + auto schemeEnd = uri.find("://"); + if (schemeEnd == std::string::npos) + { + throw IdTokenValidationException("Invalid JWKS URI: missing scheme"); + } + + auto pathStart = uri.find('/', schemeEnd + 3); + if (pathStart == std::string::npos) + { + return {uri, "/"}; + } + + return {uri.substr(0, pathStart), uri.substr(pathStart)}; + } + + static nlohmann::json FetchJwksFromNetwork(const std::string &jwksUri) { - web::http::client::http_client_config config; - config.set_timeout(std::chrono::seconds(10)); - web::http::client::http_client client( - utility::conversions::to_string_t(jwksUri), config); + auto [hostPart, path] = SplitJwksUri(jwksUri); + + httplib::Client client(hostPart); + client.set_connection_timeout(10, 0); + client.set_read_timeout(10, 0); - auto response = client.request(web::http::methods::GET).get(); + auto response = client.Get(path); - if (response.status_code() != web::http::status_codes::OK) + // cpp-httplib returns a null Result on connection failure instead of + // throwing like cpprestsdk did — translate that into the same + // exception type/message shape callers already expect. + if (!response) + { + throw IdTokenValidationException( + "Failed to fetch JWKS: " + httplib::to_string(response.error())); + } + + if (response->status != 200) { throw IdTokenValidationException( "Failed to fetch JWKS: HTTP " + - std::to_string(response.status_code())); + std::to_string(response->status)); } - return response.extract_json().get(); + try + { + return nlohmann::json::parse(response->body); + } + catch (const nlohmann::json::exception &ex) + { + throw IdTokenValidationException( + std::string("Failed to fetch JWKS: ") + ex.what()); + } } - static web::json::value FetchJwks(const std::string &jwksUri) + static nlohmann::json FetchJwks(const std::string &jwksUri) { auto now = std::chrono::steady_clock::now(); @@ -125,7 +167,7 @@ namespace auth0_flutter } } - web::json::value fresh = FetchJwksFromNetwork(jwksUri); + nlohmann::json fresh = FetchJwksFromNetwork(jwksUri); { std::lock_guard lock(g_jwksCacheMutex); @@ -136,35 +178,34 @@ namespace auth0_flutter } // Search JWKS for key matching kid. Validates RFC 7517 "use" and "alg" fields. - static web::json::value FindKeyByKid( - const web::json::value &jwks, + static nlohmann::json FindKeyByKid( + const nlohmann::json &jwks, const std::string &kid) { // Verify JWKS has "keys" array; throw if missing or not an array - if (!jwks.has_field(U("keys")) || !jwks.at(U("keys")).is_array()) + if (!jwks.contains("keys") || !jwks.at("keys").is_array()) { throw IdTokenValidationException("Invalid JWKS response: missing 'keys' array"); } // Iterate through each key in the JWKS keys array - for (const auto &jwk : jwks.at(U("keys")).as_array()) + for (const auto &jwk : jwks.at("keys")) { // Skip key if it doesn't have a "kid" field or "kid" is not a string - if (!jwk.has_field(U("kid")) || !jwk.at(U("kid")).is_string()) + if (!jwk.contains("kid") || !jwk.at("kid").is_string()) continue; - // Convert key's kid value to UTF-8 string - std::string jwkKid = - utility::conversions::to_utf8string(jwk.at(U("kid")).as_string()); + // Convert key's kid value to a string + std::string jwkKid = jwk.at("kid").get(); // Check if this key's kid matches the requested kid if (jwkKid == kid) { // Check "use" field if present (must be "sig" for signature verification, not "enc") - if (jwk.has_field(U("use")) && jwk.at(U("use")).is_string()) + if (jwk.contains("use") && jwk.at("use").is_string()) { // Convert "use" field to string - std::string use = utility::conversions::to_utf8string(jwk.at(U("use")).as_string()); + std::string use = jwk.at("use").get(); // Skip this key if "use" is not "sig" (prevents using encryption keys for signatures) if (use != "sig") { @@ -173,10 +214,10 @@ namespace auth0_flutter } // Check "alg" field if present (must be "RS256", our only supported algorithm) - if (jwk.has_field(U("alg")) && jwk.at(U("alg")).is_string()) + if (jwk.contains("alg") && jwk.at("alg").is_string()) { // Convert "alg" field to string - std::string alg = utility::conversions::to_utf8string(jwk.at(U("alg")).as_string()); + std::string alg = jwk.at("alg").get(); // Skip this key if algorithm is not RS256 (prevents using keys for unsupported algorithms) if (alg != "RS256") { @@ -190,7 +231,7 @@ namespace auth0_flutter } // Return null if no matching kid found after checking all keys - return web::json::value::null(); + return nlohmann::json(nullptr); } /** @@ -289,18 +330,18 @@ namespace auth0_flutter const std::string &alg, const std::string &signingInput, const std::vector &signatureBytes, - const web::json::value &jwk) + const nlohmann::json &jwk) { if (alg == "RS256") { - if (!jwk.has_field(U("n")) || !jwk.has_field(U("e"))) + if (!jwk.contains("n") || !jwk.contains("e")) { throw IdTokenValidationException( "JWK is missing required RSA key material (n, e)"); } - std::string nStr = utility::conversions::to_utf8string(jwk.at(U("n")).as_string()); - std::string eStr = utility::conversions::to_utf8string(jwk.at(U("e")).as_string()); + std::string nStr = jwk.at("n").get(); + std::string eStr = jwk.at("e").get(); std::vector modulusBytes, exponentBytes; try @@ -339,7 +380,7 @@ namespace auth0_flutter const std::string &jwksUri) { // --- 1. Extract algorithm and kid from JWT header --- - web::json::value header; + nlohmann::json header; try { header = DecodeJwtHeader(idToken); @@ -351,9 +392,9 @@ namespace auth0_flutter } std::string alg; - if (header.has_field(U("alg")) && header.at(U("alg")).is_string()) + if (header.contains("alg") && header.at("alg").is_string()) { - alg = utility::conversions::to_utf8string(header.at(U("alg")).as_string()); + alg = header.at("alg").get(); } if (alg.empty()) { @@ -371,9 +412,9 @@ namespace auth0_flutter } std::string kid; - if (header.has_field(U("kid")) && header.at(U("kid")).is_string()) + if (header.contains("kid") && header.at("kid").is_string()) { - kid = utility::conversions::to_utf8string(header.at(U("kid")).as_string()); + kid = header.at("kid").get(); } if (kid.empty()) { @@ -382,7 +423,7 @@ namespace auth0_flutter } // --- 2. Fetch JWKS --- - web::json::value jwks; + nlohmann::json jwks; try { jwks = FetchJwks(jwksUri); @@ -417,7 +458,7 @@ namespace auth0_flutter } // --- 3. Find JWK by kid --- - web::json::value jwk = FindKeyByKid(jwks, kid); + nlohmann::json jwk = FindKeyByKid(jwks, kid); if (jwk.is_null()) { diff --git a/auth0_flutter/windows/id_token_validator.cpp b/auth0_flutter/windows/id_token_validator.cpp index c6ff408f6..2b1dc82b5 100644 --- a/auth0_flutter/windows/id_token_validator.cpp +++ b/auth0_flutter/windows/id_token_validator.cpp @@ -28,69 +28,69 @@ namespace auth0_flutter * @brief Extract a required integer claim from JWT payload */ static int64_t GetRequiredIntClaim( - const web::json::value &payload, + const nlohmann::json &payload, const std::string &claimName) { - if (!payload.has_field(utility::conversions::to_string_t(claimName))) + if (!payload.contains(claimName)) { throw IdTokenValidationException("Missing required claim: " + claimName); } - const auto &field = payload.at(utility::conversions::to_string_t(claimName)); + const auto &field = payload.at(claimName); if (!field.is_number()) { throw IdTokenValidationException("Claim '" + claimName + "' is not a number"); } - return field.as_number().to_int64(); + return field.get(); } /** * @brief Extract an optional integer claim from JWT payload */ static std::optional GetOptionalIntClaim( - const web::json::value &payload, + const nlohmann::json &payload, const std::string &claimName) { - if (!payload.has_field(utility::conversions::to_string_t(claimName))) + if (!payload.contains(claimName)) { return std::nullopt; } - const auto &field = payload.at(utility::conversions::to_string_t(claimName)); + const auto &field = payload.at(claimName); if (!field.is_number()) { return std::nullopt; } - return field.as_number().to_int64(); + return field.get(); } /** * @brief Extract an optional string claim from JWT payload */ static std::optional GetOptionalStringClaim( - const web::json::value &payload, + const nlohmann::json &payload, const std::string &claimName) { - if (!payload.has_field(utility::conversions::to_string_t(claimName))) + if (!payload.contains(claimName)) { return std::nullopt; } - const auto &field = payload.at(utility::conversions::to_string_t(claimName)); + const auto &field = payload.at(claimName); if (!field.is_string()) { return std::nullopt; } - return utility::conversions::to_utf8string(field.as_string()); + return field.get(); } void ValidateIdToken( const std::string &idToken, const IdTokenValidationConfig &config, - web::json::value *outPayload) + nlohmann::json *outPayload) { if (idToken.empty()) { @@ -106,7 +106,7 @@ namespace auth0_flutter } // Decode JWT payload - web::json::value payload; + nlohmann::json payload; try { payload = DecodeJwtPayload(idToken); @@ -146,19 +146,19 @@ namespace auth0_flutter } } - // 3. Validate audience (aud) claim + // 3. Validate audience (aud) claim // aud can be a string or an array of strings. - if (!payload.has_field(U("aud"))) + if (!payload.contains("aud")) { throw IdTokenValidationException( "Audience (aud) claim must be a string or array of strings present in the ID token"); } - const auto &audField = payload.at(U("aud")); + const auto &audField = payload.at("aud"); if (audField.is_string()) { - std::string aud = utility::conversions::to_utf8string(audField.as_string()); + std::string aud = audField.get(); if (aud != config.audience) { std::ostringstream msg; @@ -169,8 +169,7 @@ namespace auth0_flutter } else if (audField.is_array()) { - const auto &audArray = audField.as_array(); - if (audArray.size() == 0) + if (audField.empty()) { throw IdTokenValidationException( "Audience (aud) claim must be a string or array of strings present in the ID token"); @@ -178,11 +177,11 @@ namespace auth0_flutter std::vector audValues; bool found = false; - for (const auto &v : audArray) + for (const auto &v : audField) { if (v.is_string()) { - std::string s = utility::conversions::to_utf8string(v.as_string()); + std::string s = v.get(); audValues.push_back(s); if (s == config.audience) found = true; } @@ -209,7 +208,7 @@ namespace auth0_flutter // 4. Validate expiration time (exp) claim { - if (!payload.has_field(U("exp"))) + if (!payload.contains("exp")) { throw IdTokenValidationException( "Expiration time (exp) claim must be a number present in the ID token"); @@ -226,7 +225,7 @@ namespace auth0_flutter } // 5. Validate issued at (iat) claim - if (!payload.has_field(U("iat"))) + if (!payload.contains("iat")) { throw IdTokenValidationException( "Issued At (iat) claim must be a number present in the ID token"); @@ -260,8 +259,8 @@ namespace auth0_flutter } } - // 7. Validate azp (Authorized Party) when aud has multiple values - if (audField.is_array() && audField.as_array().size() > 1) + // 7. Validate azp (Authorized Party) when aud has multiple values + if (audField.is_array() && audField.size() > 1) { auto azp = GetOptionalStringClaim(payload, "azp"); if (!azp.has_value()) diff --git a/auth0_flutter/windows/id_token_validator.h b/auth0_flutter/windows/id_token_validator.h index 7c512a7e7..5c615b3bb 100644 --- a/auth0_flutter/windows/id_token_validator.h +++ b/auth0_flutter/windows/id_token_validator.h @@ -11,7 +11,7 @@ #include #include #include -#include +#include namespace auth0_flutter { @@ -73,6 +73,6 @@ namespace auth0_flutter void ValidateIdToken( const std::string &idToken, const IdTokenValidationConfig &config, - web::json::value *payload = nullptr); + nlohmann::json *payload = nullptr); } // namespace auth0_flutter diff --git a/auth0_flutter/windows/jwt_util.cpp b/auth0_flutter/windows/jwt_util.cpp index 582687c90..3139473ce 100644 --- a/auth0_flutter/windows/jwt_util.cpp +++ b/auth0_flutter/windows/jwt_util.cpp @@ -75,36 +75,36 @@ JwtParts SplitJwt(const std::string &token) return {parts[0], parts[1], parts[2]}; } -web::json::value DecodeJwtHeader(const std::string &token) +nlohmann::json DecodeJwtHeader(const std::string &token) { auto parts = SplitJwt(token); auto decoded = Base64UrlDecode(parts.header); - return web::json::value::parse(decoded); + return nlohmann::json::parse(decoded); } -web::json::value DecodeJwtPayload(const std::string &token) +nlohmann::json DecodeJwtPayload(const std::string &token) { auto parts = SplitJwt(token); auto decoded = Base64UrlDecode(parts.payload); - return web::json::value::parse(decoded); + return nlohmann::json::parse(decoded); } -flutter::EncodableValue JsonToEncodable(const web::json::value &v) +flutter::EncodableValue JsonToEncodable(const nlohmann::json &v) { if (v.is_null()) return flutter::EncodableValue(); if (v.is_boolean()) - return flutter::EncodableValue(v.as_bool()); + return flutter::EncodableValue(v.get()); if (v.is_number()) - return flutter::EncodableValue(v.as_double()); + return flutter::EncodableValue(v.get()); if (v.is_string()) - return flutter::EncodableValue(utility::conversions::to_utf8string(v.as_string())); + return flutter::EncodableValue(v.get()); if (v.is_array()) { flutter::EncodableList list; - for (const auto &item : v.as_array()) + for (const auto &item : v) { list.push_back(JsonToEncodable(item)); } @@ -114,10 +114,9 @@ flutter::EncodableValue JsonToEncodable(const web::json::value &v) if (v.is_object()) { flutter::EncodableMap map; - for (const auto &kv : v.as_object()) + for (const auto &kv : v.items()) { - map[flutter::EncodableValue(utility::conversions::to_utf8string(kv.first))] = - JsonToEncodable(kv.second); + map[flutter::EncodableValue(kv.key())] = JsonToEncodable(kv.value()); } return flutter::EncodableValue(map); } diff --git a/auth0_flutter/windows/jwt_util.h b/auth0_flutter/windows/jwt_util.h index 580eead92..187304e8d 100644 --- a/auth0_flutter/windows/jwt_util.h +++ b/auth0_flutter/windows/jwt_util.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include @@ -13,7 +13,7 @@ struct JwtParts }; JwtParts SplitJwt(const std::string &token); -web::json::value DecodeJwtHeader(const std::string &token); -web::json::value DecodeJwtPayload(const std::string &token); +nlohmann::json DecodeJwtHeader(const std::string &token); +nlohmann::json DecodeJwtPayload(const std::string &token); -flutter::EncodableValue JsonToEncodable(const web::json::value &v); \ No newline at end of file +flutter::EncodableValue JsonToEncodable(const nlohmann::json &v); \ No newline at end of file diff --git a/auth0_flutter/windows/networking.cpp b/auth0_flutter/windows/networking.cpp index 664fa991f..a2bd0e1c0 100644 --- a/auth0_flutter/windows/networking.cpp +++ b/auth0_flutter/windows/networking.cpp @@ -1,10 +1,8 @@ #include "networking.h" -#include +#include -using namespace web; -using namespace web::http; -using namespace web::http::client; +#include namespace auth0_flutter { @@ -15,31 +13,33 @@ HttpNetworking::HttpNetworking(std::string baseUrl, int timeoutSeconds) NetworkResponse HttpNetworking::post( const std::string &path, - const web::json::value &body, + const nlohmann::json &body, const std::map &headers) { - http_client_config config; - config.set_timeout(std::chrono::seconds(timeoutSeconds_)); - - http_client client(utility::conversions::to_string_t(baseUrl_), config); - - http_request request(methods::POST); - request.set_request_uri(utility::conversions::to_string_t(path)); - request.headers().set_content_type(U("application/json")); + httplib::Client client(baseUrl_); + client.set_connection_timeout(timeoutSeconds_, 0); + client.set_read_timeout(timeoutSeconds_, 0); + client.set_write_timeout(timeoutSeconds_, 0); + httplib::Headers httpHeaders; for (const auto &[key, value] : headers) { - request.headers().add( - utility::conversions::to_string_t(key), - utility::conversions::to_string_t(value)); + httpHeaders.emplace(key, value); } - request.set_body(body); + auto res = client.Post(path, httpHeaders, body.dump(), "application/json"); - auto response = client.request(request).get(); - auto json = response.extract_json().get(); + // cpp-httplib returns a null Result on connection failure (DNS, refused + // connection, timeout before any response, etc.) instead of throwing like + // cpprestsdk did — translate that into an exception so callers (e.g. + // AuthenticationApiClient) can still catch it as a network error. + if (!res) + { + throw std::runtime_error( + "HTTP request failed: " + httplib::to_string(res.error())); + } - return {response.status_code(), json}; + return {res->status, nlohmann::json::parse(res->body)}; } } // namespace auth0_flutter diff --git a/auth0_flutter/windows/networking.h b/auth0_flutter/windows/networking.h index 8de6d1f5e..a27353dbb 100644 --- a/auth0_flutter/windows/networking.h +++ b/auth0_flutter/windows/networking.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include namespace auth0_flutter { @@ -11,7 +11,7 @@ namespace auth0_flutter struct NetworkResponse { int statusCode; - web::json::value body; + nlohmann::json body; }; class Networking @@ -21,7 +21,7 @@ class Networking virtual NetworkResponse post( const std::string &path, - const web::json::value &body, + const nlohmann::json &body, const std::map &headers = {}) = 0; }; @@ -32,7 +32,7 @@ class HttpNetworking : public Networking NetworkResponse post( const std::string &path, - const web::json::value &body, + const nlohmann::json &body, const std::map &headers = {}) override; private: diff --git a/auth0_flutter/windows/oauth_helpers.cpp b/auth0_flutter/windows/oauth_helpers.cpp index bd73da102..a5a743c12 100644 --- a/auth0_flutter/windows/oauth_helpers.cpp +++ b/auth0_flutter/windows/oauth_helpers.cpp @@ -16,22 +16,12 @@ #include #include #include -#include +#include // OpenSSL for PKCE #include #include -// cpprestsdk for HTTP listener and client -#include -#include -#include - -using namespace web; -using namespace web::http; -using namespace web::http::client; -using namespace web::http::experimental::listener; - namespace auth0_flutter { @@ -159,7 +149,7 @@ namespace auth0_flutter OAuthCallbackResult waitForAuthCode_CustomScheme( int timeoutSeconds, const std::string &expectedState, - pplx::cancellation_token ct, + concurrency::cancellation_token ct, const std::string &appCustomUrl) { static constexpr DWORD kStackBufChars = 2048; @@ -214,7 +204,7 @@ namespace auth0_flutter { if (ct.is_canceled()) { - throw pplx::task_canceled(); + throw concurrency::task_canceled(); } std::string uri = readAndClearEnv(); @@ -285,7 +275,7 @@ namespace auth0_flutter bool waitForLogoutCallback( const std::string &returnToUri, int timeoutSeconds, - pplx::cancellation_token ct) + concurrency::cancellation_token ct) { static constexpr DWORD kStackBufChars = 2048; @@ -339,7 +329,7 @@ namespace auth0_flutter { if (ct.is_canceled()) { - throw pplx::task_canceled(); + throw concurrency::task_canceled(); } std::string uri = readAndClearEnv(); diff --git a/auth0_flutter/windows/oauth_helpers.h b/auth0_flutter/windows/oauth_helpers.h index 9bd3a1dcf..82dfb3715 100644 --- a/auth0_flutter/windows/oauth_helpers.h +++ b/auth0_flutter/windows/oauth_helpers.h @@ -7,7 +7,7 @@ #include #include -#include +#include namespace auth0_flutter { @@ -84,7 +84,7 @@ namespace auth0_flutter OAuthCallbackResult waitForAuthCode_CustomScheme( int timeoutSeconds = 180, const std::string &expectedState = "", - pplx::cancellation_token ct = pplx::cancellation_token::none(), + concurrency::cancellation_token ct = concurrency::cancellation_token::none(), const std::string &appCustomUrl = kDefaultRedirectUri); /** @@ -106,6 +106,6 @@ namespace auth0_flutter bool waitForLogoutCallback( const std::string &returnToUri, int timeoutSeconds = 300, - pplx::cancellation_token ct = pplx::cancellation_token::none()); + concurrency::cancellation_token ct = concurrency::cancellation_token::none()); } // namespace auth0_flutter diff --git a/auth0_flutter/windows/request_handlers/web_auth/login_web_auth_request_handler.cpp b/auth0_flutter/windows/request_handlers/web_auth/login_web_auth_request_handler.cpp index 82a4f6b5c..d84b574c5 100644 --- a/auth0_flutter/windows/request_handlers/web_auth/login_web_auth_request_handler.cpp +++ b/auth0_flutter/windows/request_handlers/web_auth/login_web_auth_request_handler.cpp @@ -24,7 +24,7 @@ #include #include -#include +#include namespace auth0_flutter { @@ -360,13 +360,13 @@ namespace auth0_flutter // Cancel any previously running login task so a second call to handle() // does not leave a stale task that still holds a reference to the old // (now-replaced) MethodResult. - // pplx::cancellation_token has a private default constructor; it must + // concurrency::cancellation_token has a private default constructor; it must // be obtained from a cancellation_token_source or from ::none(). - pplx::cancellation_token token = pplx::cancellation_token::none(); + concurrency::cancellation_token token = concurrency::cancellation_token::none(); { std::lock_guard lock(_cts_mutex); _cts.cancel(); - _cts = pplx::cancellation_token_source{}; + _cts = concurrency::cancellation_token_source{}; token = _cts.get_token(); } @@ -374,10 +374,10 @@ namespace auth0_flutter auto taskRunner = ui_task_runner_; - // Run authentication on a cancellable pplx task to avoid blocking the + // Run authentication on a cancellable PPL task to avoid blocking the // Flutter UI thread. The cancellation token lets the destructor (or a // subsequent handle() call) abort a running flow cleanly. - pplx::create_task([taskRunner, sharedResult, + concurrency::create_task([taskRunner, sharedResult, clientId, domain, domainUrl, scopeStr, redirectUri, appCustomURL, audience, organizationId, invitationUrl, authTimeoutSeconds, leeway, maxAge, state, nonce, issuer, token, queryParams, auth0ClientHeader]() { try @@ -571,7 +571,7 @@ namespace auth0_flutter // Step 7: Validate ID token (OIDC compliance) // This validates issuer, audience, expiration, and other critical claims - web::json::value validatedPayload; + nlohmann::json validatedPayload; try { IdTokenValidationConfig validationConfig; @@ -659,7 +659,7 @@ namespace auth0_flutter }); } } - catch (const pplx::task_canceled &) + catch (const concurrency::task_canceled &) { // Cancellation was requested (engine shutdown or a subsequent // handle() call). result is no longer valid — exit silently. diff --git a/auth0_flutter/windows/request_handlers/web_auth/login_web_auth_request_handler.h b/auth0_flutter/windows/request_handlers/web_auth/login_web_auth_request_handler.h index 3ff0bf7f7..539230236 100644 --- a/auth0_flutter/windows/request_handlers/web_auth/login_web_auth_request_handler.h +++ b/auth0_flutter/windows/request_handlers/web_auth/login_web_auth_request_handler.h @@ -18,7 +18,7 @@ #define FLUTTER_PLUGIN_LOGIN_WEB_AUTH_REQUEST_HANDLER_H_ #include "web_auth_request_handler.h" -#include +#include #include #include @@ -58,7 +58,7 @@ namespace auth0_flutter explicit LoginWebAuthRequestHandler(std::function)> post_ui_task) : ui_task_runner_(std::move(post_ui_task)) {} - // Cancels any in-flight pplx task so the task body stops + // Cancels any in-flight PPL task so the task body stops // before it can touch the (now-destroyed) MethodResult. ~LoginWebAuthRequestHandler() override { std::lock_guard lock(_cts_mutex); @@ -77,7 +77,7 @@ namespace auth0_flutter private: std::function)> ui_task_runner_; std::mutex _cts_mutex; - pplx::cancellation_token_source _cts; + concurrency::cancellation_token_source _cts; }; } // namespace auth0_flutter diff --git a/auth0_flutter/windows/request_handlers/web_auth/logout_web_auth_request_handler.cpp b/auth0_flutter/windows/request_handlers/web_auth/logout_web_auth_request_handler.cpp index 489b93c6e..7e4d4193c 100644 --- a/auth0_flutter/windows/request_handlers/web_auth/logout_web_auth_request_handler.cpp +++ b/auth0_flutter/windows/request_handlers/web_auth/logout_web_auth_request_handler.cpp @@ -168,14 +168,14 @@ namespace auth0_flutter std::string logoutUrl = BuildLogoutUrl(httpsUrl(domain), clientId, returnTo, federated); // Create new cancellation token for this logout task - pplx::cancellation_token token = pplx::cancellation_token::none(); + concurrency::cancellation_token token = concurrency::cancellation_token::none(); { // Lock cancellation token source during update std::lock_guard lock(_cts_mutex); // Cancel any existing logout task _cts.cancel(); // Create new token source for this logout task - _cts = pplx::cancellation_token_source{}; + _cts = concurrency::cancellation_token_source{}; // Get cancellation token from new source token = _cts.get_token(); } @@ -187,7 +187,7 @@ namespace auth0_flutter // if the handler is destroyed while the background task is still running. auto taskRunner = ui_task_runner_; - pplx::create_task([taskRunner, sharedResult, logoutUrl, appCustomURL, token]() + concurrency::create_task([taskRunner, sharedResult, logoutUrl, appCustomURL, token]() { try { @@ -211,7 +211,7 @@ namespace auth0_flutter } } // Catch task cancellation (engine shutdown or subsequent handle() call) - catch (const pplx::task_canceled &) + catch (const concurrency::task_canceled &) { // Exit silently; result is no longer valid after cancellation } diff --git a/auth0_flutter/windows/request_handlers/web_auth/logout_web_auth_request_handler.h b/auth0_flutter/windows/request_handlers/web_auth/logout_web_auth_request_handler.h index 16e11caa8..95b9d27c9 100644 --- a/auth0_flutter/windows/request_handlers/web_auth/logout_web_auth_request_handler.h +++ b/auth0_flutter/windows/request_handlers/web_auth/logout_web_auth_request_handler.h @@ -17,7 +17,7 @@ #define FLUTTER_PLUGIN_LOGOUT_WEB_AUTH_REQUEST_HANDLER_H_ #include "web_auth_request_handler.h" -#include +#include #include #include @@ -68,7 +68,7 @@ namespace auth0_flutter private: std::function)> ui_task_runner_; std::mutex _cts_mutex; - pplx::cancellation_token_source _cts; + concurrency::cancellation_token_source _cts; }; diff --git a/auth0_flutter/windows/test/authentication_api_client_test.cpp b/auth0_flutter/windows/test/authentication_api_client_test.cpp index 3ffccd4ca..ddaecaff6 100644 --- a/auth0_flutter/windows/test/authentication_api_client_test.cpp +++ b/auth0_flutter/windows/test/authentication_api_client_test.cpp @@ -14,18 +14,18 @@ class MockNetworking : public Networking public: MOCK_METHOD(NetworkResponse, post, (const std::string &path, - const web::json::value &body, + const nlohmann::json &body, const HeaderMap &headers), (override)); }; -static web::json::value MakeTokenResponse() +static nlohmann::json MakeTokenResponse() { - web::json::value json; - json[U("access_token")] = web::json::value::string(U("test-access-token")); - json[U("id_token")] = web::json::value::string(U("test-id-token")); - json[U("token_type")] = web::json::value::string(U("Bearer")); - json[U("expires_in")] = web::json::value::number(86400); + nlohmann::json json; + json["access_token"] = "test-access-token"; + json["id_token"] = "test-id-token"; + json["token_type"] = "Bearer"; + json["expires_in"] = 86400; return json; } @@ -41,7 +41,7 @@ TEST_F(AuthenticationApiClientTest, ExchangeCodeForTokensSendsAuth0ClientHeader) HeaderMap capturedHeaders; EXPECT_CALL(*mockNet, post(::testing::_, ::testing::_, ::testing::_)) - .WillOnce([&](const std::string &, const web::json::value &, + .WillOnce([&](const std::string &, const nlohmann::json &, const HeaderMap &headers) { capturedHeaders = headers; return NetworkResponse{200, MakeTokenResponse()}; @@ -59,7 +59,7 @@ TEST_F(AuthenticationApiClientTest, ExchangeCodeForTokensOmitsHeaderWhenEmpty) HeaderMap capturedHeaders; EXPECT_CALL(*mockNet, post(::testing::_, ::testing::_, ::testing::_)) - .WillOnce([&](const std::string &, const web::json::value &, + .WillOnce([&](const std::string &, const nlohmann::json &, const HeaderMap &headers) { capturedHeaders = headers; return NetworkResponse{200, MakeTokenResponse()}; @@ -76,7 +76,7 @@ TEST_F(AuthenticationApiClientTest, ExchangeCodeForTokensPostsToCorrectPath) std::string capturedPath; EXPECT_CALL(*mockNet, post(::testing::_, ::testing::_, ::testing::_)) - .WillOnce([&](const std::string &path, const web::json::value &, + .WillOnce([&](const std::string &path, const nlohmann::json &, const HeaderMap &) { capturedPath = path; return NetworkResponse{200, MakeTokenResponse()}; @@ -90,10 +90,10 @@ TEST_F(AuthenticationApiClientTest, ExchangeCodeForTokensPostsToCorrectPath) TEST_F(AuthenticationApiClientTest, ExchangeCodeForTokensSendsCorrectBody) { - web::json::value capturedBody; + nlohmann::json capturedBody; EXPECT_CALL(*mockNet, post(::testing::_, ::testing::_, ::testing::_)) - .WillOnce([&](const std::string &, const web::json::value &body, + .WillOnce([&](const std::string &, const nlohmann::json &body, const HeaderMap &) { capturedBody = body; return NetworkResponse{200, MakeTokenResponse()}; @@ -102,17 +102,17 @@ TEST_F(AuthenticationApiClientTest, ExchangeCodeForTokensSendsCorrectBody) AuthenticationApiClient client("test.auth0.com", "client123", headerValue, mockNet); client.ExchangeCodeForTokens("https://callback", "auth-code", "verifier"); - EXPECT_EQ(utility::conversions::to_utf8string(capturedBody[U("grant_type")].as_string()), "authorization_code"); - EXPECT_EQ(utility::conversions::to_utf8string(capturedBody[U("client_id")].as_string()), "client123"); - EXPECT_EQ(utility::conversions::to_utf8string(capturedBody[U("code")].as_string()), "auth-code"); - EXPECT_EQ(utility::conversions::to_utf8string(capturedBody[U("redirect_uri")].as_string()), "https://callback"); - EXPECT_EQ(utility::conversions::to_utf8string(capturedBody[U("code_verifier")].as_string()), "verifier"); + EXPECT_EQ(capturedBody["grant_type"].get(), "authorization_code"); + EXPECT_EQ(capturedBody["client_id"].get(), "client123"); + EXPECT_EQ(capturedBody["code"].get(), "auth-code"); + EXPECT_EQ(capturedBody["redirect_uri"].get(), "https://callback"); + EXPECT_EQ(capturedBody["code_verifier"].get(), "verifier"); } TEST_F(AuthenticationApiClientTest, ExchangeCodeForTokensReturnsCredentials) { EXPECT_CALL(*mockNet, post(::testing::_, ::testing::_, ::testing::_)) - .WillOnce([](const std::string &, const web::json::value &, + .WillOnce([](const std::string &, const nlohmann::json &, const HeaderMap &) { return NetworkResponse{200, MakeTokenResponse()}; }); @@ -127,12 +127,12 @@ TEST_F(AuthenticationApiClientTest, ExchangeCodeForTokensReturnsCredentials) TEST_F(AuthenticationApiClientTest, ExchangeCodeForTokensThrowsOnApiError) { - web::json::value errorBody; - errorBody[U("error")] = web::json::value::string(U("invalid_grant")); - errorBody[U("error_description")] = web::json::value::string(U("Invalid authorization code")); + nlohmann::json errorBody; + errorBody["error"] = "invalid_grant"; + errorBody["error_description"] = "Invalid authorization code"; EXPECT_CALL(*mockNet, post(::testing::_, ::testing::_, ::testing::_)) - .WillOnce([&](const std::string &, const web::json::value &, + .WillOnce([&](const std::string &, const nlohmann::json &, const HeaderMap &) { return NetworkResponse{403, errorBody}; }); @@ -154,7 +154,7 @@ TEST_F(AuthenticationApiClientTest, ExchangeCodeForTokensThrowsOnApiError) TEST_F(AuthenticationApiClientTest, ExchangeCodeForTokensThrowsNetworkErrorOnException) { EXPECT_CALL(*mockNet, post(::testing::_, ::testing::_, ::testing::_)) - .WillOnce([](const std::string &, const web::json::value &, + .WillOnce([](const std::string &, const nlohmann::json &, const HeaderMap &) -> NetworkResponse { throw std::runtime_error("connection refused"); }); diff --git a/auth0_flutter/windows/test/authentication_error_test.cpp b/auth0_flutter/windows/test/authentication_error_test.cpp index 3c7aded0a..1986c1fb2 100644 --- a/auth0_flutter/windows/test/authentication_error_test.cpp +++ b/auth0_flutter/windows/test/authentication_error_test.cpp @@ -1,7 +1,7 @@ #include #include "authentication_error.h" -#include +#include using namespace auth0_flutter; @@ -162,9 +162,9 @@ TEST(AuthenticationErrorClassificationTest, IsRuleError) { /* ------------------------------------------------------------------ */ TEST(AuthenticationErrorJsonTest, ParsesModernFormat) { - web::json::value json; - json[U("error")] = web::json::value::string(U("invalid_grant")); - json[U("error_description")] = web::json::value::string(U("Wrong email or password.")); + nlohmann::json json; + json["error"] = "invalid_grant"; + json["error_description"] = "Wrong email or password."; AuthenticationError err(json, 400); EXPECT_EQ(err.GetCode(), "invalid_grant"); @@ -177,9 +177,9 @@ TEST(AuthenticationErrorJsonTest, ParsesModernFormat) { /* ------------------------------------------------------------------ */ TEST(AuthenticationErrorJsonTest, ParsesLegacyFormat) { - web::json::value json; - json[U("code")] = web::json::value::string(U("some_legacy_error")); - json[U("description")] = web::json::value::string(U("Legacy description.")); + nlohmann::json json; + json["code"] = "some_legacy_error"; + json["description"] = "Legacy description."; AuthenticationError err(json, 400); EXPECT_EQ(err.GetCode(), "some_legacy_error"); @@ -191,7 +191,7 @@ TEST(AuthenticationErrorJsonTest, ParsesLegacyFormat) { /* ------------------------------------------------------------------ */ TEST(AuthenticationErrorJsonTest, FallsBackToUnknownError) { - web::json::value json = web::json::value::object(); + nlohmann::json json = nlohmann::json::object(); AuthenticationError err(json, 500); EXPECT_EQ(err.GetCode(), "UNKNOWN_ERROR"); @@ -205,10 +205,10 @@ TEST(AuthenticationErrorJsonTest, FallsBackToUnknownError) { /* ------------------------------------------------------------------ */ TEST(AuthenticationErrorJsonTest, GetValueReturnsExtraField) { - web::json::value json; - json[U("error")] = web::json::value::string(U("mfa_required")); - json[U("error_description")] = web::json::value::string(U("MFA required.")); - json[U("mfa_token")] = web::json::value::string(U("tok_abc123")); + nlohmann::json json; + json["error"] = "mfa_required"; + json["error_description"] = "MFA required."; + json["mfa_token"] = "tok_abc123"; AuthenticationError err(json, 403); EXPECT_EQ(err.GetValue("mfa_token"), "tok_abc123"); @@ -234,28 +234,28 @@ TEST(AuthenticationErrorTest, GetStatusCodeReturnsCorrectValue) { // extra fields, so the string constructor can never return true. TEST(AuthenticationErrorClassificationTest, IsPasswordNotStrongEnough_True) { - web::json::value json; - json[U("error")] = web::json::value::string(U("invalid_password")); - json[U("error_description")] = web::json::value::string(U("Password is too weak.")); - json[U("name")] = web::json::value::string(U("PasswordStrengthError")); + nlohmann::json json; + json["error"] = "invalid_password"; + json["error_description"] = "Password is too weak."; + json["name"] = "PasswordStrengthError"; AuthenticationError err(json, 400); EXPECT_TRUE(err.IsPasswordNotStrongEnough()); } TEST(AuthenticationErrorClassificationTest, IsPasswordNotStrongEnough_WrongCode) { - web::json::value json; - json[U("error")] = web::json::value::string(U("access_denied")); - json[U("name")] = web::json::value::string(U("PasswordStrengthError")); + nlohmann::json json; + json["error"] = "access_denied"; + json["name"] = "PasswordStrengthError"; AuthenticationError err(json, 403); EXPECT_FALSE(err.IsPasswordNotStrongEnough()); } TEST(AuthenticationErrorClassificationTest, IsPasswordNotStrongEnough_WrongName) { - web::json::value json; - json[U("error")] = web::json::value::string(U("invalid_password")); - json[U("name")] = web::json::value::string(U("SomeOtherError")); + nlohmann::json json; + json["error"] = "invalid_password"; + json["name"] = "SomeOtherError"; AuthenticationError err(json, 400); EXPECT_FALSE(err.IsPasswordNotStrongEnough()); @@ -272,28 +272,28 @@ TEST(AuthenticationErrorClassificationTest, IsPasswordNotStrongEnough_NoNameFiel /* ------------------------------------------------------------------ */ TEST(AuthenticationErrorClassificationTest, IsPasswordAlreadyUsed_True) { - web::json::value json; - json[U("error")] = web::json::value::string(U("invalid_password")); - json[U("error_description")] = web::json::value::string(U("Password was already used.")); - json[U("name")] = web::json::value::string(U("PasswordHistoryError")); + nlohmann::json json; + json["error"] = "invalid_password"; + json["error_description"] = "Password was already used."; + json["name"] = "PasswordHistoryError"; AuthenticationError err(json, 400); EXPECT_TRUE(err.IsPasswordAlreadyUsed()); } TEST(AuthenticationErrorClassificationTest, IsPasswordAlreadyUsed_WrongName) { - web::json::value json; - json[U("error")] = web::json::value::string(U("invalid_password")); - json[U("name")] = web::json::value::string(U("PasswordStrengthError")); + nlohmann::json json; + json["error"] = "invalid_password"; + json["name"] = "PasswordStrengthError"; AuthenticationError err(json, 400); EXPECT_FALSE(err.IsPasswordAlreadyUsed()); } TEST(AuthenticationErrorClassificationTest, IsPasswordAlreadyUsed_WrongCode) { - web::json::value json; - json[U("error")] = web::json::value::string(U("other_error")); - json[U("name")] = web::json::value::string(U("PasswordHistoryError")); + nlohmann::json json; + json["error"] = "other_error"; + json["name"] = "PasswordHistoryError"; AuthenticationError err(json, 400); EXPECT_FALSE(err.IsPasswordAlreadyUsed()); @@ -357,16 +357,16 @@ TEST(AuthenticationErrorClassificationTest, RefreshTokenDeletedAndInvalidAreExcl } TEST(AuthenticationErrorClassificationTest, PasswordStrengthAndHistoryAreExclusive) { - web::json::value strengthJson; - strengthJson[U("error")] = web::json::value::string(U("invalid_password")); - strengthJson[U("name")] = web::json::value::string(U("PasswordStrengthError")); + nlohmann::json strengthJson; + strengthJson["error"] = "invalid_password"; + strengthJson["name"] = "PasswordStrengthError"; AuthenticationError strength(strengthJson, 400); EXPECT_TRUE(strength.IsPasswordNotStrongEnough()); EXPECT_FALSE(strength.IsPasswordAlreadyUsed()); - web::json::value historyJson; - historyJson[U("error")] = web::json::value::string(U("invalid_password")); - historyJson[U("name")] = web::json::value::string(U("PasswordHistoryError")); + nlohmann::json historyJson; + historyJson["error"] = "invalid_password"; + historyJson["name"] = "PasswordHistoryError"; AuthenticationError history(historyJson, 400); EXPECT_TRUE(history.IsPasswordAlreadyUsed()); EXPECT_FALSE(history.IsPasswordNotStrongEnough()); @@ -378,9 +378,9 @@ TEST(AuthenticationErrorClassificationTest, PasswordStrengthAndHistoryAreExclusi TEST(AuthenticationErrorJsonTest, ModernErrorWithLegacyDescriptionField) { // "error" present (modern) but "description" instead of "error_description" - web::json::value json; - json[U("error")] = web::json::value::string(U("invalid_grant")); - json[U("description")] = web::json::value::string(U("Legacy description field.")); + nlohmann::json json; + json["error"] = "invalid_grant"; + json["description"] = "Legacy description field."; AuthenticationError err(json, 400); EXPECT_EQ(err.GetCode(), "invalid_grant"); @@ -390,10 +390,10 @@ TEST(AuthenticationErrorJsonTest, ModernErrorWithLegacyDescriptionField) { TEST(AuthenticationErrorJsonTest, BothModernAndLegacyDescriptionPrefersModern) { // When both fields are present, "error_description" takes priority. - web::json::value json; - json[U("error")] = web::json::value::string(U("invalid_grant")); - json[U("error_description")] = web::json::value::string(U("Modern description.")); - json[U("description")] = web::json::value::string(U("Legacy description.")); + nlohmann::json json; + json["error"] = "invalid_grant"; + json["error_description"] = "Modern description."; + json["description"] = "Legacy description."; AuthenticationError err(json, 400); EXPECT_EQ(err.GetDescription(), "Modern description."); @@ -401,8 +401,8 @@ TEST(AuthenticationErrorJsonTest, BothModernAndLegacyDescriptionPrefersModern) { TEST(AuthenticationErrorJsonTest, FallbackDescriptionContainsCode) { // When neither description field is present the fallback embeds the code. - web::json::value json; - json[U("error")] = web::json::value::string(U("my_error_code")); + nlohmann::json json; + json["error"] = "my_error_code"; AuthenticationError err(json, 400); EXPECT_NE(err.GetDescription().find("my_error_code"), std::string::npos); diff --git a/auth0_flutter/windows/test/id_token_signature_validator_test.cpp b/auth0_flutter/windows/test/id_token_signature_validator_test.cpp index a330a4db7..3c409261f 100644 --- a/auth0_flutter/windows/test/id_token_signature_validator_test.cpp +++ b/auth0_flutter/windows/test/id_token_signature_validator_test.cpp @@ -22,14 +22,12 @@ #include "../id_token_signature_validator.h" #include "../id_token_validator.h" -#include +#include #include +#include #include - -using web::http::experimental::listener::http_listener; -using web::http::http_response; -using web::http::status_codes; +#include using namespace auth0_flutter; using ::testing::HasSubstr; @@ -236,7 +234,7 @@ TEST(IdTokenSignatureValidatorTest, HeaderIsValidBase64ButNotJsonThrowsDecodeMes TEST(IdTokenSignatureValidatorTest, HeaderIsJsonArrayNotObjectThrowsDecodeMessage) { - // A JSON array [] is valid JSON but not a JWT header object; cpprestsdk + // A JSON array [] is valid JSON but not a JWT header object; nlohmann::json // should reject it when accessing fields, or we catch the parse difference. std::string token = Base64UrlEncode("[1,2,3]") + ".payload.sig"; try @@ -672,46 +670,60 @@ TEST(IdTokenSignatureValidatorTest, Step3FailureThrowsIdTokenValidationException // which avoids long test timeouts. // // HTTP 5xx, malformed JSON, and missing 'keys' array are tested with a real -// cpprestsdk http_listener spun up inline. Each test binds a unique port so +// httplib::Server spun up inline. Each test binds a unique port so // that the in-process g_jwksCache cannot serve a stale entry from a prior run. // =========================================================================== // --------------------------------------------------------------------------- -// TestJwksServer — RAII HTTP listener for step-4 tests +// TestJwksServer — RAII HTTP server for step-4 tests // -// Starts a cpprestsdk http_listener on localhost:. Every GET request +// Starts an httplib::Server on localhost:. Every GET request // receives the caller-supplied HTTP status code and response body. -// Destructor closes the listener; any close() error is swallowed so that test +// Destructor stops the server and joins its background thread so that test // teardown is always clean. // --------------------------------------------------------------------------- class TestJwksServer { public: - TestJwksServer(int port, web::http::status_code status, const std::string &body) - : uri_("http://127.0.0.1:" + std::to_string(port) + "/jwks.json"), - listener_(utility::conversions::to_string_t(uri_)) + TestJwksServer(int port, int status, const std::string &body) + : uri_("http://127.0.0.1:" + std::to_string(port) + "/jwks.json") { - listener_.support( - [status, body](web::http::http_request req) + server_.Get("/jwks.json", + [status, body](const httplib::Request &, httplib::Response &res) { - http_response resp(status); - resp.set_body(body, "application/json"); - req.reply(resp); + res.status = status; + res.set_content(body, "application/json"); }); - listener_.open().wait(); + + serverThread_ = std::thread([this, port]() { + server_.listen("127.0.0.1", port); + }); + + // listen() runs the accept loop on the background thread above and + // only starts accepting once bound — wait for that so the test's + // immediate follow-up request doesn't race the bind. + while (!server_.is_running()) + { + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } } ~TestJwksServer() { - try { listener_.close().wait(); } catch (...) {} + server_.stop(); + if (serverThread_.joinable()) + { + serverThread_.join(); + } } const std::string &uri() const { return uri_; } private: - std::string uri_; - http_listener listener_; + std::string uri_; + httplib::Server server_; + std::thread serverThread_; }; // Build a JWT whose header passes steps 1-3 so the validator reaches step 4. @@ -751,7 +763,7 @@ TEST(IdTokenSignatureValidatorJwksTest, ThrowsWhenJwksEndpointIsUnreachable) TEST(IdTokenSignatureValidatorJwksTest, ThrowsIdTokenValidationExceptionNotStdException) { // A network failure must be wrapped in IdTokenValidationException, not - // propagated as a raw cpprestsdk or std::exception. + // propagated as a raw httplib or std::exception. const std::string unreachableJwksUri = "http://127.0.0.1:9/jwks.json"; @@ -779,7 +791,7 @@ TEST(IdTokenSignatureValidatorJwksTest, ThrowsOnHttp5xxResponse) { // Listener returns HTTP 500; FetchJwksFromNetwork should throw // IdTokenValidationException("Failed to fetch JWKS: HTTP 500"). - TestJwksServer server(19081, status_codes::InternalError, ""); + TestJwksServer server(19081, 500, ""); std::string token = MakeValidHeaderJwt("kid-5xx"); try @@ -803,7 +815,7 @@ TEST(IdTokenSignatureValidatorJwksTest, ThrowsOnMalformedJsonResponse) // Listener returns HTTP 200 with a body that is not valid JSON. // extract_json() throws; the catch in ValidateIdTokenSignature wraps it as // "Failed to fetch JWKS: ". - TestJwksServer server(19082, status_codes::OK, "not-valid-json!!!"); + TestJwksServer server(19082, 200, "not-valid-json!!!"); std::string token = MakeValidHeaderJwt("kid-malformed"); try @@ -827,7 +839,7 @@ TEST(IdTokenSignatureValidatorJwksTest, ThrowsWhenJwksResponseMissingKeysArray) // Listener returns a valid JSON object that has no "keys" field. // FindKeyByKid detects this and throws // IdTokenValidationException("Invalid JWKS response: missing 'keys' array"). - TestJwksServer server(19083, status_codes::OK, R"({"foo":"bar"})"); + TestJwksServer server(19083, 200, R"({"foo":"bar"})"); std::string token = MakeValidHeaderJwt("kid-no-keys"); try @@ -875,7 +887,7 @@ TEST(IdTokenSignatureValidatorJwksTest, SkipsKeyWithUseEncryption) ] })"; - TestJwksServer server(19084, status_codes::OK, jwksJson); + TestJwksServer server(19084, 200, jwksJson); std::string token = MakeValidHeaderJwt("test-kid"); try @@ -910,7 +922,7 @@ TEST(IdTokenSignatureValidatorJwksTest, SkipsKeyWithWrongAlgorithm) ] })"; - TestJwksServer server(19085, status_codes::OK, jwksJson); + TestJwksServer server(19085, 200, jwksJson); std::string token = MakeValidHeaderJwt("test-kid"); try @@ -941,7 +953,7 @@ TEST(IdTokenSignatureValidatorJwksTest, AcceptsKeyWithoutUseField) ] })"; - TestJwksServer server(19086, status_codes::OK, jwksJson); + TestJwksServer server(19086, 200, jwksJson); std::string token = MakeValidHeaderJwt("test-kid"); try diff --git a/auth0_flutter/windows/test/id_token_validator_test.cpp b/auth0_flutter/windows/test/id_token_validator_test.cpp index f3d457b20..fad17770e 100644 --- a/auth0_flutter/windows/test/id_token_validator_test.cpp +++ b/auth0_flutter/windows/test/id_token_validator_test.cpp @@ -1,7 +1,7 @@ #include #include "../id_token_validator.h" #include -#include +#include using namespace auth0_flutter; @@ -51,15 +51,15 @@ static std::string SimpleBase64UrlEncode(const std::string &input) * @brief Helper to create a simple unsigned JWT for testing * Note: These are NOT cryptographically secure - only for validation testing */ -static std::string CreateTestJWT(const web::json::value &payload) +static std::string CreateTestJWT(const nlohmann::json &payload) { // Create a simple JWT header - web::json::value header; - header[U("alg")] = web::json::value::string(U("HS256")); - header[U("typ")] = web::json::value::string(U("JWT")); + nlohmann::json header; + header["alg"] = "HS256"; + header["typ"] = "JWT"; - std::string headerStr = utility::conversions::to_utf8string(header.serialize()); - std::string payloadStr = utility::conversions::to_utf8string(payload.serialize()); + std::string headerStr = header.dump(); + std::string payloadStr = payload.dump(); std::string encodedHeader = SimpleBase64UrlEncode(headerStr); std::string encodedPayload = SimpleBase64UrlEncode(payloadStr); @@ -83,12 +83,12 @@ TEST(IdTokenValidatorTest, ValidatesValidToken) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); // Valid for 1 hour - payload[U("iat")] = web::json::value::number(now - 10); // Issued 10 seconds ago - payload[U("sub")] = web::json::value::string(U("auth0|123")); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["aud"] = "test_client_id"; + payload["exp"] = now + 3600; // Valid for 1 hour + payload["iat"] = now - 10; // Issued 10 seconds ago + payload["sub"] = "auth0|123"; std::string jwt = CreateTestJWT(payload); @@ -105,19 +105,19 @@ TEST(IdTokenValidatorTest, ValidatesTokenWithArrayAudience) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["sub"] = "auth0|123"; // Audience as array — azp is required when there are multiple entries (step 7) - web::json::value audArray = web::json::value::array(); - audArray[0] = web::json::value::string(U("test_client_id")); - audArray[1] = web::json::value::string(U("other_audience")); - payload[U("aud")] = audArray; - payload[U("azp")] = web::json::value::string(U("test_client_id")); + nlohmann::json audArray = nlohmann::json::array(); + audArray[0] = "test_client_id"; + audArray[1] = "other_audience"; + payload["aud"] = audArray; + payload["azp"] = "test_client_id"; - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); + payload["exp"] = now + 3600; + payload["iat"] = now - 10; std::string jwt = CreateTestJWT(payload); @@ -133,12 +133,12 @@ TEST(IdTokenValidatorTest, ValidatesTokenWithLeeway) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now - 30); // Expired 30 seconds ago - payload[U("iat")] = web::json::value::number(now - 3630); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["sub"] = "auth0|123"; + payload["aud"] = "test_client_id"; + payload["exp"] = now - 30; // Expired 30 seconds ago + payload["iat"] = now - 3630; std::string jwt = CreateTestJWT(payload); @@ -156,12 +156,12 @@ TEST(IdTokenValidatorTest, RejectsInvalidIssuer) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://evil.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); + nlohmann::json payload; + payload["iss"] = "https://evil.com/"; + payload["sub"] = "auth0|123"; + payload["aud"] = "test_client_id"; + payload["exp"] = now + 3600; + payload["iat"] = now - 10; std::string jwt = CreateTestJWT(payload); @@ -178,12 +178,12 @@ TEST(IdTokenValidatorTest, RejectsMissingIssuer) { int64_t now = GetNow(); - web::json::value payload; + nlohmann::json payload; // Missing iss claim - payload[U("sub")] = web::json::value::string(U("auth0|123")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); + payload["sub"] = "auth0|123"; + payload["aud"] = "test_client_id"; + payload["exp"] = now + 3600; + payload["iat"] = now - 10; std::string jwt = CreateTestJWT(payload); @@ -202,12 +202,12 @@ TEST(IdTokenValidatorTest, RejectsInvalidAudience) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); - payload[U("aud")] = web::json::value::string(U("wrong_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["sub"] = "auth0|123"; + payload["aud"] = "wrong_client_id"; + payload["exp"] = now + 3600; + payload["iat"] = now - 10; std::string jwt = CreateTestJWT(payload); @@ -224,12 +224,12 @@ TEST(IdTokenValidatorTest, RejectsMissingAudience) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["sub"] = "auth0|123"; // Missing aud claim - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); + payload["exp"] = now + 3600; + payload["iat"] = now - 10; std::string jwt = CreateTestJWT(payload); @@ -246,17 +246,17 @@ TEST(IdTokenValidatorTest, RejectsArrayAudienceWithoutMatch) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["sub"] = "auth0|123"; - web::json::value audArray = web::json::value::array(); - audArray[0] = web::json::value::string(U("other_client")); - audArray[1] = web::json::value::string(U("another_client")); - payload[U("aud")] = audArray; + nlohmann::json audArray = nlohmann::json::array(); + audArray[0] = "other_client"; + audArray[1] = "another_client"; + payload["aud"] = audArray; - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); + payload["exp"] = now + 3600; + payload["iat"] = now - 10; std::string jwt = CreateTestJWT(payload); @@ -275,12 +275,12 @@ TEST(IdTokenValidatorTest, RejectsExpiredToken) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now - 120); // Expired 2 minutes ago - payload[U("iat")] = web::json::value::number(now - 3720); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["sub"] = "auth0|123"; + payload["aud"] = "test_client_id"; + payload["exp"] = now - 120; // Expired 2 minutes ago + payload["iat"] = now - 3720; std::string jwt = CreateTestJWT(payload); @@ -298,12 +298,12 @@ TEST(IdTokenValidatorTest, RejectsMissingExpiration) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["sub"] = "auth0|123"; + payload["aud"] = "test_client_id"; // Missing exp claim - payload[U("iat")] = web::json::value::number(now - 10); + payload["iat"] = now - 10; std::string jwt = CreateTestJWT(payload); @@ -322,12 +322,12 @@ TEST(IdTokenValidatorTest, RejectsTokenIssuedInFuture) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now + 120); // Issued 2 minutes in future + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["sub"] = "auth0|123"; + payload["aud"] = "test_client_id"; + payload["exp"] = now + 3600; + payload["iat"] = now + 120; // Issued 2 minutes in future std::string jwt = CreateTestJWT(payload); @@ -345,11 +345,11 @@ TEST(IdTokenValidatorTest, RejectsMissingIssuedAt) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["sub"] = "auth0|123"; + payload["aud"] = "test_client_id"; + payload["exp"] = now + 3600; // Missing iat claim std::string jwt = CreateTestJWT(payload); @@ -369,13 +369,13 @@ TEST(IdTokenValidatorTest, ValidatesAuthTimeWithMaxAge) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); - payload[U("auth_time")] = web::json::value::number(now - 300); // Authenticated 5 minutes ago + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["sub"] = "auth0|123"; + payload["aud"] = "test_client_id"; + payload["exp"] = now + 3600; + payload["iat"] = now - 10; + payload["auth_time"] = now - 300; // Authenticated 5 minutes ago std::string jwt = CreateTestJWT(payload); @@ -391,13 +391,13 @@ TEST(IdTokenValidatorTest, RejectsOldAuthenticationWithMaxAge) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); - payload[U("auth_time")] = web::json::value::number(now - 700); // Authenticated 11.7 minutes ago + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["sub"] = "auth0|123"; + payload["aud"] = "test_client_id"; + payload["exp"] = now + 3600; + payload["iat"] = now - 10; + payload["auth_time"] = now - 700; // Authenticated 11.7 minutes ago std::string jwt = CreateTestJWT(payload); @@ -416,12 +416,12 @@ TEST(IdTokenValidatorTest, RejectsMissingAuthTimeWhenMaxAgeSpecified) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["sub"] = "auth0|123"; + payload["aud"] = "test_client_id"; + payload["exp"] = now + 3600; + payload["iat"] = now - 10; // Missing auth_time std::string jwt = CreateTestJWT(payload); @@ -442,13 +442,13 @@ TEST(IdTokenValidatorTest, ValidatesMatchingNonce) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); - payload[U("nonce")] = web::json::value::string(U("test_nonce_123")); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["sub"] = "auth0|123"; + payload["aud"] = "test_client_id"; + payload["exp"] = now + 3600; + payload["iat"] = now - 10; + payload["nonce"] = "test_nonce_123"; std::string jwt = CreateTestJWT(payload); @@ -464,13 +464,13 @@ TEST(IdTokenValidatorTest, RejectsMismatchedNonce) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); - payload[U("nonce")] = web::json::value::string(U("wrong_nonce")); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["sub"] = "auth0|123"; + payload["aud"] = "test_client_id"; + payload["exp"] = now + 3600; + payload["iat"] = now - 10; + payload["nonce"] = "wrong_nonce"; std::string jwt = CreateTestJWT(payload); @@ -488,12 +488,12 @@ TEST(IdTokenValidatorTest, RejectsMissingNonceWhenExpected) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("sub")] = web::json::value::string(U("auth0|123")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["sub"] = "auth0|123"; + payload["aud"] = "test_client_id"; + payload["exp"] = now + 3600; + payload["iat"] = now - 10; // Missing nonce std::string jwt = CreateTestJWT(payload); @@ -539,11 +539,11 @@ TEST(IdTokenValidatorTest, RejectsMalformedToken) * The signature is a dummy value (not cryptographically valid). */ static std::string CreateTestJWTWithCustomHeader( - const web::json::value &header, - const web::json::value &payload) + const nlohmann::json &header, + const nlohmann::json &payload) { - std::string headerStr = utility::conversions::to_utf8string(header.serialize()); - std::string payloadStr = utility::conversions::to_utf8string(payload.serialize()); + std::string headerStr = header.dump(); + std::string payloadStr = payload.dump(); return SimpleBase64UrlEncode(headerStr) + "." + SimpleBase64UrlEncode(payloadStr) + ".dummy_signature"; } @@ -557,16 +557,16 @@ TEST(IdTokenValidatorTest, RejectsUnsupportedAlgorithmWhenJwksUriSet) int64_t now = GetNow(); // Header declares HS256 – only RS256 is accepted - web::json::value header; - header[U("alg")] = web::json::value::string(U("HS256")); - header[U("typ")] = web::json::value::string(U("JWT")); - header[U("kid")] = web::json::value::string(U("key-1")); + nlohmann::json header; + header["alg"] = "HS256"; + header["typ"] = "JWT"; + header["kid"] = "key-1"; - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["aud"] = "test_client_id"; + payload["exp"] = now + 3600; + payload["iat"] = now - 10; std::string jwt = CreateTestJWTWithCustomHeader(header, payload); @@ -584,15 +584,15 @@ TEST(IdTokenValidatorTest, RejectsMissingAlgorithmWhenJwksUriSet) int64_t now = GetNow(); // Header has no "alg" field at all - web::json::value header; - header[U("typ")] = web::json::value::string(U("JWT")); - header[U("kid")] = web::json::value::string(U("key-1")); + nlohmann::json header; + header["typ"] = "JWT"; + header["kid"] = "key-1"; - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["aud"] = "test_client_id"; + payload["exp"] = now + 3600; + payload["iat"] = now - 10; std::string jwt = CreateTestJWTWithCustomHeader(header, payload); @@ -609,15 +609,15 @@ TEST(IdTokenValidatorTest, RejectsMissingKidWhenJwksUriSet) int64_t now = GetNow(); // Header declares RS256 but omits the kid field - web::json::value header; - header[U("alg")] = web::json::value::string(U("RS256")); - header[U("typ")] = web::json::value::string(U("JWT")); + nlohmann::json header; + header["alg"] = "RS256"; + header["typ"] = "JWT"; - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["aud"] = "test_client_id"; + payload["exp"] = now + 3600; + payload["iat"] = now - 10; std::string jwt = CreateTestJWTWithCustomHeader(header, payload); @@ -636,13 +636,13 @@ TEST(IdTokenValidatorTest, ReturnsDecodedPayload) { int64_t now = GetNow(); - web::json::value payload; - payload[U("iss")] = web::json::value::string(U("https://test.auth0.com/")); - payload[U("aud")] = web::json::value::string(U("test_client_id")); - payload[U("exp")] = web::json::value::number(now + 3600); - payload[U("iat")] = web::json::value::number(now - 10); - payload[U("sub")] = web::json::value::string(U("auth0|123")); - payload[U("custom_claim")] = web::json::value::string(U("custom_value")); + nlohmann::json payload; + payload["iss"] = "https://test.auth0.com/"; + payload["aud"] = "test_client_id"; + payload["exp"] = now + 3600; + payload["iat"] = now - 10; + payload["sub"] = "auth0|123"; + payload["custom_claim"] = "custom_value"; std::string jwt = CreateTestJWT(payload); @@ -650,16 +650,16 @@ TEST(IdTokenValidatorTest, ReturnsDecodedPayload) config.issuer = "https://test.auth0.com/"; config.audience = "test_client_id"; - web::json::value outPayload; + nlohmann::json outPayload; EXPECT_NO_THROW(ValidateIdToken(jwt, config, &outPayload)); - EXPECT_TRUE(outPayload.has_field(U("sub"))); + EXPECT_TRUE(outPayload.contains("sub")); EXPECT_EQ( - utility::conversions::to_utf8string(outPayload.at(U("sub")).as_string()), + outPayload.at("sub").get(), "auth0|123"); - EXPECT_TRUE(outPayload.has_field(U("custom_claim"))); + EXPECT_TRUE(outPayload.contains("custom_claim")); EXPECT_EQ( - utility::conversions::to_utf8string(outPayload.at(U("custom_claim")).as_string()), + outPayload.at("custom_claim").get(), "custom_value"); } diff --git a/auth0_flutter/windows/test/jwt_util_test.cpp b/auth0_flutter/windows/test/jwt_util_test.cpp index 67ac52ba8..2f2366c3f 100644 --- a/auth0_flutter/windows/test/jwt_util_test.cpp +++ b/auth0_flutter/windows/test/jwt_util_test.cpp @@ -2,13 +2,11 @@ #include "jwt_util.h" -// cpprestsdk -#include +#include // Flutter #include -using web::json::value; /* * Helper: Create a minimal valid JWT with a known payload. @@ -55,31 +53,31 @@ TEST(SplitJwtTest, InvalidJwtThrows) { TEST(DecodeJwtPayloadTest, DecodesPayloadCorrectly) { std::string jwt = CreateTestJwt(); - value payload = DecodeJwtPayload(jwt); + nlohmann::json payload = DecodeJwtPayload(jwt); ASSERT_TRUE(payload.is_object()); - EXPECT_EQ(payload.at(U("sub")).as_string(), U("123")); - EXPECT_EQ(payload.at(U("name")).as_string(), U("John")); - EXPECT_TRUE(payload.at(U("admin")).as_bool()); + EXPECT_EQ(payload.at("sub").get(), "123"); + EXPECT_EQ(payload.at("name").get(), "John"); + EXPECT_TRUE(payload.at("admin").get()); } /* ---------------- JsonToEncodable ---------------- */ TEST(JsonToEncodableTest, ConvertsPrimitiveTypes) { EXPECT_TRUE( - std::holds_alternative(JsonToEncodable(value::boolean(true)))); + std::holds_alternative(JsonToEncodable(true))); EXPECT_TRUE( - std::holds_alternative(JsonToEncodable(value::number(1.5)))); + std::holds_alternative(JsonToEncodable(1.5))); EXPECT_TRUE( std::holds_alternative( - JsonToEncodable(value::string(U("hello"))))); + JsonToEncodable("hello"))); } TEST(JsonToEncodableTest, ConvertsArray) { - value arr = value::array({ - value::number(1), - value::string(U("two")), - value::boolean(true), + nlohmann::json arr = nlohmann::json::array({ + 1, + "two", + true, }); flutter::EncodableValue ev = JsonToEncodable(arr); @@ -90,9 +88,9 @@ TEST(JsonToEncodableTest, ConvertsArray) { } TEST(JsonToEncodableTest, ConvertsObject) { - value obj; - obj[U("a")] = value::number(1); - obj[U("b")] = value::string(U("two")); + nlohmann::json obj; + obj["a"] = 1; + obj["b"] = "two"; flutter::EncodableValue ev = JsonToEncodable(obj); ASSERT_TRUE(std::holds_alternative(ev)); diff --git a/auth0_flutter/windows/test/login_web_auth_request_handler_test.cpp b/auth0_flutter/windows/test/login_web_auth_request_handler_test.cpp index f444a3611..a6d3dbfbe 100644 --- a/auth0_flutter/windows/test/login_web_auth_request_handler_test.cpp +++ b/auth0_flutter/windows/test/login_web_auth_request_handler_test.cpp @@ -2,7 +2,7 @@ * @file login_web_auth_request_handler_test.cpp * @brief Tests for LoginWebAuthRequestHandler synchronous argument validation * - * Covers only the synchronous validation paths that execute before the pplx + * Covers only the synchronous validation paths that execute before the PPL * background task is launched. Network, browser, and token exchange paths * require integration test infrastructure and are not covered here. * diff --git a/auth0_flutter/windows/test/oauth_helpers_test.cpp b/auth0_flutter/windows/test/oauth_helpers_test.cpp index 0346bbbde..ac384a8bb 100644 --- a/auth0_flutter/windows/test/oauth_helpers_test.cpp +++ b/auth0_flutter/windows/test/oauth_helpers_test.cpp @@ -274,13 +274,13 @@ TEST(WaitForAuthCodeCustomSchemeTest, TimeoutReturnsError) { TEST(WaitForAuthCodeCustomSchemeTest, CancelsWhenTokenIsAlreadyCancelled) { // Pre-cancel the token before passing it to the function. // The polling loop checks ct.is_canceled() on the very first tick and - // calls pplx::cancel_current_task(), which throws pplx::task_canceled. - pplx::cancellation_token_source cts; + // calls concurrency::cancel_current_task(), which throws concurrency::task_canceled. + concurrency::cancellation_token_source cts; cts.cancel(); EXPECT_THROW( waitForAuthCode_CustomScheme(180, "", cts.get_token()), - pplx::task_canceled); + concurrency::task_canceled); } // Note: The HTTP listener-based waitForAuthCode function has been removed. @@ -585,7 +585,7 @@ TEST(WaitForAuthCodeEnvVarTest, TrailingSlashInExpectedUrlMatches) { L"auth0flutter://callback?code=my_code&state=s2"); OAuthCallbackResult result = waitForAuthCode_CustomScheme( - 5, "s2", pplx::cancellation_token::none(), "auth0flutter://callback/"); + 5, "s2", concurrency::cancellation_token::none(), "auth0flutter://callback/"); EXPECT_TRUE(result.success); EXPECT_EQ(result.code, "my_code"); @@ -753,12 +753,12 @@ TEST(WaitForLogoutCallbackTest, WrongPrefixUrlIsIgnoredAndTimesOut) { } TEST(WaitForLogoutCallbackTest, CancelsWhenTokenIsAlreadyCancelled) { - pplx::cancellation_token_source cts; + concurrency::cancellation_token_source cts; cts.cancel(); EXPECT_THROW( waitForLogoutCallback("auth0flutter://callback", 180, cts.get_token()), - pplx::task_canceled); + concurrency::task_canceled); } /* -------- Reader-Writer Lock Tests (Issue #4 - TOCTOU Race Prevention) -------- */ diff --git a/auth0_flutter/windows/test/token_decoder_test.cpp b/auth0_flutter/windows/test/token_decoder_test.cpp index 486a8ccee..96188eef2 100644 --- a/auth0_flutter/windows/test/token_decoder_test.cpp +++ b/auth0_flutter/windows/test/token_decoder_test.cpp @@ -1,20 +1,18 @@ #include #include "token_decoder.h" -#include +#include #include -using web::json::value; - /* ---------------- DecodeTokenResponse ---------------- */ TEST(DecodeTokenResponseTest, DecodesMinimalResponse) { // openid is always requested, so Auth0 always returns an id_token. // The minimal real-world response always includes access_token, token_type, and id_token. - value json; - json[U("access_token")] = value::string(U("test_access_token")); - json[U("token_type")] = value::string(U("Bearer")); - json[U("id_token")] = value::string(U("test_id_token")); + nlohmann::json json; + json["access_token"] = "test_access_token"; + json["token_type"] = "Bearer"; + json["id_token"] = "test_id_token"; Credentials creds = DecodeTokenResponse(json); @@ -29,13 +27,13 @@ TEST(DecodeTokenResponseTest, DecodesMinimalResponse) { } TEST(DecodeTokenResponseTest, DecodesFullResponse) { - value json; - json[U("access_token")] = value::string(U("test_access_token")); - json[U("token_type")] = value::string(U("Bearer")); - json[U("id_token")] = value::string(U("test_id_token")); - json[U("refresh_token")] = value::string(U("test_refresh_token")); - json[U("expires_in")] = value::number(3600); - json[U("scope")] = value::string(U("openid profile email")); + nlohmann::json json; + json["access_token"] = "test_access_token"; + json["token_type"] = "Bearer"; + json["id_token"] = "test_id_token"; + json["refresh_token"] = "test_refresh_token"; + json["expires_in"] = 3600; + json["scope"] = "openid profile email"; Credentials creds = DecodeTokenResponse(json); @@ -57,10 +55,10 @@ TEST(DecodeTokenResponseTest, DecodesFullResponse) { TEST(DecodeTokenResponseTest, ComputesExpiresAtFromExpiresIn) { auto before = std::chrono::system_clock::now(); - value json; - json[U("access_token")] = value::string(U("test_access_token")); - json[U("token_type")] = value::string(U("Bearer")); - json[U("expires_in")] = value::number(7200); // 2 hours + nlohmann::json json; + json["access_token"] = "test_access_token"; + json["token_type"] = "Bearer"; + json["expires_in"] = 7200; // 2 hours Credentials creds = DecodeTokenResponse(json); @@ -80,10 +78,10 @@ TEST(DecodeTokenResponseTest, ComputesExpiresAtFromExpiresIn) { } TEST(DecodeTokenResponseTest, UsesExplicitExpiresAt) { - value json; - json[U("access_token")] = value::string(U("test_access_token")); - json[U("token_type")] = value::string(U("Bearer")); - json[U("expires_at")] = value::string(U("2025-12-31T23:59:59Z")); + nlohmann::json json; + json["access_token"] = "test_access_token"; + json["token_type"] = "Bearer"; + json["expires_at"] = "2025-12-31T23:59:59Z"; Credentials creds = DecodeTokenResponse(json); @@ -105,11 +103,11 @@ TEST(DecodeTokenResponseTest, UsesExplicitExpiresAt) { } TEST(DecodeTokenResponseTest, PrefersExpiresAtOverExpiresIn) { - value json; - json[U("access_token")] = value::string(U("test_access_token")); - json[U("token_type")] = value::string(U("Bearer")); - json[U("expires_at")] = value::string(U("2025-12-31T23:59:59Z")); - json[U("expires_in")] = value::number(3600); // This should be ignored + nlohmann::json json; + json["access_token"] = "test_access_token"; + json["token_type"] = "Bearer"; + json["expires_at"] = "2025-12-31T23:59:59Z"; + json["expires_in"] = 3600; // This should be ignored Credentials creds = DecodeTokenResponse(json); @@ -129,10 +127,10 @@ TEST(DecodeTokenResponseTest, PrefersExpiresAtOverExpiresIn) { } TEST(DecodeTokenResponseTest, HandlesSingleScope) { - value json; - json[U("access_token")] = value::string(U("test_access_token")); - json[U("token_type")] = value::string(U("Bearer")); - json[U("scope")] = value::string(U("openid")); + nlohmann::json json; + json["access_token"] = "test_access_token"; + json["token_type"] = "Bearer"; + json["scope"] = "openid"; Credentials creds = DecodeTokenResponse(json); @@ -141,10 +139,10 @@ TEST(DecodeTokenResponseTest, HandlesSingleScope) { } TEST(DecodeTokenResponseTest, HandlesEmptyScope) { - value json; - json[U("access_token")] = value::string(U("test_access_token")); - json[U("token_type")] = value::string(U("Bearer")); - json[U("scope")] = value::string(U("")); + nlohmann::json json; + json["access_token"] = "test_access_token"; + json["token_type"] = "Bearer"; + json["scope"] = ""; Credentials creds = DecodeTokenResponse(json); @@ -152,10 +150,10 @@ TEST(DecodeTokenResponseTest, HandlesEmptyScope) { } TEST(DecodeTokenResponseTest, HandlesScopeWithMultipleSpaces) { - value json; - json[U("access_token")] = value::string(U("test_access_token")); - json[U("token_type")] = value::string(U("Bearer")); - json[U("scope")] = value::string(U("openid profile email")); + nlohmann::json json; + json["access_token"] = "test_access_token"; + json["token_type"] = "Bearer"; + json["scope"] = "openid profile email"; Credentials creds = DecodeTokenResponse(json); @@ -167,24 +165,24 @@ TEST(DecodeTokenResponseTest, HandlesScopeWithMultipleSpaces) { } TEST(DecodeTokenResponseTest, ThrowsOnMissingAccessToken) { - value json; - json[U("token_type")] = value::string(U("Bearer")); + nlohmann::json json; + json["token_type"] = "Bearer"; EXPECT_THROW(DecodeTokenResponse(json), std::runtime_error); } TEST(DecodeTokenResponseTest, ThrowsOnMissingTokenType) { - value json; - json[U("access_token")] = value::string(U("test_access_token")); + nlohmann::json json; + json["access_token"] = "test_access_token"; EXPECT_THROW(DecodeTokenResponse(json), std::runtime_error); } TEST(DecodeTokenResponseTest, HandlesNonIntegerExpiresIn) { - value json; - json[U("access_token")] = value::string(U("test_access_token")); - json[U("token_type")] = value::string(U("Bearer")); - json[U("expires_in")] = value::string(U("not_a_number")); + nlohmann::json json; + json["access_token"] = "test_access_token"; + json["token_type"] = "Bearer"; + json["expires_in"] = "not_a_number"; // Should not throw, just skip expires_in Credentials creds = DecodeTokenResponse(json); @@ -194,11 +192,11 @@ TEST(DecodeTokenResponseTest, HandlesNonIntegerExpiresIn) { } TEST(DecodeTokenResponseTest, HandlesInvalidExpiresAtFormat) { - value json; - json[U("access_token")] = value::string(U("test_access_token")); - json[U("token_type")] = value::string(U("Bearer")); - json[U("expires_at")] = value::string(U("invalid-date")); - json[U("expires_in")] = value::number(3600); + nlohmann::json json; + json["access_token"] = "test_access_token"; + json["token_type"] = "Bearer"; + json["expires_at"] = "invalid-date"; + json["expires_in"] = 3600; Credentials creds = DecodeTokenResponse(json); @@ -208,9 +206,9 @@ TEST(DecodeTokenResponseTest, HandlesInvalidExpiresAtFormat) { TEST(DecodeTokenResponseTest, DPoPTokenType) { // DPoP token type must be preserved exactly as returned by Auth0. - value json; - json[U("access_token")] = value::string(U("dpop_bound_token")); - json[U("token_type")] = value::string(U("DPoP")); + nlohmann::json json; + json["access_token"] = "dpop_bound_token"; + json["token_type"] = "DPoP"; Credentials creds = DecodeTokenResponse(json); @@ -221,10 +219,10 @@ TEST(DecodeTokenResponseTest, DPoPTokenType) { TEST(DecodeTokenResponseTest, ZeroExpiresInProducesExpiresAtApproximatelyNow) { auto before = std::chrono::system_clock::now(); - value json; - json[U("access_token")] = value::string(U("tok")); - json[U("token_type")] = value::string(U("Bearer")); - json[U("expires_in")] = value::number(0); + nlohmann::json json; + json["access_token"] = "tok"; + json["token_type"] = "Bearer"; + json["expires_in"] = 0; Credentials creds = DecodeTokenResponse(json); @@ -244,10 +242,10 @@ TEST(DecodeTokenResponseTest, ZeroExpiresInProducesExpiresAtApproximatelyNow) { TEST(DecodeTokenResponseTest, LargeExpiresInOneYear) { const int oneYear = 365 * 24 * 3600; - value json; - json[U("access_token")] = value::string(U("tok")); - json[U("token_type")] = value::string(U("Bearer")); - json[U("expires_in")] = value::number(oneYear); + nlohmann::json json; + json["access_token"] = "tok"; + json["token_type"] = "Bearer"; + json["expires_in"] = oneYear; Credentials creds = DecodeTokenResponse(json); @@ -263,10 +261,10 @@ TEST(DecodeTokenResponseTest, LargeExpiresInOneYear) { TEST(DecodeTokenResponseTest, ScopeWithLeadingAndTrailingSpaces) { // istringstream-based parsing skips leading/trailing whitespace. - value json; - json[U("access_token")] = value::string(U("tok")); - json[U("token_type")] = value::string(U("Bearer")); - json[U("scope")] = value::string(U(" openid profile ")); + nlohmann::json json; + json["access_token"] = "tok"; + json["token_type"] = "Bearer"; + json["scope"] = " openid profile "; Credentials creds = DecodeTokenResponse(json); @@ -276,10 +274,10 @@ TEST(DecodeTokenResponseTest, ScopeWithLeadingAndTrailingSpaces) { } TEST(DecodeTokenResponseTest, ScopeWithOnlyWhitespaceIsEmpty) { - value json; - json[U("access_token")] = value::string(U("tok")); - json[U("token_type")] = value::string(U("Bearer")); - json[U("scope")] = value::string(U(" ")); + nlohmann::json json; + json["access_token"] = "tok"; + json["token_type"] = "Bearer"; + json["scope"] = " "; Credentials creds = DecodeTokenResponse(json); @@ -290,9 +288,9 @@ TEST(DecodeTokenResponseTest, ScopeWithOnlyWhitespaceIsEmpty) { TEST(DecodeTokenResponseTest, NoExpiryFieldsProducesNoExpiresAt) { // When neither expires_at nor expires_in is present, expiresAt must be // absent (not guessed or defaulted). - value json; - json[U("access_token")] = value::string(U("tok")); - json[U("token_type")] = value::string(U("Bearer")); + nlohmann::json json; + json["access_token"] = "tok"; + json["token_type"] = "Bearer"; Credentials creds = DecodeTokenResponse(json); @@ -302,10 +300,10 @@ TEST(DecodeTokenResponseTest, NoExpiryFieldsProducesNoExpiresAt) { TEST(DecodeTokenResponseTest, InvalidExpiresAtWithNoFallbackProducesNoExpiresAt) { // Malformed expires_at with no expires_in → no expiry information at all. - value json; - json[U("access_token")] = value::string(U("tok")); - json[U("token_type")] = value::string(U("Bearer")); - json[U("expires_at")] = value::string(U("not-a-date")); + nlohmann::json json; + json["access_token"] = "tok"; + json["token_type"] = "Bearer"; + json["expires_at"] = "not-a-date"; Credentials creds = DecodeTokenResponse(json); diff --git a/auth0_flutter/windows/test/user_identity_test.cpp b/auth0_flutter/windows/test/user_identity_test.cpp index 2fed229c5..baee9b3ec 100644 --- a/auth0_flutter/windows/test/user_identity_test.cpp +++ b/auth0_flutter/windows/test/user_identity_test.cpp @@ -1,20 +1,19 @@ #include #include "user_identity.h" -#include +#include #include -using web::json::value; using flutter::EncodableMap; using flutter::EncodableValue; /* ---------------- FromJson ---------------- */ TEST(UserIdentityFromJsonTest, ParsesMinimalIdentity) { - value json; - json[U("user_id")] = value::string(U("auth0|123456")); - json[U("connection")] = value::string(U("Username-Password-Authentication")); - json[U("provider")] = value::string(U("auth0")); + nlohmann::json json; + json["user_id"] = "auth0|123456"; + json["connection"] = "Username-Password-Authentication"; + json["provider"] = "auth0"; UserIdentity identity = UserIdentity::FromJson(json); @@ -28,18 +27,18 @@ TEST(UserIdentityFromJsonTest, ParsesMinimalIdentity) { } TEST(UserIdentityFromJsonTest, ParsesFullIdentity) { - value json; - json[U("user_id")] = value::string(U("google-oauth2|123456")); - json[U("connection")] = value::string(U("google-oauth2")); - json[U("provider")] = value::string(U("google-oauth2")); - json[U("isSocial")] = value::boolean(true); - json[U("access_token")] = value::string(U("test_access_token")); - json[U("access_token_secret")] = value::string(U("test_secret")); - - value profileData; - profileData[U("email")] = value::string(U("user@example.com")); - profileData[U("name")] = value::string(U("John Doe")); - json[U("profileData")] = profileData; + nlohmann::json json; + json["user_id"] = "google-oauth2|123456"; + json["connection"] = "google-oauth2"; + json["provider"] = "google-oauth2"; + json["isSocial"] = true; + json["access_token"] = "test_access_token"; + json["access_token_secret"] = "test_secret"; + + nlohmann::json profileData; + profileData["email"] = "user@example.com"; + profileData["name"] = "John Doe"; + json["profileData"] = profileData; UserIdentity identity = UserIdentity::FromJson(json); @@ -58,11 +57,11 @@ TEST(UserIdentityFromJsonTest, ParsesFullIdentity) { } TEST(UserIdentityFromJsonTest, HandlesSocialIdentityWithoutTokens) { - value json; - json[U("user_id")] = value::string(U("facebook|123456")); - json[U("connection")] = value::string(U("facebook")); - json[U("provider")] = value::string(U("facebook")); - json[U("isSocial")] = value::boolean(true); + nlohmann::json json; + json["user_id"] = "facebook|123456"; + json["connection"] = "facebook"; + json["provider"] = "facebook"; + json["isSocial"] = true; UserIdentity identity = UserIdentity::FromJson(json); @@ -73,11 +72,11 @@ TEST(UserIdentityFromJsonTest, HandlesSocialIdentityWithoutTokens) { } TEST(UserIdentityFromJsonTest, HandlesEmptyProfileData) { - value json; - json[U("user_id")] = value::string(U("auth0|123456")); - json[U("connection")] = value::string(U("Username-Password-Authentication")); - json[U("provider")] = value::string(U("auth0")); - json[U("profileData")] = value::object(); + nlohmann::json json; + json["user_id"] = "auth0|123456"; + json["connection"] = "Username-Password-Authentication"; + json["provider"] = "auth0"; + json["profileData"] = nlohmann::json::object(); UserIdentity identity = UserIdentity::FromJson(json); @@ -85,16 +84,16 @@ TEST(UserIdentityFromJsonTest, HandlesEmptyProfileData) { } TEST(UserIdentityFromJsonTest, HandlesProfileDataWithVariousTypes) { - value json; - json[U("user_id")] = value::string(U("auth0|123456")); - json[U("connection")] = value::string(U("Username-Password-Authentication")); - json[U("provider")] = value::string(U("auth0")); + nlohmann::json json; + json["user_id"] = "auth0|123456"; + json["connection"] = "Username-Password-Authentication"; + json["provider"] = "auth0"; - value profileData; - profileData[U("string_field")] = value::string(U("text")); - profileData[U("number_field")] = value::number(42); - profileData[U("bool_field")] = value::boolean(true); - json[U("profileData")] = profileData; + nlohmann::json profileData; + profileData["string_field"] = "text"; + profileData["number_field"] = 42; + profileData["bool_field"] = true; + json["profileData"] = profileData; UserIdentity identity = UserIdentity::FromJson(json); @@ -114,12 +113,12 @@ TEST(UserIdentityFromJsonTest, HandlesProfileDataWithVariousTypes) { } TEST(UserIdentityFromJsonTest, ThrowsOnMissingRequiredField) { - value json; - json[U("user_id")] = value::string(U("auth0|123456")); - json[U("connection")] = value::string(U("Username-Password-Authentication")); + nlohmann::json json; + json["user_id"] = "auth0|123456"; + json["connection"] = "Username-Password-Authentication"; // Missing provider - EXPECT_THROW(UserIdentity::FromJson(json), web::json::json_exception); + EXPECT_THROW(UserIdentity::FromJson(json), nlohmann::json::out_of_range); } /* ---------------- FromEncodable ---------------- */ @@ -238,12 +237,12 @@ TEST(UserIdentityToEncodableMapTest, HandlesEmptyProfileInfo) { /* ---------------- Round-trip tests ---------------- */ TEST(UserIdentityRoundTripTest, FromJsonToEncodableMapPreservesData) { - value json; - json[U("user_id")] = value::string(U("google-oauth2|123456")); - json[U("connection")] = value::string(U("google-oauth2")); - json[U("provider")] = value::string(U("google-oauth2")); - json[U("isSocial")] = value::boolean(true); - json[U("access_token")] = value::string(U("test_token")); + nlohmann::json json; + json["user_id"] = "google-oauth2|123456"; + json["connection"] = "google-oauth2"; + json["provider"] = "google-oauth2"; + json["isSocial"] = true; + json["access_token"] = "test_token"; UserIdentity identity = UserIdentity::FromJson(json); EncodableMap map = identity.ToEncodableMap(); diff --git a/auth0_flutter/windows/token_decoder.cpp b/auth0_flutter/windows/token_decoder.cpp index 58c28679a..2791b99d9 100644 --- a/auth0_flutter/windows/token_decoder.cpp +++ b/auth0_flutter/windows/token_decoder.cpp @@ -3,56 +3,50 @@ #include #include "time_util.h" Credentials DecodeTokenResponse( - const web::json::value &json) + const nlohmann::json &json) { Credentials creds; // ---- Required fields ---- - if (!json.has_field(U("access_token")) || !json.at(U("access_token")).is_string()) + if (!json.contains("access_token") || !json.at("access_token").is_string()) { throw std::runtime_error("Token response missing required 'access_token' field"); } - creds.accessToken = - utility::conversions::to_utf8string( - json.at(U("access_token")).as_string()); + creds.accessToken = json.at("access_token").get(); - if (!json.has_field(U("token_type")) || !json.at(U("token_type")).is_string()) + if (!json.contains("token_type") || !json.at("token_type").is_string()) { throw std::runtime_error("Token response missing required 'token_type' field"); } - creds.tokenType = - utility::conversions::to_utf8string( - json.at(U("token_type")).as_string()); + creds.tokenType = json.at("token_type").get(); // ---- Optional fields ---- - if (json.has_field(U("id_token"))) + if (json.contains("id_token")) { - creds.idToken = - utility::conversions::to_utf8string( - json.at(U("id_token")).as_string()); + creds.idToken = json.at("id_token").get(); } - if (json.has_field(U("refresh_token"))) + if (json.contains("refresh_token")) { - creds.refreshToken = - utility::conversions::to_utf8string( - json.at(U("refresh_token")).as_string()); + creds.refreshToken = json.at("refresh_token").get(); } - if (json.has_field(U("expires_in")) && - json.at(U("expires_in")).is_integer()) + // Use is_number() rather than a strict integer-only check: a server may + // legitimately emit "expires_in" as a float (e.g. 86400.0), which nlohmann + // would not consider is_number_integer() even though it is a whole number. + if (json.contains("expires_in") && + json.at("expires_in").is_number()) { - creds.expiresIn = json.at(U("expires_in")).as_integer(); + creds.expiresIn = json.at("expires_in").get(); } // Try expires_at from JSON - if (json.has_field(U("expires_at")) && - json.at(U("expires_at")).is_string()) + if (json.contains("expires_at") && + json.at("expires_at").is_string()) { - auto iso = utility::conversions::to_utf8string( - json.at(U("expires_at")).as_string()); + auto iso = json.at("expires_at").get(); creds.expiresAt = ParseIso8601(iso); } @@ -68,12 +62,11 @@ Credentials DecodeTokenResponse( // -------------------------------------------------- // scope (optional, space-separated string) - if (json.has_field(U("scope")) && - json.at(U("scope")).is_string()) + if (json.contains("scope") && + json.at("scope").is_string()) { - auto scopeStr = utility::conversions::to_utf8string( - json.at(U("scope")).as_string()); + auto scopeStr = json.at("scope").get(); std::istringstream iss(scopeStr); std::string s; diff --git a/auth0_flutter/windows/token_decoder.h b/auth0_flutter/windows/token_decoder.h index 0a5bef81e..34095c8fd 100644 --- a/auth0_flutter/windows/token_decoder.h +++ b/auth0_flutter/windows/token_decoder.h @@ -1,6 +1,6 @@ #pragma once -#include +#include #include "credentials.h" Credentials DecodeTokenResponse( - const web::json::value &json); + const nlohmann::json &json); diff --git a/auth0_flutter/windows/user_identity.cpp b/auth0_flutter/windows/user_identity.cpp index deb2dcc5d..63508bc2d 100644 --- a/auth0_flutter/windows/user_identity.cpp +++ b/auth0_flutter/windows/user_identity.cpp @@ -1,41 +1,38 @@ #include "user_identity.h" #include "jwt_util.h" -using web::json::value; - static std::string GetRequiredString( - const value& v, const utility::string_t& key) { - return utility::conversions::to_utf8string(v.at(key).as_string()); + const nlohmann::json& v, const std::string& key) { + return v.at(key).get(); } static std::optional GetOptionalString( - const value& v, const utility::string_t& key) { - if (v.has_field(key) && v.at(key).is_string()) { - return utility::conversions::to_utf8string(v.at(key).as_string()); + const nlohmann::json& v, const std::string& key) { + if (v.contains(key) && v.at(key).is_string()) { + return v.at(key).get(); } return std::nullopt; } -UserIdentity UserIdentity::FromJson(const value& json) { +UserIdentity UserIdentity::FromJson(const nlohmann::json& json) { UserIdentity identity; - identity.id = GetRequiredString(json, U("user_id")); - identity.connection = GetRequiredString(json, U("connection")); - identity.provider = GetRequiredString(json, U("provider")); + identity.id = GetRequiredString(json, "user_id"); + identity.connection = GetRequiredString(json, "connection"); + identity.provider = GetRequiredString(json, "provider"); - if (json.has_field(U("isSocial"))) { - identity.isSocial = json.at(U("isSocial")).as_bool(); + if (json.contains("isSocial")) { + identity.isSocial = json.at("isSocial").get(); } - identity.accessToken = GetOptionalString(json, U("access_token")); - identity.accessTokenSecret = GetOptionalString(json, U("access_token_secret")); + identity.accessToken = GetOptionalString(json, "access_token"); + identity.accessTokenSecret = GetOptionalString(json, "access_token_secret"); - if (json.has_field(U("profileData")) && - json.at(U("profileData")).is_object()) { - for (const auto& kv : json.at(U("profileData")).as_object()) { - identity.profileInfo[flutter::EncodableValue( - utility::conversions::to_utf8string(kv.first))] = - JsonToEncodable(kv.second); + if (json.contains("profileData") && + json.at("profileData").is_object()) { + for (const auto& kv : json.at("profileData").items()) { + identity.profileInfo[flutter::EncodableValue(kv.key())] = + JsonToEncodable(kv.value()); } } diff --git a/auth0_flutter/windows/user_identity.h b/auth0_flutter/windows/user_identity.h index 61083fd4b..93ea76762 100644 --- a/auth0_flutter/windows/user_identity.h +++ b/auth0_flutter/windows/user_identity.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include class UserIdentity { public: @@ -15,7 +15,7 @@ class UserIdentity { std::optional accessTokenSecret; flutter::EncodableMap profileInfo; - static UserIdentity FromJson(const web::json::value& json); + static UserIdentity FromJson(const nlohmann::json& json); static UserIdentity FromEncodable(const flutter::EncodableMap& map); flutter::EncodableMap ToEncodableMap() const; diff --git a/auth0_flutter/windows/vcpkg.json b/auth0_flutter/windows/vcpkg.json index 55411eb27..b547041bc 100644 --- a/auth0_flutter/windows/vcpkg.json +++ b/auth0_flutter/windows/vcpkg.json @@ -3,10 +3,8 @@ "version-string": "0.1.0", "description": "Auth0 Flutter plugin native C++ dependencies", "dependencies": [ - "cpprestsdk", - "openssl", - "boost-system", - "boost-date-time", - "boost-regex" + "cpp-httplib", + "nlohmann-json", + "openssl" ] } diff --git a/auth0_flutter/windows/windows_utils.cpp b/auth0_flutter/windows/windows_utils.cpp index 3aeffaa0a..653732389 100644 --- a/auth0_flutter/windows/windows_utils.cpp +++ b/auth0_flutter/windows/windows_utils.cpp @@ -25,7 +25,7 @@ namespace auth0_flutter void BringFlutterWindowToFront() { // GetActiveWindow() only returns windows on the calling thread's message - // queue. Since this runs on a background pplx worker thread it always + // queue. Since this runs on a background PPL worker thread it always // returns NULL. Instead, enumerate all top-level windows that belong to // this process to find the Flutter window. struct FindData