diff --git a/.github/actions/install/reflectcpp-json/action.yml b/.github/actions/install/reflectcpp-json/action.yml new file mode 100644 index 000000000..0853cbf9c --- /dev/null +++ b/.github/actions/install/reflectcpp-json/action.yml @@ -0,0 +1,12 @@ +name: Install reflect-cpp +description: Install reflect-cpp for building test application +inputs: + version: + description: The desired reflect-cpp version to install + required: false + default: "0.24.0" +runs: + using: composite + steps: + - run: ${{ github.action_path }}/install-reflect-cpp.sh ${{ inputs.version }} + shell: bash diff --git a/.github/actions/install/reflectcpp-json/install-reflect-cpp.sh b/.github/actions/install/reflectcpp-json/install-reflect-cpp.sh new file mode 100755 index 000000000..6793920a9 --- /dev/null +++ b/.github/actions/install/reflectcpp-json/install-reflect-cpp.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Install reflect-cpp library with specified version + +set -e # Exit on error + +# Check if version is provided +if [[ -z "$1" ]] || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then + echo "Usage: $0 VERSION" + echo "" + echo "Install reflect-cpp library" + echo "" + echo "Arguments:" + echo " VERSION reflect-cpp version to install (required)" + echo "" + echo "Examples:" + echo " $0 0.24.0" + exit 1 +fi + +# Configuration +reflectcpp_VERSION="$1" +INSTALL_DIR="/tmp" +BUILD_DIR="${INSTALL_DIR}/reflect-cpp-${reflectcpp_VERSION}" + +echo "Installing reflectcpp v${reflectcpp_VERSION}..." + +# Download +echo "Downloading reflectcpp v${reflectcpp_VERSION}..." +cd "${INSTALL_DIR}" +wget -q "https://github.com/getml/reflect-cpp/archive/v${reflectcpp_VERSION}.tar.gz" + +# Extract +echo "Extracting archive..." +tar -zxf "v${reflectcpp_VERSION}.tar.gz" + +# Build and install +echo "Building and installing..." +cd "${BUILD_DIR}" +cmake . -DCMAKE_BUILD_TYPE=Release -DREFLECTCPP_INSTALL=ON +cmake --build . +sudo cmake --install . + +# Cleanup +echo "Cleaning up..." +rm -f "${INSTALL_DIR}/v${reflectcpp_VERSION}.tar.gz" +rm -rf "${BUILD_DIR}" + +echo "✓ reflect-cpp v${reflectcpp_VERSION} installed successfully!" diff --git a/.github/workflows/jwt.yml b/.github/workflows/jwt.yml index ff03d6719..ee8461d60 100644 --- a/.github/workflows/jwt.yml +++ b/.github/workflows/jwt.yml @@ -17,7 +17,7 @@ jobs: - uses: ./.github/actions/install/boost-json - uses: ./.github/actions/install/open-source-parsers-jsoncpp - uses: ./.github/actions/install/glaze-json - + - uses: ./.github/actions/install/reflectcpp-json - name: Configure LCOV to ignore mismatches # https://github.com/linux-test-project/lcov/issues/316#issuecomment-2304694266 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a95086ffa..035b8608a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -72,6 +72,7 @@ jobs: - { name: "nlohmann_json", library: "JSON for Modern C++", url: "https://github.com/nlohmann/json", disable_pico: true } - { name: "open_source_parsers_jsoncpp", library: "jsoncpp", url: "https://github.com/open-source-parsers/jsoncpp", disable_pico: true } - { name: "glaze_json", library: "Glaze", url: "https://github.com/stephenberry/glaze", disable_pico: true } + - { name: "reflectcpp_json", library: "ReflectCpp", url: "https://github.com/getml/reflect-cpp", disable_pico: true } name: render-defaults (${{ matrix.traits.name }}) steps: - uses: actions/checkout@v4 @@ -102,6 +103,7 @@ jobs: - { name: "nlohmann_json", suite: "NlohmannTest" } - { name: "open_source_parsers_jsoncpp", suite: "OspJsoncppTest" } # - { name: "glaze_json", suite: "GlazeTest" } # No supported due to istream buffer being specialized + # - { name: "reflectcpp_json", suite: "ReflectCppJsonTest" } # No supported due to istream buffer being specialized name: render-tests (${{ matrix.traits.name }}) steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/traits.yml b/.github/workflows/traits.yml index cb9d2b4b7..27b1420c6 100644 --- a/.github/workflows/traits.yml +++ b/.github/workflows/traits.yml @@ -22,6 +22,7 @@ jobs: - { name: "kazuho-picojson", tag: "111c9be5188f7350c2eac9ddaedd8cca3d7bf394", version: "111c9be" } - { name: "open-source-parsers-jsoncpp", tag: "1.9.6", version: "v1.9.6" } - { name: "glaze-json", tag: "7.0.2", version: "v7.0.2" } + - { name: "reflectcpp-json", tag: "0.24.0", version: "v0.24.0" } steps: - uses: actions/checkout@v4 - uses: lukka/get-cmake@latest @@ -65,6 +66,11 @@ jobs: with: version: ${{matrix.target.tag}} + - if: matrix.target.name == 'reflectcpp-json' + uses: ./.github/actions/install/reflectcpp-json + with: + version: ${{ matrix.target.tag }} + - name: test working-directory: example/traits run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 479d4bfb1..a8086ae58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,7 +36,7 @@ set(JWT_SSL_LIBRARY OpenSSL CACHE STRING "Determines which SSL library to build set_property(CACHE JWT_SSL_LIBRARY PROPERTY STRINGS ${JWT_SSL_LIBRARY_OPTIONS}) set(JWT_JSON_TRAITS_OPTIONS boost-json danielaparker-jsoncons kazuho-picojson nlohmann-json open-source-parsers-jsoncpp - glaze-json) + glaze-json reflectcpp-json) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") diff --git a/docs/traits.md b/docs/traits.md index 8d21823ef..940a29e0b 100644 --- a/docs/traits.md +++ b/docs/traits.md @@ -11,7 +11,8 @@ For your convenience there are serval traits implementation which provide some p [![jsoncons][jsoncons]](https://github.com/danielaparker/jsoncons) [![boostjson][boostjson]](https://github.com/boostorg/json) [![jsoncpp][jsoncpp]](https://github.com/open-source-parsers/jsoncpp) -[![glaze][glaze]]([https://github.com/open-source-parsers/jsoncpp](https://github.com/stephenberry/glaze)) +[![glaze][glaze]](https://github.com/stephenberry/glaze) +[![reflectcpp][reflectcpp]](https://github.com/getml/reflect-cpp) [picojson]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/traits/kazuho-picojson/shields.json [nlohmann]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/traits/nlohmann-json/shields.json @@ -19,6 +20,7 @@ For your convenience there are serval traits implementation which provide some p [boostjson]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/traits/boost-json/shields.json [jsoncpp]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/traits/open-source-parsers-jsoncpp/shields.json [glaze]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/traits/glaze-json/shields.json +[reflectcpp]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/traits/reflectcpp-json/shields.json In order to maintain compatibility, [picojson](https://github.com/kazuho/picojson) is still used to provide a specialized `jwt::claim` along with all helpers. Defining `JWT_DISABLE_PICOJSON` will remove this optional dependency. It's possible to directly include the traits defaults for the other JSON libraries. See the [traits examples](https://github.com/Thalhammer/jwt-cpp/tree/master/example/traits) for details. diff --git a/example/traits/CMakeLists.txt b/example/traits/CMakeLists.txt index 46c926f24..8ac66cfed 100644 --- a/example/traits/CMakeLists.txt +++ b/example/traits/CMakeLists.txt @@ -38,5 +38,13 @@ endif() find_package(glaze CONFIG) if(TARGET glaze::glaze) add_executable(glaze-json glaze-json.cpp) + target_compile_features(glaze-json PRIVATE cxx_std_23) target_link_libraries(glaze-json glaze::glaze jwt-cpp::jwt-cpp) endif() + +find_package(reflectcpp CONFIG) +if(TARGET reflectcpp::reflectcpp) + add_executable(reflectcpp-json reflectcpp-json.cpp) + target_compile_features(reflectcpp-json PRIVATE cxx_std_20) + target_link_libraries(reflectcpp-json jwt-cpp::jwt-cpp reflectcpp::reflectcpp) +endif() diff --git a/example/traits/reflectcpp-json.cpp b/example/traits/reflectcpp-json.cpp new file mode 100644 index 000000000..38531dcba --- /dev/null +++ b/example/traits/reflectcpp-json.cpp @@ -0,0 +1,56 @@ +#include "jwt-cpp/jwt.h" +#include "jwt-cpp/traits/reflectcpp-json/traits.h" + +#include +#include + +int main() { + using sec = std::chrono::seconds; + using min = std::chrono::minutes; + using traits = jwt::traits::reflectcpp_json; + using claim = jwt::basic_claim; + + traits::value_type raw_value; + traits::parse(raw_value, R"##({"api":{"array":[1,2,3],"null":null}})##"); + claim from_raw_json(raw_value); + + claim::set_t list{"once", "twice"}; + std::vector big_numbers{727663072LL, 770979831LL, 427239169LL, 525936436LL}; + + const auto time = jwt::date::clock::now(); + const auto token = jwt::create() + .set_type("JWT") + .set_issuer("auth.mydomain.io") + .set_audience("mydomain.io") + .set_issued_at(time) + .set_not_before(time) + .set_expires_at(time + min{2} + sec{15}) + .set_payload_claim("boolean", true) + .set_payload_claim("integer", 12345) + .set_payload_claim("precision", 12.3456789) + .set_payload_claim("strings", claim(list)) + .set_payload_claim("array", claim{big_numbers.begin(), big_numbers.end()}) + .set_payload_claim("object", from_raw_json) + .sign(jwt::algorithm::none{}); + + const auto decoded = jwt::decode(token); + + const auto array = decoded.get_payload_claim("object") + .to_json() + .to_object() + .value()["api"] + .to_object() + .value()["array"] + .to_array() + .value(); + std::cout << "payload /object/api/array = " << rfl::json::write(array) << '\n'; + + jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_issuer("auth.mydomain.io") + .with_audience("mydomain.io") + .with_claim("object", from_raw_json) + .verify(decoded); + + return 0; +} diff --git a/include/jwt-cpp/traits/reflectcpp-json/defaults.h b/include/jwt-cpp/traits/reflectcpp-json/defaults.h new file mode 100644 index 000000000..191bceb98 --- /dev/null +++ b/include/jwt-cpp/traits/reflectcpp-json/defaults.h @@ -0,0 +1,91 @@ +#ifndef JWT_CPP_REFLECTCPP_JSON_DEFAULTS_H +#define JWT_CPP_REFLECTCPP_JSON_DEFAULTS_H + +#ifndef JWT_DISABLE_PICOJSON +#define JWT_DISABLE_PICOJSON +#endif + +#include "traits.h" + +namespace jwt { + /** + * \brief a class to store a generic [ReflectCpp](https://github.com/getml/reflect-cpp) value as claim + * + * This type is the specialization of the \ref basic_claim class which + * uses the standard template types. + */ + using claim = basic_claim; + + /** + * Create a verifier using the default clock + * \return verifier instance + */ + inline verifier verify() { + return verify(default_clock{}); + } + + /** + * Create a builder using the default clock + * \return builder instance to create a new token + */ + inline builder create() { + return builder(default_clock{}); + } + +#ifndef JWT_DISABLE_BASE64 + /** + * Decode a token + * \param token Token to decode + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + inline decoded_jwt decode(const std::string& token) { + return decoded_jwt(token); + } +#endif + + /** + * Decode a token + * \tparam Decode is callable, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64url decode and + * return the results. + * \param token Token to decode + * \param decode The token to parse + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const std::string& token, Decode decode) { + return decoded_jwt(token, decode); + } + + /** + * Parse a jwk + * \param token JWK Token to parse + * \return Parsed JWK + * \throw std::runtime_error Token is not in correct format + */ + inline jwk parse_jwk(const traits::reflectcpp_json::string_type& token) { + return jwk(token); + } + + /** + * Parse a jwks + * \param token JWKs Token to parse + * \return Parsed JWKs + * \throw std::runtime_error Token is not in correct format + */ + inline jwks parse_jwks(const traits::reflectcpp_json::string_type& token) { + return jwks(token); + } + + /** + * This type is the specialization of the \ref verify_ops::verify_context class which + * uses the standard template types. + */ + using verify_context = verify_ops::verify_context; +} // namespace jwt + +#endif // JWT_CPP_REFLECTCPP_JSON_DEFAULTS_H diff --git a/include/jwt-cpp/traits/reflectcpp-json/traits.h b/include/jwt-cpp/traits/reflectcpp-json/traits.h new file mode 100644 index 000000000..80f7febbf --- /dev/null +++ b/include/jwt-cpp/traits/reflectcpp-json/traits.h @@ -0,0 +1,95 @@ +#ifndef JWT_CPP_REFLECT_CPP_TRAITS_H +#define JWT_CPP_REFLECT_CPP_TRAITS_H + +#ifndef JWT_DISABLE_PICOJSON +#define JWT_DISABLE_PICOJSON +#endif + +#include "jwt-cpp/jwt.h" + +#include +#include +#include + +namespace jwt::traits { + + struct reflectcpp_json { + + using value_type = rfl::Generic; + using object_type = rfl::Generic::Object; + using array_type = rfl::Generic::Array; + using string_type = std::string; + using number_type = double; + using integer_type = int64_t; + using boolean_type = bool; + + template + struct variant_overloaded : Ts... { + using Ts::operator()...; + }; + + static jwt::json::type get_type(const value_type& val) { + return std::visit( + variant_overloaded{ + [](boolean_type const&) -> jwt::json::type { return jwt::json::type::boolean; }, + [](integer_type const&) -> jwt::json::type { return jwt::json::type::integer; }, + [](number_type const&) -> jwt::json::type { return jwt::json::type::number; }, + [](string_type const&) -> jwt::json::type { return jwt::json::type::string; }, + [](array_type const&) -> jwt::json::type { return jwt::json::type::array; }, + [](object_type const&) -> jwt::json::type { return jwt::json::type::object; }, + [](std::nullopt_t const&) -> jwt::json::type { throw std::logic_error("invalid type"); }, + }, + val.get()); + } + + static object_type as_object(const value_type& val) { + const auto& variant = val.get(); + if (!std::holds_alternative(variant)) throw std::bad_cast(); + return std::get(variant); + } + + static array_type as_array(const value_type& val) { + const auto& variant = val.get(); + if (!std::holds_alternative(variant)) throw std::bad_cast(); + return std::get(variant); + } + + static string_type as_string(const value_type& val) { + const auto& variant = val.get(); + if (!std::holds_alternative(variant)) throw std::bad_cast(); + return std::get(variant); + } + + static integer_type as_integer(const value_type& val) { + const auto& variant = val.get(); + if (!std::holds_alternative(variant)) throw std::bad_cast(); + return std::get(variant); + } + + static boolean_type as_boolean(const value_type& val) { + const auto& variant = val.get(); + if (!std::holds_alternative(variant)) throw std::bad_cast(); + return std::get(variant); + } + + static number_type as_number(const value_type& val) { + const auto& variant = val.get(); + if (!std::holds_alternative(variant)) throw std::bad_cast(); + return std::get(variant); + } + + static bool parse(value_type& out, string_type const& json) { + auto res = rfl::json::read(json); + if (res) { + out = *std::move(res); + return true; + } + return false; + } + + static std::string serialize(const value_type& val) { return rfl::json::write(val); } + }; + +} // namespace jwt::traits + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7e568db61..cd15a284c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -51,16 +51,22 @@ if(TARGET boost_json) list(APPEND AVAILABLE_TRAIT_TYPES "::jwt::traits::boost_json") endif() +find_package(jsoncpp CONFIG) +if(TARGET jsoncpp_static) + list(APPEND AVAILABLE_TRAIT_NAMES "jsoncpp") + list(APPEND AVAILABLE_TRAIT_TYPES "::jwt::traits::open_source_parsers_jsoncpp") +endif() + find_package(glaze CONFIG) if(TARGET glaze::glaze) list(APPEND AVAILABLE_TRAIT_NAMES "glaze_json") list(APPEND AVAILABLE_TRAIT_TYPES "::jwt::traits::glaze_json") endif() -find_package(jsoncpp CONFIG) -if(TARGET jsoncpp_static) - list(APPEND AVAILABLE_TRAIT_NAMES "jsoncpp") - list(APPEND AVAILABLE_TRAIT_TYPES "::jwt::traits::open_source_parsers_jsoncpp") +find_package(reflectcpp CONFIG) +if(TARGET reflectcpp::reflectcpp) + list(APPEND AVAILABLE_TRAIT_NAMES "reflectcpp") + list(APPEND AVAILABLE_TRAIT_TYPES "::jwt::traits::reflectcpp_json") endif() message(STATUS "jwt-cpp: Generating traits_typelist.h with ${AVAILABLE_TRAIT_NAMES}") @@ -72,7 +78,7 @@ string(APPEND TRAITS_TYPELIST_CONTENT "// Auto-generated list of available JWT t string(APPEND TRAITS_TYPELIST_CONTENT "// Includes all necessary trait headers\n\n") # Include headers for all available traits -string(APPEND TRAITS_TYPELIST_CONTENT "#include \n\n") +string(APPEND TRAITS_TYPELIST_CONTENT "#include \n") string(APPEND TRAITS_TYPELIST_CONTENT "#include \n") if(TARGET jsoncons) @@ -83,16 +89,20 @@ if(TARGET boost_json) string(APPEND TRAITS_TYPELIST_CONTENT "#include \n") endif() +if(TARGET jsoncpp_static) + string(APPEND TRAITS_TYPELIST_CONTENT "#include \n") +endif() + if(TARGET glaze::glaze) string(APPEND TRAITS_TYPELIST_CONTENT "#include \n") endif() -if(TARGET jsoncpp_static) - string(APPEND TRAITS_TYPELIST_CONTENT "#include \n") +if(TARGET reflectcpp::reflectcpp) + string(APPEND TRAITS_TYPELIST_CONTENT "#include \n") endif() # Add the type list using the available traits -string(APPEND TRAITS_TYPELIST_CONTENT "using AllTraitTypes = ::testing::Types<\n") +string(APPEND TRAITS_TYPELIST_CONTENT "\nusing AllTraitTypes = ::testing::Types<\n") list(LENGTH AVAILABLE_TRAIT_TYPES count) math(EXPR last_index "${count} - 1") @@ -127,12 +137,16 @@ if(TARGET boost_json) list(APPEND TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/traits/BoostJsonTest.cpp) endif() +if(TARGET jsoncpp_static) + list(APPEND TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/traits/OspJsoncppTest.cpp) +endif() + if(TARGET glaze::glaze) list(APPEND TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/traits/GlazeTest.cpp) endif() -if(TARGET jsoncpp_static) - list(APPEND TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/traits/OspJsoncppTest.cpp) +if(TARGET reflectcpp::reflectcpp) + list(APPEND TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/traits/ReflectCppJsonTest.cpp) endif() add_executable(jwt-cpp-test ${TEST_SOURCES}) @@ -147,6 +161,7 @@ set(JWT_TESTER_CLANG_FLAGS -Weverything -Wno-c++98-compat -Wno-global-constructo target_compile_options( jwt-cpp-test PRIVATE $<$:/W4> $<$:${JWT_TESTER_GCC_FLAGS}> $<$:${JWT_TESTER_CLANG_FLAGS}>) + if(HUNTER_ENABLED) target_link_libraries(jwt-cpp-test PRIVATE GTest::gtest GTest::gtest_main) # Define a compile define to bypass openssl error tests @@ -160,15 +175,19 @@ else() if(TARGET boost_json) target_link_libraries(jwt-cpp-test PRIVATE boost_json) endif() + if(TARGET jsoncpp_static) + target_link_libraries(jwt-cpp-test PRIVATE jsoncpp_static) + endif() if(TARGET glaze::glaze) + target_compile_features(jwt-cpp-test PRIVATE cxx_std_23) target_link_libraries(jwt-cpp-test PRIVATE glaze::glaze) - target_compile_features(jwt-cpp-test PUBLIC cxx_std_23) - set_target_properties(jwt-cpp-test PROPERTIES CXX_STANDARD 23 CXX_EXTENSIONS OFF CXX_STANDARD_REQUIRED ON) endif() - if(TARGET jsoncpp_static) - target_link_libraries(jwt-cpp-test PRIVATE jsoncpp_static) + if(TARGET reflectcpp::reflectcpp) + target_compile_features(jwt-cpp-test PRIVATE cxx_std_20) + target_link_libraries(jwt-cpp-test PRIVATE reflectcpp::reflectcpp) endif() endif() + target_link_libraries(jwt-cpp-test PRIVATE jwt-cpp nlohmann_json::nlohmann_json $<$>:${CMAKE_DL_LIBS}>) diff --git a/tests/traits/ReflectCppJsonTest.cpp b/tests/traits/ReflectCppJsonTest.cpp new file mode 100644 index 000000000..6eca7391a --- /dev/null +++ b/tests/traits/ReflectCppJsonTest.cpp @@ -0,0 +1,149 @@ +#include "jwt-cpp/traits/reflectcpp-json/traits.h" + +#include + +TEST(ReflectCppTest, BasicClaims) { + const auto string = + jwt::basic_claim(jwt::traits::reflectcpp_json::string_type("string")); + ASSERT_EQ(string.get_type(), jwt::json::type::string); + + const auto array = jwt::basic_claim( + std::set{"string", "string"}); + ASSERT_EQ(array.get_type(), jwt::json::type::array); + + const auto integer = jwt::basic_claim(159816816); + ASSERT_EQ(integer.get_type(), jwt::json::type::integer); +} + +TEST(ReflectCppTest, AudienceAsString) { + jwt::traits::reflectcpp_json::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0In0.WZnM3SIiSRHsbO3O7Z2bmIzTJ4EC32HRBKfLznHhrh4"; + auto decoded = jwt::decode(token); + + ASSERT_TRUE(decoded.has_algorithm()); + ASSERT_TRUE(decoded.has_type()); + ASSERT_FALSE(decoded.has_content_type()); + ASSERT_FALSE(decoded.has_key_id()); + ASSERT_FALSE(decoded.has_issuer()); + ASSERT_FALSE(decoded.has_subject()); + ASSERT_TRUE(decoded.has_audience()); + ASSERT_FALSE(decoded.has_expires_at()); + ASSERT_FALSE(decoded.has_not_before()); + ASSERT_FALSE(decoded.has_issued_at()); + ASSERT_FALSE(decoded.has_id()); + + ASSERT_EQ("HS256", decoded.get_algorithm()); + ASSERT_EQ("JWT", decoded.get_type()); + auto aud = decoded.get_audience(); + ASSERT_EQ(1, aud.size()); + ASSERT_EQ("test", *aud.begin()); +} + +TEST(ReflectCppTest, SetArray) { + jwt::traits::reflectcpp_json::array_type arr{100, 20, 10}; + jwt::traits::reflectcpp_json::value_type value(arr); + jwt::basic_claim array_claim(value); + auto token = + jwt::create().set_payload_claim("test", array_claim).sign(jwt::algorithm::none{}); + ASSERT_EQ(token, "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."); +} + +TEST(ReflectCppTest, SetObject) { + jwt::traits::reflectcpp_json::value_type value; + ASSERT_TRUE(jwt::traits::reflectcpp_json::parse(value, "{\"api-x\": [1]}")); + + // Wrap into a claim and verify type + jwt::basic_claim object(value); + ASSERT_EQ(object.get_type(), jwt::json::type::object); + + auto token = jwt::create() + .set_payload_claim("namespace", object) + .sign(jwt::algorithm::hs256("test")); + ASSERT_EQ(token, + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"); +} + +TEST(ReflectCppTest, VerifyTokenHS256) { + jwt::traits::reflectcpp_json::string_type token = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXUyJ9.eyJpc3MiOiJhdXRoMCJ9.AbIJTDMFc7yUa5MhvcP03nJPyCPzZtQcGEp-zWfOkEE"; + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(ReflectCppTest, VerifyTokenExpirationValid) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now()) + .set_expires_at(std::chrono::system_clock::now() + std::chrono::seconds{3600}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(ReflectCppTest, VerifyTokenExpirationInValid) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_now() + .set_expires_in(std::chrono::hours{1}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + verify.verify(decoded_token); +} + +TEST(ReflectCppTest, VerifyTokenExpired) { + const auto token = jwt::create() + .set_issuer("auth0") + .set_issued_at(std::chrono::system_clock::now() - std::chrono::seconds{3601}) + .set_expires_at(std::chrono::system_clock::now() - std::chrono::seconds{1}) + .sign(jwt::algorithm::hs256{"secret"}); + + const auto decoded_token = jwt::decode(token); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256{"secret"}) + .with_issuer("auth0"); + ASSERT_THROW(verify.verify(decoded_token), jwt::error::token_verification_exception); + + std::error_code ec; + ASSERT_NO_THROW(verify.verify(decoded_token, ec)); + ASSERT_TRUE(!(!ec)); + ASSERT_EQ(ec.category(), jwt::error::token_verification_error_category()); + ASSERT_EQ(ec.value(), static_cast(jwt::error::token_verification_error::token_expired)); +} + +TEST(ReflectCppTest, VerifyArray) { + jwt::traits::reflectcpp_json::string_type token = "eyJhbGciOiJub25lIn0.eyJ0ZXN0IjpbMTAwLDIwLDEwXX0."; + const auto decoded_token = jwt::decode(token); + + jwt::traits::reflectcpp_json::array_type arr{100, 20, 10}; + jwt::basic_claim array_claim{jwt::traits::reflectcpp_json::value_type(arr)}; + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::none{}) + .with_claim("test", array_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +} + +TEST(ReflectCppTest, VerifyObject) { + jwt::traits::reflectcpp_json::string_type token = + "eyJhbGciOiJIUzI1NiJ9.eyJuYW1lc3BhY2UiOnsiYXBpLXgiOlsxXX19.F8I6I2RcSF98bKa0IpIz09fRZtHr1CWnWKx2za-tFQA"; + const auto decoded_token = jwt::decode(token); + + jwt::traits::reflectcpp_json::value_type value; + ASSERT_TRUE(jwt::traits::reflectcpp_json::parse(value, "{\"api-x\": [1]}")); + jwt::basic_claim object_claim(value); + const auto verify = jwt::verify() + .allow_algorithm(jwt::algorithm::hs256("test")) + .with_claim("namespace", object_claim); + ASSERT_NO_THROW(verify.verify(decoded_token)); +}