Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/actions/install/reflectcpp-json/action.yml
Original file line number Diff line number Diff line change
@@ -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
48 changes: 48 additions & 0 deletions .github/actions/install/reflectcpp-json/install-reflect-cpp.sh
Original file line number Diff line number Diff line change
@@ -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!"
2 changes: 1 addition & 1 deletion .github/workflows/jwt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/traits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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: |
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
4 changes: 3 additions & 1 deletion docs/traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ 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
[jsoncons]: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Thalhammer/jwt-cpp/badges/traits/danielaparker-jsoncons/shields.json
[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.

Expand Down
8 changes: 8 additions & 0 deletions example/traits/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
56 changes: 56 additions & 0 deletions example/traits/reflectcpp-json.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include "jwt-cpp/jwt.h"
#include "jwt-cpp/traits/reflectcpp-json/traits.h"

#include <chrono>
#include <iostream>

int main() {
using sec = std::chrono::seconds;
using min = std::chrono::minutes;
using traits = jwt::traits::reflectcpp_json;
using claim = jwt::basic_claim<traits>;

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<int64_t> big_numbers{727663072LL, 770979831LL, 427239169LL, 525936436LL};

const auto time = jwt::date::clock::now();
const auto token = jwt::create<traits>()
.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<traits>(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<traits>()
.allow_algorithm(jwt::algorithm::none{})
.with_issuer("auth.mydomain.io")
.with_audience("mydomain.io")
.with_claim("object", from_raw_json)
.verify(decoded);

return 0;
}
91 changes: 91 additions & 0 deletions include/jwt-cpp/traits/reflectcpp-json/defaults.h
Original file line number Diff line number Diff line change
@@ -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<traits::reflectcpp_json>;

/**
* Create a verifier using the default clock
* \return verifier instance
*/
inline verifier<default_clock, traits::reflectcpp_json> verify() {
return verify<default_clock, traits::reflectcpp_json>(default_clock{});
}

/**
* Create a builder using the default clock
* \return builder instance to create a new token
*/
inline builder<default_clock, traits::reflectcpp_json> create() {
return builder<default_clock, traits::reflectcpp_json>(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<traits::reflectcpp_json> decode(const std::string& token) {
return decoded_jwt<traits::reflectcpp_json>(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<typename Decode>
decoded_jwt<traits::reflectcpp_json> decode(const std::string& token, Decode decode) {
return decoded_jwt<traits::reflectcpp_json>(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<traits::reflectcpp_json> parse_jwk(const traits::reflectcpp_json::string_type& token) {
return jwk<traits::reflectcpp_json>(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<traits::reflectcpp_json> parse_jwks(const traits::reflectcpp_json::string_type& token) {
return jwks<traits::reflectcpp_json>(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<traits::reflectcpp_json>;
} // namespace jwt

#endif // JWT_CPP_REFLECTCPP_JSON_DEFAULTS_H
95 changes: 95 additions & 0 deletions include/jwt-cpp/traits/reflectcpp-json/traits.h
Original file line number Diff line number Diff line change
@@ -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 <rfl/Generic.hpp>
#include <rfl/json.hpp>
#include <variant>

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<class... Ts>
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<object_type>(variant)) throw std::bad_cast();
return std::get<object_type>(variant);
}

static array_type as_array(const value_type& val) {
const auto& variant = val.get();
if (!std::holds_alternative<array_type>(variant)) throw std::bad_cast();
return std::get<array_type>(variant);
}

static string_type as_string(const value_type& val) {
const auto& variant = val.get();
if (!std::holds_alternative<string_type>(variant)) throw std::bad_cast();
return std::get<string_type>(variant);
}

static integer_type as_integer(const value_type& val) {
const auto& variant = val.get();
if (!std::holds_alternative<integer_type>(variant)) throw std::bad_cast();
return std::get<integer_type>(variant);
}

static boolean_type as_boolean(const value_type& val) {
const auto& variant = val.get();
if (!std::holds_alternative<boolean_type>(variant)) throw std::bad_cast();
return std::get<boolean_type>(variant);
}

static number_type as_number(const value_type& val) {
const auto& variant = val.get();
if (!std::holds_alternative<number_type>(variant)) throw std::bad_cast();
return std::get<number_type>(variant);
}

static bool parse(value_type& out, string_type const& json) {
auto res = rfl::json::read<rfl::Generic>(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
Loading