Files
DarkflameServer/dWeb/HTTPContext.h
Aaron Kimbrell 821b7c8767 feat(dashboard): permission grants count in every dashboard permission check
What someone may do on the dashboard is now their GM level's permissions plus the grants on their account, minus its
denies (PermissionGrants.h). A deny beats a grant; denies never apply to GM 9, and settings and permissions_manage stay
GM 9 only. The account's grants are read with every request (like its GM level), so a change applies at once, and
they are passed through every check: RouteUtils::Can, CanViewCharacter, the rank rules (self_* and manage_equal_rank),
routes guarded by a permission, the templates' `can`, the API documentation, API access, API key scopes (a key never
does more than its owner may now) and WebSocket subscriptions.

New permission grants_manage (GM 9 by default) and the API to manage grants: GET /api/grants/catalog, GET /api/grants,
POST /api/grants, POST /api/grants/:id/remove. Nobody grants or takes away what they don't hold themselves (a
permission, every permission of a group, a command they may use, every command up to their own GM level), and only on
accounts the rank rules let them manage (their own with self_moderation). Commands with a fixed level or a floor
above GM 1 (/execute) can't be granted. Every change goes in the audit log (grant_permission, deny_permission,
remove_grant). Also: the Showcase gate and the traffic subscription now check their permission by name.

Check: grant a GM 2 account accounts_ban (it can ban, and the Ban button shows); deny a GM 8 account accounts_view (the
accounts list is refused); give an expiry a minute ahead and see it stop; try to grant a permission your account
doesn't have (refused); dWebTests PermissionGrantsTests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 01:16:38 -05:00

84 lines
2.7 KiB
C++

#pragma once
#include <string>
#include <map>
#include <memory>
#include <algorithm>
#include "eHTTPStatusCode.h"
#include "json.hpp"
#include "ApiKeyScope.h"
namespace PermissionGrants { struct Held; }
/**
* HTTP Request Context
*
* Carries all request metadata through the middleware chain.
* Populated by the Web framework before middleware/handlers are called.
*/
struct HTTPContext {
// Request metadata
std::string method{};
std::string path{}; // lowercased: routes match without regard to case
std::string originalPath{}; // as sent, for parameters whose case matters (file names)
std::string queryString{};
std::string body{};
// Request headers (header name -> value)
// Header names are lowercase for case-insensitive lookup
std::map<std::string, std::string> headers{};
// Client information
std::string clientIP{};
unsigned long connectionId = 0; // the web server's id of the connection (for Web::Defer)
// Authentication information (populated by auth middleware)
bool isAuthenticated = false;
std::string authenticatedUser{};
uint32_t accountId = 0;
uint8_t gmLevel = 0;
// Set when an API key authenticated the request: the key's scope, on top of what the account may do (gmLevel).
// Every permission check must honour it (RouteUtils::Can and friends do).
std::shared_ptr<const ApiKeys::Scope> apiKey{};
// The account's permission grants in force (PermissionGrants.h), loaded with the sign-in; every permission check
// passes them on (RouteUtils::Can and friends do). nullptr: none were loaded, the GM level alone counts.
std::shared_ptr<const PermissionGrants::Held> grants{};
// Custom data for middleware to communicate
std::map<std::string, std::string> userData{};
/**
* Get header value (case-insensitive)
*/
const std::string& GetHeader(const std::string& headerName) const {
static const std::string empty{};
// Convert to lowercase for comparison
std::string lowerName = headerName;
std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower);
const auto it = headers.find(lowerName);
return it != headers.end() ? it->second : empty;
}
/**
* Set header value (automatically lowercased)
*/
void SetHeader(const std::string& headerName, const std::string& value) {
std::string lowerName = headerName;
std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::tolower);
headers[lowerName] = value;
}
/**
* Get user data as JSON for template rendering
*/
nlohmann::json GetUserDataJson() const {
nlohmann::json userData;
userData["username"] = authenticatedUser;
userData["gmLevel"] = gmLevel;
userData["accountId"] = accountId;
return userData;
}
};