| 753 | } |
| 754 | |
| 755 | bool RestServer::CheckAuthentication(const httplib::Request& req, httplib::Response& res) |
| 756 | { |
| 757 | const auto& config = *m_RunningConfig; |
| 758 | |
| 759 | if (!config.requireAuth) |
| 760 | { |
| 761 | return true; |
| 762 | } |
| 763 | |
| 764 | // Exempt health and info endpoints |
| 765 | if (IsAuthExemptPath(req.path)) |
| 766 | { |
| 767 | return true; |
| 768 | } |
| 769 | |
| 770 | // Check Authorization header |
| 771 | if (!req.has_header("Authorization")) |
| 772 | { |
| 773 | auto error = ErrorResponse::Unauthorized("Authentication required. Provide Authorization: Bearer <token> header.", req.path); |
| 774 | res.status = 401; |
| 775 | res.set_header("WWW-Authenticate", "Bearer"); |
| 776 | res.set_content(error.dump(), "application/json"); |
| 777 | |
| 778 | this->RecordRequest(req.path, req.method, res.status, req.remote_addr); |
| 779 | return false; |
| 780 | } |
| 781 | |
| 782 | const std::string authHeader = req.get_header_value("Authorization"); |
| 783 | |
| 784 | // Validate Bearer token format |
| 785 | const std::string bearerPrefix = "Bearer "; |
| 786 | if (authHeader.size() <= bearerPrefix.size() || |
| 787 | authHeader.substr(0, bearerPrefix.size()) != bearerPrefix) |
| 788 | { |
| 789 | auto error = ErrorResponse::Unauthorized("Invalid authorization format. Expected: Bearer <token>", req.path); |
| 790 | res.status = 401; |
| 791 | res.set_header("WWW-Authenticate", "Bearer"); |
| 792 | res.set_content(error.dump(), "application/json"); |
| 793 | |
| 794 | this->RecordRequest(req.path, req.method, res.status, req.remote_addr); |
| 795 | return false; |
| 796 | } |
| 797 | |
| 798 | const std::string providedToken = authHeader.substr(bearerPrefix.size()); |
| 799 | |
| 800 | // Constant-time comparison to prevent timing attacks |
| 801 | if (!ConstantTimeCompare(providedToken, config.apiToken)) |
| 802 | { |
| 803 | // Log every failed auth attempt for audit purposes. |
| 804 | // NOTE: auth-specific brute-force throttling is not implemented separately. |
| 805 | // Enable rate limiting (rateLimitEnabled=true) to bound the overall request |
| 806 | // volume from any single IP when authentication is required. |
| 807 | MITK_WARN << "REST API: authentication failed from " << SanitizeForLog(req.remote_addr); |
| 808 | |
| 809 | auto error = ErrorResponse::Unauthorized("Invalid API token", req.path); |
| 810 | res.status = 401; |
| 811 | res.set_header("WWW-Authenticate", "Bearer"); |
| 812 | res.set_content(error.dump(), "application/json"); |
no test coverage detected