Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add JWT implementation #971

Merged
merged 12 commits into from
Oct 29, 2024
39 changes: 39 additions & 0 deletions include/faker-cxx/internet.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include <array>
#include <ctime>
#include <map>
#include <optional>
#include <string>
#include <string_view>
Expand Down Expand Up @@ -350,4 +352,41 @@ FAKER_CXX_EXPORT std::string_view domainSuffix();
* @endcode
*/
FAKER_CXX_EXPORT std::string anonymousUsername(unsigned maxLength);

/**
* @brief Generates a JSON Web Token (JWT).
*
* This function generates a JWT using the provided header, payload, and reference date.
* If no header or payload is provided, default values will be used.
* The reference date is optional and can be used to set the "iat" (issued at) claim in the payload.
*
* @param header The optional header map to include in the JWT. Defaults to a standard header.
* @param payload The optional payload map to include in the JWT. Defaults to a standard payload.
* @param refDate The optional reference date to set the "iat" claim. Defaults to the current date and time.
*
* @returns A string representing the generated JWT.
*
* @code
* std::map<std::string, std::string> header = {{"alg", "HS256"}, {"typ", "JWT"}};
* std::map<std::string, std::string> payload = {{"sub", "1234567890"}, {"name", "John Doe"}, {"admin", "true"}};
* faker::internet::getJWTToken(header, payload); // "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
* @endcode
*/
FAKER_CXX_EXPORT std::string
getJWTToken(const std::optional<std::map<std::string, std::string>>& header = std::nullopt,
const std::optional<std::map<std::string, std::string>>& payload = std::nullopt,
const std::optional<std::string>& refDate = std::nullopt);

/**
* @brief Returns the algorithm used for signing the JWT.
*
* This function provides the algorithm that is used to sign the JSON Web Token (JWT).
*
* @returns A string view representing the JWT signing algorithm.
*
* @code
* faker::internet::getJWTAlgorithm(); // "HS256"
* @endcode
*/
FAKER_CXX_EXPORT std::string_view getJWTAlgorithm();
}
135 changes: 135 additions & 0 deletions src/modules/internet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

#include <algorithm>
#include <array>
#include <cmath>
#include <initializer_list>
#include <map>
#include <optional>
#include <regex>
#include <string>
#include <string_view>
#include <utility>
Expand All @@ -13,6 +15,8 @@
#include "common/algo_helper.h"
#include "common/format_helper.h"
#include "common/string_helper.h"
#include "faker-cxx/company.h"
#include "faker-cxx/faker.h"
#include "faker-cxx/helper.h"
#include "faker-cxx/number.h"
#include "faker-cxx/person.h"
Expand All @@ -22,6 +26,89 @@
#include "internet_data.h"
#include "modules/string_data.h"

namespace
{
/**
* @brief Encodes a given string to Base64 URL format.
*
* This function takes an input string and converts it into a Base64 URL encoded string.
* Base64 URL encoding is a variant of Base64 encoding that is URL-safe.
*
* @param input The string to be encoded.
*
* @returns A Base64 URL encoded string.
*
* @code
* std::string input = "Hello, World!";
* faker::internet::toBase64UrlEncode(input); // "SGVsbG8sIFdvcmxkIQ"
* @endcode
*/
std::string toBase64UrlEncode(const std::string& input)
{
const std::string base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string encodedInput;

int value = 0;
int validBits = -6;

for (unsigned char character : input)
{
value = (value << 8) + character;
validBits += 8;
while (validBits >= 0)
{
encodedInput.push_back(base64Chars[(value >> validBits) & 0x3F]);
validBits -= 6;
}
}

if (validBits > -6)
{
encodedInput.push_back(base64Chars[((value << 8) >> validBits) & 0x3F]);
}

while (encodedInput.size() % 4)
{
encodedInput.push_back('=');
}

std::replace(encodedInput.begin(), encodedInput.end(), '+', '-');
std::replace(encodedInput.begin(), encodedInput.end(), '/', '_');
encodedInput = std::regex_replace(encodedInput, std::regex{"=+$"}, "");

return encodedInput;
}

/**
* @brief Converts a map of key-value pairs to a JSON string.
*
* This function takes a map where both keys and values are strings and converts it into a JSON formatted string.
*
* @param data The map containing key-value pairs to be converted to JSON.
*
* @returns A JSON formatted string representing the input map.
*
* @code
* std::map<std::string, std::string> data = {{"name", "John"}, {"age", "30"}};
* faker::internet::toJSON(data); // json is now "{\"name\":\"John\",\"age\":\"30\"}"
* @endcode
*/
std::string toJSON(std::map<std::string, std::string>& data)
{
std::string json = "{";
for (auto it = data.begin(); it != data.end(); ++it)
{
if (it != data.begin())
{
json += ",";
}
json += "\"" + it->first + "\":\"" + it->second + "\"";
}
json += "}";
return json;
}
}

namespace faker::internet
{
namespace
Expand Down Expand Up @@ -348,4 +435,52 @@ std::string anonymousUsername(unsigned maxLength)
return common::format("{}{}", word::adjective(adjectiveLength), word::noun(nounLength));
}

std::string_view getJWTAlgorithm()
{
return helper::randomElement(jwtAlgorithms);
}

std::string getJWTToken(const std::optional<std::map<std::string, std::string>>& header,
const std::optional<std::map<std::string, std::string>>& payload,
const std::optional<std::string>& refDate)
{
const auto refDateValue = refDate.value_or(faker::date::anytime());

// maybe add option to set ref date to date functions then refactor this
const auto iatDefault = faker::date::recentDate(faker::date::dayOfMonth(), faker::date::DateFormat::Timestamp);
const auto expDefault = faker::date::soonDate(faker::date::dayOfMonth(), faker::date::DateFormat::Timestamp);
const auto nbfDefault = faker::date::anytime(faker::date::DateFormat::Timestamp);

std::optional<std::map<std::string, std::string>> localHeader = header;
std::optional<std::map<std::string, std::string>> localPayload = payload;

std::string algorithm(getJWTAlgorithm());

if (!localHeader)
{
localHeader = {{"alg", algorithm}, {"typ", "JWT"}};
}

if (!localPayload)
{
localPayload = {{"iat", std::to_string(std::round(std::stoll(iatDefault)))},
{"exp", std::to_string(std::round(std::stoll(expDefault)))},
{"nbf", std::to_string(std::round(std::stoll(nbfDefault)))},
{"iss", faker::company::companyName()},
{"sub", faker::string::uuid()},
{"aud", faker::string::uuid()},
{"jti", faker::string::uuid()}};
}

const auto headerToJSON = toJSON(localHeader.value());
const auto encodedHeader = toBase64UrlEncode(headerToJSON);

const auto payloadToJSON = toJSON(localPayload.value());
const auto encodedPayload = toBase64UrlEncode(payloadToJSON);

const auto signature = faker::string::alphanumeric(64);

return encodedHeader + "." + encodedPayload + "." + signature;
}

}
Loading
Loading