Skip to content

Commit

Permalink
feat: add JWT implementation (#971)
Browse files Browse the repository at this point in the history
* [INTERNET-MODULE][JWT-TOKEN] Implementation, docstrings and simple test to verification

* [JWT][INTERNET] cleanup

* [PROJECT] sync

* [INTERNET MODULE][JWT] Implementation + test

* [INTERNET MODULE][JWT] Refactoring and wider test coverage

* [PROJECT] submodule update

* [INTERNET DATA] MERGE CONFLICT FIXED

* [INTERNET MODULE] namespace for utility methods anonymized

---------

Co-authored-by: Martin Onufrak <[email protected]>
  • Loading branch information
MartinOnufrak and Martin Onufrak authored Oct 29, 2024
1 parent 8dbe13a commit babc1fe
Show file tree
Hide file tree
Showing 6 changed files with 350 additions and 184 deletions.
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

0 comments on commit babc1fe

Please sign in to comment.