Skip to content

Commit e48688e

Browse files
committed
fix: OIDC endpoint auth parsing and auth.* template variables (#17)
- parseEndpointAuth(): add oidc block parsing (was silently ignored) - parseAuthConfig(): store parsed config in global_auth_config member instead of discarding into a local variable - loadConfig(): propagate global OIDC config to endpoints with type:oidc that have no local oidc block - ConfigManager: add getGlobalOIDCConfig() accessor - createTemplateContext(): extract __auth_* reserved params and expose as auth.username, auth.roles, auth.email, auth.type in Mustache context - AuthMiddleware::context: add email and auth_type fields - handleDynamicRequest(): inject auth context as __auth_* params - RequestHandler: thread authParams through handleRequest/Get/Write All 425 unit tests pass.
1 parent d9f47cf commit e48688e

10 files changed

Lines changed: 373 additions & 35 deletions

src/api_server.cpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,25 @@ void APIServer::handleDynamicRequest(const crow::request& req, crow::response& r
184184
return;
185185
}
186186

187-
requestHandler.handleRequest(req, res, *endpoint, pathParams);
187+
// Build auth params from middleware context for template variable injection
188+
auto& auth_ctx = app.get_context<AuthMiddleware>(req);
189+
std::map<std::string, std::string> auth_params;
190+
if (auth_ctx.authenticated) {
191+
auth_params["__auth_username"] = auth_ctx.username;
192+
auth_params["__auth_email"] = auth_ctx.email;
193+
auth_params["__auth_type"] = auth_ctx.auth_type;
194+
auth_params["__auth_authenticated"] = "true";
195+
std::string roles;
196+
for (const auto& r : auth_ctx.roles) {
197+
if (!roles.empty()) {
198+
roles += ",";
199+
}
200+
roles += r;
201+
}
202+
auth_params["__auth_roles"] = roles;
203+
}
204+
205+
requestHandler.handleRequest(req, res, *endpoint, pathParams, auth_params);
188206
}
189207

190208
crow::response APIServer::getConfig() {

src/auth_middleware.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,13 @@ void AuthMiddleware::before_handle(crow::request& req, crow::response& res, cont
162162
}
163163

164164
if (endpoint->auth.type == "basic") {
165+
ctx.auth_type = "basic";
165166
ctx.authenticated = authenticateBasic(auth_header, *endpoint, ctx);
166167
} else if (endpoint->auth.type == "bearer") {
168+
ctx.auth_type = "bearer";
167169
ctx.authenticated = authenticateBearer(auth_header, *endpoint, ctx);
168170
} else if (endpoint->auth.type == "oidc") {
171+
ctx.auth_type = "oidc";
169172
ctx.authenticated = authenticateOIDC(auth_header, *endpoint, ctx);
170173
}
171174

@@ -410,6 +413,7 @@ bool AuthMiddleware::authenticateOIDC(const std::string& auth_header, const Endp
410413

411414
// Set authentication context
412415
ctx.username = claims->username;
416+
ctx.email = claims->email;
413417
ctx.roles = claims->roles;
414418
ctx.authenticated = true;
415419

src/config_manager.cpp

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ void ConfigManager::loadConfig() {
7878

7979
std::filesystem::path template_path = getTemplateConfig().path;
8080
loadEndpointConfigsRecursively(template_path);
81+
82+
// Propagate global OIDC config to endpoints that have type:oidc but no local oidc block
83+
if (global_auth_config.oidc) {
84+
for (auto& ep : endpoints) {
85+
if (ep.auth.type == "oidc" && !ep.auth.oidc) {
86+
ep.auth.oidc = global_auth_config.oidc;
87+
CROW_LOG_DEBUG << "Propagated global OIDC config to endpoint: " << ep.urlPath;
88+
}
89+
}
90+
}
91+
8192
CROW_LOG_INFO << "Configuration loaded successfully";
8293
} catch (const YAML::Exception& e) {
8394
std::ostringstream error_msg;
@@ -580,8 +591,14 @@ void ConfigManager::parseEndpointAuth(const YAML::Node& endpoint_config, Endpoin
580591
CROW_LOG_DEBUG << "\t\t\tSecret Key: *****[" << aws_config.secret_key.length() << "]";
581592
}
582593

594+
// Parse OIDC configuration if present
595+
if (auth_node["oidc"]) {
596+
CROW_LOG_DEBUG << "\t\tParsing OIDC configuration for endpoint";
597+
endpoint.auth.oidc = parseOIDCConfigNode(auth_node["oidc"]);
598+
}
599+
583600
// Parse inline users if present
584-
else if (auth_node["users"]) {
601+
if (auth_node["users"]) {
585602
CROW_LOG_DEBUG << "\t\tParsing inline users configuration";
586603
for (const auto& user : auth_node["users"]) {
587604
AuthUser auth_user;
@@ -806,33 +823,37 @@ void ConfigManager::parseAuthConfig() {
806823
auto auth_node = config["auth"];
807824
auth_enabled = auth_node["enabled"].as<bool>();
808825
CROW_LOG_DEBUG << "Auth enabled: " << auth_enabled;
809-
if (auth_enabled) {
810-
AuthConfig auth_config;
811-
auth_config.type = auth_node["type"].as<std::string>();
812-
auth_config.jwt_secret = auth_node["jwt-secret"].as<std::string>();
813-
auth_config.jwt_issuer = auth_node["jwt-issuer"].as<std::string>();
814-
CROW_LOG_DEBUG << "Auth type: " << auth_config.type;
815-
CROW_LOG_DEBUG << "JWT issuer: " << auth_config.jwt_issuer;
816-
817-
if (auth_node["users"]) {
818-
for (const auto& user : auth_node["users"]) {
819-
AuthUser auth_user;
820-
auth_user.username = user["username"].as<std::string>();
821-
auth_user.password = user["password"].as<std::string>();
822-
if (user["roles"]) {
823-
auth_user.roles = user["roles"].as<std::vector<std::string>>();
824-
}
825-
auth_config.users.push_back(auth_user);
826-
CROW_LOG_DEBUG << "Added user: " << auth_user.username << " with " << auth_user.roles.size() << " roles";
826+
827+
if (auth_node["type"]) {
828+
global_auth_config.type = auth_node["type"].as<std::string>();
829+
CROW_LOG_DEBUG << "Auth type: " << global_auth_config.type;
830+
}
831+
if (auth_node["jwt-secret"]) {
832+
global_auth_config.jwt_secret = auth_node["jwt-secret"].as<std::string>();
833+
}
834+
if (auth_node["jwt-issuer"]) {
835+
global_auth_config.jwt_issuer = auth_node["jwt-issuer"].as<std::string>();
836+
CROW_LOG_DEBUG << "JWT issuer: " << global_auth_config.jwt_issuer;
837+
}
838+
839+
if (auth_node["users"]) {
840+
for (const auto& user : auth_node["users"]) {
841+
AuthUser auth_user;
842+
auth_user.username = user["username"].as<std::string>();
843+
auth_user.password = user["password"].as<std::string>();
844+
if (user["roles"]) {
845+
auth_user.roles = user["roles"].as<std::vector<std::string>>();
827846
}
847+
global_auth_config.users.push_back(auth_user);
848+
CROW_LOG_DEBUG << "Added user: " << auth_user.username << " with " << auth_user.roles.size() << " roles";
828849
}
850+
}
829851

830-
// Parse OIDC configuration if present
831-
if (auth_node["oidc"]) {
832-
CROW_LOG_INFO << "Parsing OIDC configuration";
833-
auth_config.oidc = parseOIDCConfigNode(auth_node["oidc"]);
834-
CROW_LOG_INFO << "OIDC configuration parsed successfully";
835-
}
852+
// Parse OIDC configuration if present — store in global_auth_config
853+
if (auth_node["oidc"]) {
854+
CROW_LOG_INFO << "Parsing global OIDC configuration";
855+
global_auth_config.oidc = parseOIDCConfigNode(auth_node["oidc"]);
856+
CROW_LOG_INFO << "Global OIDC configuration parsed: issuer=" << global_auth_config.oidc->issuer_url;
836857
}
837858
}
838859
}
@@ -1111,6 +1132,7 @@ const RateLimitConfig& ConfigManager::getRateLimitConfig() const { return rate_l
11111132
bool ConfigManager::isHttpsEnforced() const { return https_config.enabled; }
11121133
const HttpsConfig& ConfigManager::getHttpsConfig() const { return https_config; }
11131134
bool ConfigManager::isAuthEnabled() const { return auth_enabled; }
1135+
std::optional<OIDCConfig> ConfigManager::getGlobalOIDCConfig() const { return global_auth_config.oidc; }
11141136
const std::vector<EndpointConfig>& ConfigManager::getEndpoints() const { return endpoints; }
11151137
std::string ConfigManager::getBasePath() const { return base_path.string(); }
11161138

src/include/auth_middleware.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ class AuthMiddleware {
4141
struct context {
4242
bool authenticated = false;
4343
std::string username;
44+
std::string email; // populated for OIDC auth, empty otherwise
45+
std::string auth_type; // "basic", "bearer", or "oidc"
4446
std::vector<std::string> roles;
4547
};
4648

src/include/config_manager.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,7 @@ class ConfigManager {
497497
const HttpsConfig& getHttpsConfig() const;
498498
bool isHttpsEnforced() const;
499499
bool isAuthEnabled() const;
500+
std::optional<OIDCConfig> getGlobalOIDCConfig() const;
500501
const EndpointConfig* getEndpointForPath(const std::string& path) const;
501502
const EndpointConfig* getEndpointForPathAndMethod(const std::string& path, const std::string& httpMethod) const;
502503
const std::vector<EndpointConfig>& getEndpoints() const;
@@ -564,6 +565,7 @@ class ConfigManager {
564565
std::unordered_map<std::string, ConnectionConfig> connections;
565566
RateLimitConfig rate_limit_config;
566567
bool auth_enabled;
568+
AuthConfig global_auth_config;
567569
std::vector<EndpointConfig> endpoints;
568570
std::filesystem::path base_path;
569571
DuckDBConfig duckdb_config;

src/include/request_handler.hpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ namespace flapi {
1414
class RequestHandler {
1515
public:
1616
RequestHandler(std::shared_ptr<DatabaseManager> db_manager, std::shared_ptr<ConfigManager> config_manager);
17-
void handleRequest(const crow::request& req, crow::response& res, const EndpointConfig& endpoint, const std::map<std::string, std::string>& pathParams);
17+
void handleRequest(const crow::request& req, crow::response& res, const EndpointConfig& endpoint, const std::map<std::string, std::string>& pathParams, const std::map<std::string, std::string>& authParams = {});
1818

1919
private:
2020
std::map<std::string, std::string> defaultParams;
2121

2222
void handleDeleteRequest(const crow::request& req, crow::response& res, const EndpointConfig& endpoint, const std::map<std::string, std::string>& pathParams);
23-
void handleGetRequest(const crow::request& req, crow::response& res, const EndpointConfig& endpoint, const std::map<std::string, std::string>& pathParams);
24-
void handleWriteRequest(const crow::request& req, crow::response& res, const EndpointConfig& endpoint, const std::map<std::string, std::string>& pathParams);
23+
void handleGetRequest(const crow::request& req, crow::response& res, const EndpointConfig& endpoint, const std::map<std::string, std::string>& pathParams, const std::map<std::string, std::string>& authParams);
24+
void handleWriteRequest(const crow::request& req, crow::response& res, const EndpointConfig& endpoint, const std::map<std::string, std::string>& pathParams, const std::map<std::string, std::string>& authParams);
2525
void handleCacheAfterWrite(const EndpointConfig& endpoint, const WriteResult& writeResult);
2626

2727
bool isCacheDetailsRequest(const crow::request& req, const EndpointConfig& endpoint, const std::map<std::string, std::string>& pathParams);

src/request_handler.cpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ RequestHandler::RequestHandler(std::shared_ptr<DatabaseManager> db_manager, std:
1919
defaultParams["limit"] = "100";
2020
}
2121

22-
void RequestHandler::handleRequest(const crow::request& req, crow::response& res, const EndpointConfig& endpoint, const std::map<std::string, std::string>& pathParams) {
22+
void RequestHandler::handleRequest(const crow::request& req, crow::response& res, const EndpointConfig& endpoint, const std::map<std::string, std::string>& pathParams, const std::map<std::string, std::string>& authParams) {
2323
// Skip if response already completed (e.g., by rate limit or auth middleware)
2424
if (res.is_completed()) {
2525
CROW_LOG_DEBUG << "Skipping request handler - response already completed";
@@ -30,12 +30,12 @@ void RequestHandler::handleRequest(const crow::request& req, crow::response& res
3030

3131
switch (req.method) {
3232
case crow::HTTPMethod::Get:
33-
handleGetRequest(req, res, endpoint, pathParams);
33+
handleGetRequest(req, res, endpoint, pathParams, authParams);
3434
break;
3535
case crow::HTTPMethod::Post:
3636
case crow::HTTPMethod::Put:
3737
case crow::HTTPMethod::Patch:
38-
handleWriteRequest(req, res, endpoint, pathParams);
38+
handleWriteRequest(req, res, endpoint, pathParams, authParams);
3939
break;
4040
case crow::HTTPMethod::Delete:
4141
handleDeleteRequest(req, res, endpoint, pathParams);
@@ -48,11 +48,15 @@ void RequestHandler::handleRequest(const crow::request& req, crow::response& res
4848
}
4949
}
5050

51-
void RequestHandler::handleWriteRequest(const crow::request& req, crow::response& res, const EndpointConfig& endpoint, const std::map<std::string, std::string>& pathParams) {
51+
void RequestHandler::handleWriteRequest(const crow::request& req, crow::response& res, const EndpointConfig& endpoint, const std::map<std::string, std::string>& pathParams, const std::map<std::string, std::string>& authParams) {
5252
try {
5353
// Extract parameters from body, path, query (body takes precedence)
5454
auto params = combineWriteParameters(req, pathParams, endpoint);
55-
55+
// Inject auth context as reserved params for template rendering
56+
for (const auto& [k, v] : authParams) {
57+
params[k] = v;
58+
}
59+
5660
// Validate parameters
5761
auto validationErrors = validator->validateRequestParameters(endpoint.request_fields, params);
5862

@@ -159,9 +163,13 @@ void RequestHandler::handleDeleteRequest(const crow::request& req, crow::respons
159163
}
160164
}
161165

162-
void RequestHandler::handleGetRequest(const crow::request& req, crow::response& res, const EndpointConfig& endpoint, const std::map<std::string, std::string>& pathParams) {
166+
void RequestHandler::handleGetRequest(const crow::request& req, crow::response& res, const EndpointConfig& endpoint, const std::map<std::string, std::string>& pathParams, const std::map<std::string, std::string>& authParams) {
163167
try {
164168
auto params = combineParameters(req, defaultParams, pathParams, endpoint);
169+
// Inject auth context as reserved params for template rendering
170+
for (const auto& [k, v] : authParams) {
171+
params[k] = v;
172+
}
165173

166174
// First validate the known parameters
167175
auto validationErrors = validator->validateRequestParameters(endpoint.request_fields, params);

src/sql_template_processor.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,22 @@ crow::mustache::context SQLTemplateProcessor::createTemplateContext(const Endpoi
150150
params.erase("primaryKeys");
151151
}
152152

153+
// Extract auth params (reserved __auth_ prefix) and inject into ctx["auth"]
154+
static const std::vector<std::pair<std::string, std::string>> auth_keys = {
155+
{"__auth_username", "username"},
156+
{"__auth_roles", "roles"},
157+
{"__auth_email", "email"},
158+
{"__auth_type", "type"},
159+
{"__auth_authenticated", "authenticated"},
160+
};
161+
for (const auto& [param_key, ctx_key] : auth_keys) {
162+
auto it = params.find(param_key);
163+
if (it != params.end()) {
164+
ctx["auth"][ctx_key] = it->second;
165+
params.erase(it);
166+
}
167+
}
168+
153169
// Add request parameters
154170
for (const auto& [key, value] : params) {
155171
ctx["params"][key] = value;

0 commit comments

Comments
 (0)