1 #pragma once 2 #include "bmcweb_config.h" 3 4 #include "authorization.hpp" 5 #include "http_response.hpp" 6 #include "http_utility.hpp" 7 #include "logging.hpp" 8 #include "timer_queue.hpp" 9 #include "utility.hpp" 10 11 #include <boost/algorithm/string.hpp> 12 #include <boost/algorithm/string/predicate.hpp> 13 #include <boost/asio/io_context.hpp> 14 #include <boost/asio/ip/tcp.hpp> 15 #include <boost/asio/ssl/stream.hpp> 16 #include <boost/beast/core/flat_static_buffer.hpp> 17 #include <boost/beast/ssl/ssl_stream.hpp> 18 #include <boost/beast/websocket.hpp> 19 #include <boost/url/url_view.hpp> 20 #include <json_html_serializer.hpp> 21 #include <security_headers.hpp> 22 #include <ssl_key_handler.hpp> 23 24 #include <atomic> 25 #include <chrono> 26 #include <vector> 27 28 namespace crow 29 { 30 31 inline void prettyPrintJson(crow::Response& res) 32 { 33 json_html_util::dumpHtml(res.body(), res.jsonValue); 34 35 res.addHeader("Content-Type", "text/html;charset=UTF-8"); 36 } 37 38 #ifdef BMCWEB_ENABLE_DEBUG 39 static std::atomic<int> connectionCount; 40 #endif 41 42 // request body limit size set by the bmcwebHttpReqBodyLimitMb option 43 constexpr unsigned int httpReqBodyLimit = 44 1024 * 1024 * bmcwebHttpReqBodyLimitMb; 45 46 constexpr uint64_t loggedOutPostBodyLimit = 4096; 47 48 constexpr uint32_t httpHeaderLimit = 8192; 49 50 // drop all connections after 1 minute, this time limit was chosen 51 // arbitrarily and can be adjusted later if needed 52 static constexpr const size_t loggedInAttempts = 53 (60 / timerQueueTimeoutSeconds); 54 55 static constexpr const size_t loggedOutAttempts = 56 (15 / timerQueueTimeoutSeconds); 57 58 template <typename Adaptor, typename Handler> 59 class Connection : 60 public std::enable_shared_from_this<Connection<Adaptor, Handler>> 61 { 62 public: 63 Connection(Handler* handlerIn, 64 std::function<std::string()>& getCachedDateStrF, 65 detail::TimerQueue& timerQueueIn, Adaptor adaptorIn) : 66 adaptor(std::move(adaptorIn)), 67 handler(handlerIn), getCachedDateStr(getCachedDateStrF), 68 timerQueue(timerQueueIn) 69 { 70 parser.emplace(std::piecewise_construct, std::make_tuple()); 71 parser->body_limit(httpReqBodyLimit); 72 parser->header_limit(httpHeaderLimit); 73 74 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 75 prepareMutualTls(); 76 #endif // BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 77 78 #ifdef BMCWEB_ENABLE_DEBUG 79 connectionCount++; 80 BMCWEB_LOG_DEBUG << this << " Connection open, total " 81 << connectionCount; 82 #endif 83 } 84 85 ~Connection() 86 { 87 res.setCompleteRequestHandler(nullptr); 88 cancelDeadlineTimer(); 89 #ifdef BMCWEB_ENABLE_DEBUG 90 connectionCount--; 91 BMCWEB_LOG_DEBUG << this << " Connection closed, total " 92 << connectionCount; 93 #endif 94 } 95 96 void prepareMutualTls() 97 { 98 std::error_code error; 99 std::filesystem::path caPath(ensuressl::trustStorePath); 100 auto caAvailable = !std::filesystem::is_empty(caPath, error); 101 caAvailable = caAvailable && !error; 102 if (caAvailable && persistent_data::SessionStore::getInstance() 103 .getAuthMethodsConfig() 104 .tls) 105 { 106 adaptor.set_verify_mode(boost::asio::ssl::verify_peer); 107 std::string id = "bmcweb"; 108 int ret = SSL_set_session_id_context( 109 adaptor.native_handle(), 110 reinterpret_cast<const unsigned char*>(id.c_str()), 111 static_cast<unsigned int>(id.length())); 112 if (ret == 0) 113 { 114 BMCWEB_LOG_ERROR << this << " failed to set SSL id"; 115 } 116 } 117 118 adaptor.set_verify_callback([this]( 119 bool preverified, 120 boost::asio::ssl::verify_context& ctx) { 121 // do nothing if TLS is disabled 122 if (!persistent_data::SessionStore::getInstance() 123 .getAuthMethodsConfig() 124 .tls) 125 { 126 BMCWEB_LOG_DEBUG << this << " TLS auth_config is disabled"; 127 return true; 128 } 129 130 // We always return true to allow full auth flow 131 if (!preverified) 132 { 133 BMCWEB_LOG_DEBUG << this << " TLS preverification failed."; 134 return true; 135 } 136 137 X509_STORE_CTX* cts = ctx.native_handle(); 138 if (cts == nullptr) 139 { 140 BMCWEB_LOG_DEBUG << this << " Cannot get native TLS handle."; 141 return true; 142 } 143 144 // Get certificate 145 X509* peerCert = 146 X509_STORE_CTX_get_current_cert(ctx.native_handle()); 147 if (peerCert == nullptr) 148 { 149 BMCWEB_LOG_DEBUG << this 150 << " Cannot get current TLS certificate."; 151 return true; 152 } 153 154 // Check if certificate is OK 155 int error = X509_STORE_CTX_get_error(cts); 156 if (error != X509_V_OK) 157 { 158 BMCWEB_LOG_INFO << this << " Last TLS error is: " << error; 159 return true; 160 } 161 // Check that we have reached final certificate in chain 162 int32_t depth = X509_STORE_CTX_get_error_depth(cts); 163 if (depth != 0) 164 165 { 166 BMCWEB_LOG_DEBUG 167 << this << " Certificate verification in progress (depth " 168 << depth << "), waiting to reach final depth"; 169 return true; 170 } 171 172 BMCWEB_LOG_DEBUG << this 173 << " Certificate verification of final depth"; 174 175 // Verify KeyUsage 176 bool isKeyUsageDigitalSignature = false; 177 bool isKeyUsageKeyAgreement = false; 178 179 ASN1_BIT_STRING* usage = static_cast<ASN1_BIT_STRING*>( 180 X509_get_ext_d2i(peerCert, NID_key_usage, nullptr, nullptr)); 181 182 if (usage == nullptr) 183 { 184 BMCWEB_LOG_DEBUG << this << " TLS usage is null"; 185 return true; 186 } 187 188 for (int i = 0; i < usage->length; i++) 189 { 190 if (KU_DIGITAL_SIGNATURE & usage->data[i]) 191 { 192 isKeyUsageDigitalSignature = true; 193 } 194 if (KU_KEY_AGREEMENT & usage->data[i]) 195 { 196 isKeyUsageKeyAgreement = true; 197 } 198 } 199 ASN1_BIT_STRING_free(usage); 200 201 if (!isKeyUsageDigitalSignature || !isKeyUsageKeyAgreement) 202 { 203 BMCWEB_LOG_DEBUG << this 204 << " Certificate ExtendedKeyUsage does " 205 "not allow provided certificate to " 206 "be used for user authentication"; 207 return true; 208 } 209 210 // Determine that ExtendedKeyUsage includes Client Auth 211 212 stack_st_ASN1_OBJECT* extUsage = 213 static_cast<stack_st_ASN1_OBJECT*>(X509_get_ext_d2i( 214 peerCert, NID_ext_key_usage, nullptr, nullptr)); 215 216 if (extUsage == nullptr) 217 { 218 BMCWEB_LOG_DEBUG << this << " TLS extUsage is null"; 219 return true; 220 } 221 222 bool isExKeyUsageClientAuth = false; 223 for (int i = 0; i < sk_ASN1_OBJECT_num(extUsage); i++) 224 { 225 if (NID_client_auth == 226 OBJ_obj2nid(sk_ASN1_OBJECT_value(extUsage, i))) 227 { 228 isExKeyUsageClientAuth = true; 229 break; 230 } 231 } 232 sk_ASN1_OBJECT_free(extUsage); 233 234 // Certificate has to have proper key usages set 235 if (!isExKeyUsageClientAuth) 236 { 237 BMCWEB_LOG_DEBUG << this 238 << " Certificate ExtendedKeyUsage does " 239 "not allow provided certificate to " 240 "be used for user authentication"; 241 return true; 242 } 243 std::string sslUser; 244 // Extract username contained in CommonName 245 sslUser.resize(256, '\0'); 246 247 int status = X509_NAME_get_text_by_NID( 248 X509_get_subject_name(peerCert), NID_commonName, sslUser.data(), 249 static_cast<int>(sslUser.size())); 250 251 if (status == -1) 252 { 253 BMCWEB_LOG_DEBUG 254 << this << " TLS cannot get username to create session"; 255 return true; 256 } 257 258 size_t lastChar = sslUser.find('\0'); 259 if (lastChar == std::string::npos || lastChar == 0) 260 { 261 BMCWEB_LOG_DEBUG << this << " Invalid TLS user name"; 262 return true; 263 } 264 sslUser.resize(lastChar); 265 std::string unsupportedClientId = ""; 266 sessionIsFromTransport = true; 267 userSession = persistent_data::SessionStore::getInstance() 268 .generateUserSession( 269 sslUser, req->ipAddress.to_string(), 270 unsupportedClientId, 271 persistent_data::PersistenceType::TIMEOUT); 272 if (userSession != nullptr) 273 { 274 BMCWEB_LOG_DEBUG 275 << this 276 << " Generating TLS session: " << userSession->uniqueId; 277 } 278 return true; 279 }); 280 } 281 282 Adaptor& socket() 283 { 284 return adaptor; 285 } 286 287 void start() 288 { 289 290 startDeadline(0); 291 292 // TODO(ed) Abstract this to a more clever class with the idea of an 293 // asynchronous "start" 294 if constexpr (std::is_same_v<Adaptor, 295 boost::beast::ssl_stream< 296 boost::asio::ip::tcp::socket>>) 297 { 298 adaptor.async_handshake(boost::asio::ssl::stream_base::server, 299 [this, self(shared_from_this())]( 300 const boost::system::error_code& ec) { 301 if (ec) 302 { 303 return; 304 } 305 doReadHeaders(); 306 }); 307 } 308 else 309 { 310 doReadHeaders(); 311 } 312 } 313 314 void handle() 315 { 316 cancelDeadlineTimer(); 317 std::error_code reqEc; 318 crow::Request& thisReq = req.emplace(parser->release(), reqEc); 319 if (reqEc) 320 { 321 BMCWEB_LOG_DEBUG << "Request failed to construct" << reqEc; 322 return; 323 } 324 thisReq.session = userSession; 325 326 // Fetch the client IP address 327 readClientIp(); 328 329 // Check for HTTP version 1.1. 330 if (thisReq.version() == 11) 331 { 332 if (thisReq.getHeaderValue(boost::beast::http::field::host).empty()) 333 { 334 res.result(boost::beast::http::status::bad_request); 335 completeRequest(); 336 return; 337 } 338 } 339 340 BMCWEB_LOG_INFO << "Request: " 341 << " " << this << " HTTP/" << thisReq.version() / 10 342 << "." << thisReq.version() % 10 << ' ' 343 << thisReq.methodString() << " " << thisReq.target() 344 << " " << thisReq.ipAddress; 345 346 res.setCompleteRequestHandler(nullptr); 347 res.isAliveHelper = [this]() -> bool { return isAlive(); }; 348 349 thisReq.ioService = static_cast<decltype(thisReq.ioService)>( 350 &adaptor.get_executor().context()); 351 352 if (res.completed) 353 { 354 completeRequest(); 355 return; 356 } 357 #ifndef BMCWEB_INSECURE_DISABLE_AUTHENTICATION 358 if (!crow::authorization::isOnAllowlist(req->url, req->method()) && 359 thisReq.session == nullptr) 360 { 361 BMCWEB_LOG_WARNING << "[AuthMiddleware] authorization failed"; 362 forward_unauthorized::sendUnauthorized( 363 req->url, req->getHeaderValue("User-Agent"), 364 req->getHeaderValue("Accept"), res); 365 completeRequest(); 366 return; 367 } 368 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION 369 res.setCompleteRequestHandler([self(shared_from_this())] { 370 boost::asio::post(self->adaptor.get_executor(), 371 [self] { self->completeRequest(); }); 372 }); 373 374 if (thisReq.isUpgrade() && 375 boost::iequals( 376 thisReq.getHeaderValue(boost::beast::http::field::upgrade), 377 "websocket")) 378 { 379 handler->handleUpgrade(thisReq, res, std::move(adaptor)); 380 // delete lambda with self shared_ptr 381 // to enable connection destruction 382 res.setCompleteRequestHandler(nullptr); 383 return; 384 } 385 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(res); 386 handler->handle(thisReq, asyncResp); 387 } 388 389 bool isAlive() 390 { 391 if constexpr (std::is_same_v<Adaptor, 392 boost::beast::ssl_stream< 393 boost::asio::ip::tcp::socket>>) 394 { 395 return adaptor.next_layer().is_open(); 396 } 397 else 398 { 399 return adaptor.is_open(); 400 } 401 } 402 void close() 403 { 404 if constexpr (std::is_same_v<Adaptor, 405 boost::beast::ssl_stream< 406 boost::asio::ip::tcp::socket>>) 407 { 408 adaptor.next_layer().close(); 409 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 410 if (userSession != nullptr) 411 { 412 BMCWEB_LOG_DEBUG 413 << this 414 << " Removing TLS session: " << userSession->uniqueId; 415 persistent_data::SessionStore::getInstance().removeSession( 416 userSession); 417 } 418 #endif // BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 419 } 420 else 421 { 422 adaptor.close(); 423 } 424 } 425 426 void completeRequest() 427 { 428 if (!req) 429 { 430 return; 431 } 432 BMCWEB_LOG_INFO << "Response: " << this << ' ' << req->url << ' ' 433 << res.resultInt() << " keepalive=" << req->keepAlive(); 434 435 addSecurityHeaders(*req, res); 436 437 crow::authorization::cleanupTempSession(*req); 438 439 if (!isAlive()) 440 { 441 // BMCWEB_LOG_DEBUG << this << " delete (socket is closed) " << 442 // isReading 443 // << ' ' << isWriting; 444 // delete this; 445 446 // delete lambda with self shared_ptr 447 // to enable connection destruction 448 res.setCompleteRequestHandler(nullptr); 449 return; 450 } 451 if (res.body().empty() && !res.jsonValue.empty()) 452 { 453 if (http_helpers::requestPrefersHtml(req->getHeaderValue("Accept"))) 454 { 455 prettyPrintJson(res); 456 } 457 else 458 { 459 res.jsonMode(); 460 res.body() = res.jsonValue.dump( 461 2, ' ', true, nlohmann::json::error_handler_t::replace); 462 } 463 } 464 465 if (res.resultInt() >= 400 && res.body().empty()) 466 { 467 res.body() = std::string(res.reason()); 468 } 469 470 if (res.result() == boost::beast::http::status::no_content) 471 { 472 // Boost beast throws if content is provided on a no-content 473 // response. Ideally, this would never happen, but in the case that 474 // it does, we don't want to throw. 475 BMCWEB_LOG_CRITICAL 476 << this << " Response content provided but code was no-content"; 477 res.body().clear(); 478 } 479 480 res.addHeader(boost::beast::http::field::date, getCachedDateStr()); 481 482 res.keepAlive(req->keepAlive()); 483 484 doWrite(); 485 486 // delete lambda with self shared_ptr 487 // to enable connection destruction 488 res.setCompleteRequestHandler(nullptr); 489 } 490 491 void readClientIp() 492 { 493 boost::asio::ip::address ip; 494 boost::system::error_code ec = getClientIp(ip); 495 if (ec) 496 { 497 return; 498 } 499 req->ipAddress = ip; 500 } 501 502 boost::system::error_code getClientIp(boost::asio::ip::address& ip) 503 { 504 boost::system::error_code ec; 505 BMCWEB_LOG_DEBUG << "Fetch the client IP address"; 506 boost::asio::ip::tcp::endpoint endpoint = 507 boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec); 508 509 if (ec) 510 { 511 // If remote endpoint fails keep going. "ClientOriginIPAddress" 512 // will be empty. 513 BMCWEB_LOG_ERROR << "Failed to get the client's IP Address. ec : " 514 << ec; 515 return ec; 516 } 517 ip = endpoint.address(); 518 return ec; 519 } 520 521 private: 522 void doReadHeaders() 523 { 524 BMCWEB_LOG_DEBUG << this << " doReadHeaders"; 525 526 // Clean up any previous Connection. 527 boost::beast::http::async_read_header( 528 adaptor, buffer, *parser, 529 [this, 530 self(shared_from_this())](const boost::system::error_code& ec, 531 std::size_t bytesTransferred) { 532 BMCWEB_LOG_DEBUG << this << " async_read_header " 533 << bytesTransferred << " Bytes"; 534 bool errorWhileReading = false; 535 if (ec) 536 { 537 errorWhileReading = true; 538 if (ec == boost::asio::error::eof) 539 { 540 BMCWEB_LOG_WARNING 541 << this << " Error while reading: " << ec.message(); 542 } 543 else 544 { 545 BMCWEB_LOG_ERROR 546 << this << " Error while reading: " << ec.message(); 547 } 548 } 549 else 550 { 551 // if the adaptor isn't open anymore, and wasn't handed to a 552 // websocket, treat as an error 553 if (!isAlive() && 554 !boost::beast::websocket::is_upgrade(parser->get())) 555 { 556 errorWhileReading = true; 557 } 558 } 559 560 cancelDeadlineTimer(); 561 562 if (errorWhileReading) 563 { 564 close(); 565 BMCWEB_LOG_DEBUG << this << " from read(1)"; 566 return; 567 } 568 569 readClientIp(); 570 571 boost::asio::ip::address ip; 572 if (getClientIp(ip)) 573 { 574 BMCWEB_LOG_DEBUG << "Unable to get client IP"; 575 } 576 sessionIsFromTransport = false; 577 #ifndef BMCWEB_INSECURE_DISABLE_AUTHENTICATION 578 boost::beast::http::verb method = parser->get().method(); 579 userSession = crow::authorization::authenticate( 580 ip, res, method, parser->get().base(), userSession); 581 #endif // BMCWEB_INSECURE_DISABLE_AUTHENTICATION 582 bool loggedIn = userSession != nullptr; 583 if (loggedIn) 584 { 585 startDeadline(loggedInAttempts); 586 BMCWEB_LOG_DEBUG << "Starting slow deadline"; 587 588 req->urlParams = req->urlView.query_params(); 589 590 #ifdef BMCWEB_ENABLE_DEBUG 591 std::string paramList = ""; 592 for (const auto param : req->urlParams) 593 { 594 paramList += param->key() + " " + param->value() + " "; 595 } 596 BMCWEB_LOG_DEBUG << "QueryParams: " << paramList; 597 #endif 598 } 599 else 600 { 601 const boost::optional<uint64_t> contentLength = 602 parser->content_length(); 603 if (contentLength && 604 *contentLength > loggedOutPostBodyLimit) 605 { 606 BMCWEB_LOG_DEBUG << "Content length greater than limit " 607 << *contentLength; 608 close(); 609 return; 610 } 611 612 startDeadline(loggedOutAttempts); 613 BMCWEB_LOG_DEBUG << "Starting quick deadline"; 614 } 615 doRead(); 616 }); 617 } 618 619 void doRead() 620 { 621 BMCWEB_LOG_DEBUG << this << " doRead"; 622 623 boost::beast::http::async_read( 624 adaptor, buffer, *parser, 625 [this, 626 self(shared_from_this())](const boost::system::error_code& ec, 627 std::size_t bytesTransferred) { 628 BMCWEB_LOG_DEBUG << this << " async_read " << bytesTransferred 629 << " Bytes"; 630 631 bool errorWhileReading = false; 632 if (ec) 633 { 634 BMCWEB_LOG_ERROR 635 << this << " Error while reading: " << ec.message(); 636 errorWhileReading = true; 637 } 638 else 639 { 640 if (isAlive()) 641 { 642 cancelDeadlineTimer(); 643 if (userSession != nullptr) 644 { 645 startDeadline(loggedInAttempts); 646 } 647 else 648 { 649 startDeadline(loggedOutAttempts); 650 } 651 } 652 else 653 { 654 errorWhileReading = true; 655 } 656 } 657 if (errorWhileReading) 658 { 659 cancelDeadlineTimer(); 660 close(); 661 BMCWEB_LOG_DEBUG << this << " from read(1)"; 662 return; 663 } 664 handle(); 665 }); 666 } 667 668 void doWrite() 669 { 670 bool loggedIn = req && req->session; 671 if (loggedIn) 672 { 673 startDeadline(loggedInAttempts); 674 } 675 else 676 { 677 startDeadline(loggedOutAttempts); 678 } 679 BMCWEB_LOG_DEBUG << this << " doWrite"; 680 res.preparePayload(); 681 serializer.emplace(*res.stringResponse); 682 boost::beast::http::async_write( 683 adaptor, *serializer, 684 [this, 685 self(shared_from_this())](const boost::system::error_code& ec, 686 std::size_t bytesTransferred) { 687 BMCWEB_LOG_DEBUG << this << " async_write " << bytesTransferred 688 << " bytes"; 689 690 cancelDeadlineTimer(); 691 692 if (ec) 693 { 694 BMCWEB_LOG_DEBUG << this << " from write(2)"; 695 return; 696 } 697 if (!res.keepAlive()) 698 { 699 close(); 700 BMCWEB_LOG_DEBUG << this << " from write(1)"; 701 return; 702 } 703 704 serializer.reset(); 705 BMCWEB_LOG_DEBUG << this << " Clearing response"; 706 res.clear(); 707 parser.emplace(std::piecewise_construct, std::make_tuple()); 708 parser->body_limit(httpReqBodyLimit); // reset body limit for 709 // newly created parser 710 buffer.consume(buffer.size()); 711 712 // If the session was built from the transport, we don't need to 713 // clear it. All other sessions are generated per request. 714 if (!sessionIsFromTransport) 715 { 716 userSession = nullptr; 717 } 718 719 // Destroy the Request via the std::optional 720 req.reset(); 721 doReadHeaders(); 722 }); 723 } 724 725 void cancelDeadlineTimer() 726 { 727 if (timerCancelKey) 728 { 729 BMCWEB_LOG_DEBUG << this << " timer cancelled: " << &timerQueue 730 << ' ' << *timerCancelKey; 731 timerQueue.cancel(*timerCancelKey); 732 timerCancelKey.reset(); 733 } 734 } 735 736 void startDeadline(size_t timerIterations) 737 { 738 cancelDeadlineTimer(); 739 740 if (timerIterations) 741 { 742 timerIterations--; 743 } 744 745 timerCancelKey = 746 timerQueue.add([self(shared_from_this()), timerIterations, 747 readCount{parser->get().body().size()}] { 748 // Mark timer as not active to avoid canceling it during 749 // Connection destructor which leads to double free issue 750 self->timerCancelKey.reset(); 751 if (!self->isAlive()) 752 { 753 return; 754 } 755 756 bool loggedIn = self->req && self->req->session; 757 // allow slow uploads for logged in users 758 if (loggedIn && self->parser->get().body().size() > readCount) 759 { 760 BMCWEB_LOG_DEBUG << self.get() 761 << " restart timer - read in progress"; 762 self->startDeadline(timerIterations); 763 return; 764 } 765 766 // Threshold can be used to drop slow connections 767 // to protect against slow-rate DoS attack 768 if (timerIterations) 769 { 770 BMCWEB_LOG_DEBUG << self.get() << " restart timer"; 771 self->startDeadline(timerIterations); 772 return; 773 } 774 775 self->close(); 776 }); 777 778 if (!timerCancelKey) 779 { 780 close(); 781 return; 782 } 783 BMCWEB_LOG_DEBUG << this << " timer added: " << &timerQueue << ' ' 784 << *timerCancelKey; 785 } 786 787 private: 788 Adaptor adaptor; 789 Handler* handler; 790 // Making this a std::optional allows it to be efficiently destroyed and 791 // re-created on Connection reset 792 std::optional< 793 boost::beast::http::request_parser<boost::beast::http::string_body>> 794 parser; 795 796 boost::beast::flat_static_buffer<8192> buffer; 797 798 std::optional<boost::beast::http::response_serializer< 799 boost::beast::http::string_body>> 800 serializer; 801 802 std::optional<crow::Request> req; 803 crow::Response res; 804 805 bool sessionIsFromTransport = false; 806 std::shared_ptr<persistent_data::UserSession> userSession; 807 808 std::optional<size_t> timerCancelKey; 809 810 std::function<std::string()>& getCachedDateStr; 811 detail::TimerQueue& timerQueue; 812 813 using std::enable_shared_from_this< 814 Connection<Adaptor, Handler>>::shared_from_this; 815 }; 816 } // namespace crow 817