Skip to content

Commit cf927fd

Browse files
committed
- Moving error messages out of TokenRequest.
- Updated logic to populate TokenResponse with error message and status code. - Added TokenWithExpiration struct to auth_utils.h and auth_utils.cpp.
1 parent 8daff84 commit cf927fd

7 files changed

Lines changed: 216 additions & 138 deletions

File tree

src/common/json_request.cpp

Lines changed: 63 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -130,105 +130,133 @@ std::string EncodeBase64Safe(const std::vector<uint8_t>& data) {
130130
}
131131

132132
void TokenRequest::Parse(const std::string& request_body) {
133-
client_id.clear();
134-
api_key.clear();
135-
error_message = std::nullopt;
133+
client_id_.clear();
134+
api_key_.clear();
136135

137136
auto json_body_opt = SafeLoadJsonBody(request_body);
138137
if (!json_body_opt) {
139-
error_message = "Invalid JSON in request body. Invalid format.";
140138
return;
141139
}
142140

143141
auto json_body = *json_body_opt;
144142
if (json_body.t() != crow::json::type::Object) {
145-
error_message = "Invalid JSON in request body. Invalid format.";
146143
return;
147144
}
148145

149146
if (json_body.has("client_id") && json_body["client_id"].t() == crow::json::type::String) {
150-
client_id = std::string(json_body["client_id"]);
147+
client_id_ = std::string(json_body["client_id"]);
151148
} else {
152-
error_message = "Missing required field: client_id";
153149
return;
154150
}
155151

156152
if (json_body.has("api_key") && json_body["api_key"].t() == crow::json::type::String) {
157-
api_key = std::string(json_body["api_key"]);
153+
api_key_ = std::string(json_body["api_key"]);
158154
} else {
159-
error_message = "Missing required field: api_key";
160155
return;
161156
}
162157
}
163158

164159
std::string TokenRequest::ToJson() const {
165160
crow::json::wvalue json;
166-
if (error_message.has_value()) {
167-
json["error"] = error_message.value();
161+
if (!IsValid()) {
162+
json["error"] = GetValidationError();
168163
return PrettyPrintJson(json.dump());
169164
}
170165

171-
json["client_id"] = client_id;
172-
json["api_key"] = api_key;
166+
json["client_id"] = client_id_;
167+
json["api_key"] = api_key_;
173168
return PrettyPrintJson(json.dump());
174169
}
175170

171+
bool TokenRequest::IsValid() const {
172+
return !client_id_.empty() && !api_key_.empty();
173+
}
174+
175+
std::string TokenRequest::GetValidationError() const {
176+
if (IsValid()) {
177+
return "";
178+
}
179+
std::vector<std::string> missing_fields;
180+
if (client_id_.empty()) missing_fields.push_back("client_id");
181+
if (api_key_.empty()) missing_fields.push_back("api_key");
182+
return BuildValidationError(missing_fields);
183+
}
184+
176185
void TokenResponse::Parse(const std::string& response_body) {
177-
token = std::nullopt;
178-
expires_at = std::nullopt;
179-
error_message = std::nullopt;
180-
error_status_code = 400;
186+
token_ = std::nullopt;
187+
expires_at_ = std::nullopt;
188+
error_message_.clear();
189+
error_status_code_ = 0;
181190

182191
auto json_body_opt = SafeLoadJsonBody(response_body);
183192
if (!json_body_opt) {
184-
error_message = "Invalid JSON in response body. Invalid format.";
185193
return;
186194
}
187195

188196
auto json_body = *json_body_opt;
189197
if (json_body.t() != crow::json::type::Object) {
190-
error_message = "Invalid JSON in response body. Invalid format.";
191198
return;
192199
}
193200

194201
if (json_body.has("error")) {
195-
error_message = std::string(json_body["error"]);
202+
error_message_ = std::string(json_body["error"]);
196203
return;
197204
}
198205

199206
if (auto parsed_value = SafeGetFromJsonPath(json_body, {"token"})) {
200-
token = *parsed_value;
207+
token_ = *parsed_value;
201208
}
202209

203210
if (auto parsed_value = SafeGetFromJsonPath(json_body, {"expires_at"})) {
204211
try {
205-
expires_at = std::stoll(*parsed_value);
212+
expires_at_ = std::stoll(*parsed_value);
206213
} catch (const std::exception&) {
207-
error_message = "Invalid expires_at field";
208214
return;
209215
}
210216
}
211217
}
212218

213-
std::string TokenResponse::ToJson() const {
214-
crow::json::wvalue json;
219+
bool TokenResponse::IsValid() const {
220+
return error_message_.empty() &&
221+
token_.has_value() &&
222+
!token_.value().empty() &&
223+
expires_at_.has_value();
224+
}
215225

216-
if (error_message.has_value()) {
217-
json["error"] = error_message.value();
218-
return json.dump();
226+
std::string TokenResponse::GetValidationError() const {
227+
if (error_status_code_ == 401) {
228+
return "Invalid credentials";
229+
}
230+
if (!error_message_.empty()) {
231+
return error_message_;
219232
}
233+
if (IsValid()) {
234+
return "";
235+
}
236+
std::vector<std::string> missing_fields;
237+
if (!token_.has_value() || token_.value().empty()) missing_fields.push_back("token");
238+
if (!expires_at_.has_value()) missing_fields.push_back("expires_at");
239+
return BuildValidationError(missing_fields);
240+
}
241+
242+
void TokenResponse::SetErrorStatusCodeAndClearToken(int status_code) {
243+
token_ = std::nullopt;
244+
expires_at_ = std::nullopt;
245+
error_message_.clear();
246+
error_status_code_ = status_code;
247+
}
220248

221-
if (!token.has_value()) {
222-
json["error"] = "Invalid token response";
249+
std::string TokenResponse::ToJson() const {
250+
crow::json::wvalue json;
251+
252+
if (!IsValid()) {
253+
json["error"] = GetValidationError();
223254
return json.dump();
224255
}
225256

226-
json["token"] = token.value();
257+
json["token"] = token_.value();
227258
json["token_type"] = "Bearer";
228-
229-
if (expires_at.has_value()) {
230-
json["expires_at"] = expires_at.value();
231-
}
259+
json["expires_at"] = expires_at_.value();
232260

233261
return PrettyPrintJson(json.dump());
234262
}

src/common/json_request.h

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
#include <optional>
2323
#include <map>
2424
#include <cstdint>
25-
#include <utility>
2625
#include "enums.h"
2726
#include "enum_utils.h"
2827

@@ -332,24 +331,35 @@ class DecryptJsonResponse : public JsonResponse {
332331
/**
333332
* Structure to hold parsed token request data.
334333
*/
335-
struct TokenRequest {
336-
std::string client_id;
337-
std::string api_key;
338-
std::optional<std::string> error_message;
334+
class TokenRequest {
335+
public:
336+
TokenRequest() = default;
337+
338+
std::string client_id_;
339+
std::string api_key_;
339340

340341
void Parse(const std::string& request_body);
342+
bool IsValid() const;
343+
std::string GetValidationError() const;
341344
std::string ToJson() const;
342345
};
343346

344347
/**
345348
* Structure to hold token generation result.
346349
*/
347-
struct TokenResponse {
348-
std::optional<std::string> token;
349-
std::optional<std::int64_t> expires_at;
350-
std::optional<std::string> error_message;
351-
int error_status_code = 400;
350+
class TokenResponse {
351+
public:
352+
TokenResponse() = default;
353+
354+
std::optional<std::string> token_;
355+
std::optional<std::int64_t> expires_at_;
356+
std::string error_message_;
357+
int error_status_code_ = 0;
352358

353359
void Parse(const std::string& response_body);
360+
bool IsValid() const;
361+
std::string GetValidationError() const;
354362
std::string ToJson() const;
363+
364+
void SetErrorStatusCodeAndClearToken(int status_code);
355365
};

src/common/json_request_test.cpp

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -946,26 +946,28 @@ TEST(JsonRequest, TokenRequestParse) {
946946
{
947947
TokenRequest req;
948948
req.Parse(R"({"client_id":"client1","api_key":"key1"})");
949-
ASSERT_FALSE(req.error_message.has_value());
950-
ASSERT_EQ(req.client_id, "client1");
951-
ASSERT_EQ(req.api_key, "key1");
949+
ASSERT_TRUE(req.IsValid());
950+
ASSERT_EQ(req.client_id_, "client1");
951+
ASSERT_EQ(req.api_key_, "key1");
952952
}
953953
{
954954
TokenRequest req;
955955
req.Parse(R"({"api_key":"key1"})");
956-
ASSERT_TRUE(req.error_message.has_value());
956+
ASSERT_FALSE(req.IsValid());
957+
ASSERT_FALSE(req.GetValidationError().empty());
957958
}
958959
{
959960
TokenRequest req;
960961
req.Parse("{ invalid json }");
961-
ASSERT_TRUE(req.error_message.has_value());
962+
ASSERT_FALSE(req.IsValid());
963+
ASSERT_FALSE(req.GetValidationError().empty());
962964
}
963965
}
964966

965967
TEST(JsonRequest, TokenRequestToJson) {
966968
TokenRequest req;
967-
req.client_id = "clientA";
968-
req.api_key = "keyA";
969+
req.Parse(R"({"client_id":"clientA","api_key":"keyA"})");
970+
ASSERT_TRUE(req.IsValid());
969971

970972
auto json = crow::json::load(req.ToJson());
971973
ASSERT_TRUE(json);
@@ -975,8 +977,8 @@ TEST(JsonRequest, TokenRequestToJson) {
975977

976978
TEST(JsonRequest, TokenResponseRoundTrip) {
977979
TokenResponse resp;
978-
resp.token = "token123";
979-
resp.expires_at = 1234567890;
980+
resp.Parse(R"({"token":"token123","expires_at":1234567890})");
981+
ASSERT_TRUE(resp.IsValid());
980982

981983
auto json = crow::json::load(resp.ToJson());
982984
ASSERT_TRUE(json);
@@ -989,15 +991,27 @@ TEST(JsonRequest, TokenResponseRoundTrip) {
989991

990992
TokenResponse parsed;
991993
parsed.Parse(resp.ToJson());
992-
ASSERT_FALSE(parsed.error_message.has_value());
993-
ASSERT_TRUE(parsed.token.has_value());
994-
ASSERT_TRUE(parsed.expires_at.has_value());
995-
ASSERT_EQ(parsed.token.value(), "token123");
996-
ASSERT_EQ(parsed.expires_at.value(), 1234567890);
994+
ASSERT_TRUE(parsed.IsValid());
995+
ASSERT_EQ(parsed.token_.value(), "token123");
996+
ASSERT_EQ(parsed.expires_at_.value(), 1234567890);
997997
}
998998

999999
TEST(JsonRequest, TokenResponseParseError) {
10001000
TokenResponse resp;
10011001
resp.Parse(R"({"error":"unauthorized"})");
1002-
ASSERT_TRUE(resp.error_message.has_value());
1002+
ASSERT_FALSE(resp.IsValid());
1003+
ASSERT_FALSE(resp.GetValidationError().empty());
1004+
}
1005+
1006+
TEST(JsonRequest, TokenResponseSetErrorStatusCodeAndClearToken) {
1007+
TokenResponse resp;
1008+
resp.Parse(R"({"token":"token123","expires_at":1234567890})");
1009+
ASSERT_TRUE(resp.IsValid());
1010+
1011+
resp.SetErrorStatusCodeAndClearToken(401);
1012+
ASSERT_FALSE(resp.IsValid());
1013+
ASSERT_FALSE(resp.token_.has_value());
1014+
ASSERT_FALSE(resp.expires_at_.has_value());
1015+
ASSERT_EQ(resp.error_status_code_, 401);
1016+
ASSERT_TRUE(resp.GetValidationError().find("Invalid credentials") != std::string::npos);
10031017
}

src/server/auth_utils.cpp

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@
2222
#include <fstream>
2323
#include <nlohmann/json.hpp>
2424

25-
// JWT expiration time: 4 hours in seconds
26-
static const int JWT_EXPIRATION_SECONDS = 4 * 60 * 60; // 14400 seconds
27-
2825
// ClientCredentialStore implementation
2926

3027
// Constructor
@@ -110,7 +107,7 @@ bool ClientCredentialStore::HasClientId(const std::string& client_id) const {
110107
}
111108

112109
// GenerateJWT implementation
113-
std::optional<std::pair<std::string, std::int64_t>> ClientCredentialStore::GenerateJWT(
110+
std::optional<ClientCredentialStore::TokenWithExpiration> ClientCredentialStore::GenerateJWT(
114111
const std::string& client_id,
115112
const std::string& api_key) const {
116113
// Validate that client_id is not empty
@@ -145,7 +142,7 @@ std::optional<std::pair<std::string, std::int64_t>> ClientCredentialStore::Gener
145142
.set_expires_at(std::chrono::system_clock::from_time_t(exp_seconds))
146143
.sign(jwt::algorithm::hs256{jwt_secret_key_});
147144

148-
return std::make_pair(token, static_cast<std::int64_t>(exp_seconds));
145+
return TokenWithExpiration{token, static_cast<std::int64_t>(exp_seconds)};
149146
} catch (const std::exception& e) {
150147
std::cerr << "Error generating JWT: " << e.what() << std::endl;
151148
return std::nullopt;
@@ -161,31 +158,32 @@ TokenResponse ClientCredentialStore::ProcessTokenRequest(const std::string& requ
161158
token_req.Parse(request_body);
162159

163160
// Check if parsing resulted in an error
164-
if (token_req.error_message.has_value()) {
165-
response.error_message = token_req.error_message.value();
166-
response.error_status_code = 400;
161+
if (!token_req.IsValid()) {
162+
response.SetErrorStatusCodeAndClearToken(400);
163+
response.error_message_ = token_req.GetValidationError();
167164
return response;
168165
}
169166

170167
// Log the request for debugging
171168
std::cout << "=== ProcessTokenRequest ===" << std::endl;
172-
std::cout << "client_id: " << token_req.client_id << std::endl;
169+
std::cout << "client_id: " << token_req.client_id_ << std::endl;
173170
std::cout << "====================" << std::endl;
174171

175172
// Generate JWT token (validates credentials internally)
176-
auto token = GenerateJWT(token_req.client_id, token_req.api_key);
173+
auto token = GenerateJWT(token_req.client_id_, token_req.api_key_);
177174

178175
if (!token.has_value()) {
179-
response.error_message = "Invalid credentials";
180-
response.error_status_code = 401;
176+
response.SetErrorStatusCodeAndClearToken(401);
177+
response.error_message_ = "Invalid credentials";
181178
return response;
182179
}
183180

184-
response.token = token->first;
185-
response.expires_at = token->second;
181+
response.token_ = token->token;
182+
response.expires_at_ = token->expires_at;
183+
response.error_status_code_ = 200;
186184

187185
std::cout << "=== ProcessTokenRequest (Success) ===" << std::endl;
188-
std::cout << "Token generated for client_id: " << token_req.client_id << std::endl;
186+
std::cout << "Token generated for client_id: " << token_req.client_id_ << std::endl;
189187
std::cout << "=================================" << std::endl;
190188

191189
return response;

0 commit comments

Comments
 (0)