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 318 crow::Request& thisReq = req.emplace(parser->release()); 319 thisReq.session = userSession; 320 321 // Fetch the client IP address 322 readClientIp(); 323 324 // Check for HTTP version 1.1. 325 if (thisReq.version() == 11) 326 { 327 if (thisReq.getHeaderValue(boost::beast::http::field::host).empty()) 328 { 329 res.result(boost::beast::http::status::bad_request); 330 completeRequest(); 331 return; 332 } 333 } 334 335 BMCWEB_LOG_INFO << "Request: " 336 << " " << this << " HTTP/" << thisReq.version() / 10 337 << "." << thisReq.version() % 10 << ' ' 338 << thisReq.methodString() << " " << thisReq.target() 339 << " " << thisReq.ipAddress; 340 341 boost::urls::error_code ec; 342 req->urlView = boost::urls::parse_relative_ref( 343 boost::urls::string_view(req->target().data(), 344 req->target().size()), 345 ec); 346 if (ec) 347 { 348 return; 349 } 350 req->url = std::string_view(req->urlView.encoded_path().data(), 351 req->urlView.encoded_path().size()); 352 353 res.setCompleteRequestHandler(nullptr); 354 res.isAliveHelper = [this]() -> bool { return isAlive(); }; 355 356 thisReq.ioService = static_cast<decltype(thisReq.ioService)>( 357 &adaptor.get_executor().context()); 358 359 if (res.completed) 360 { 361 completeRequest(); 362 return; 363 } 364 res.setCompleteRequestHandler([self(shared_from_this())] { 365 boost::asio::post(self->adaptor.get_executor(), 366 [self] { self->completeRequest(); }); 367 }); 368 369 if (thisReq.isUpgrade() && 370 boost::iequals( 371 thisReq.getHeaderValue(boost::beast::http::field::upgrade), 372 "websocket")) 373 { 374 handler->handleUpgrade(thisReq, res, std::move(adaptor)); 375 // delete lambda with self shared_ptr 376 // to enable connection destruction 377 res.setCompleteRequestHandler(nullptr); 378 return; 379 } 380 auto asyncResp = std::make_shared<bmcweb::AsyncResp>(res); 381 handler->handle(thisReq, asyncResp); 382 } 383 384 bool isAlive() 385 { 386 if constexpr (std::is_same_v<Adaptor, 387 boost::beast::ssl_stream< 388 boost::asio::ip::tcp::socket>>) 389 { 390 return adaptor.next_layer().is_open(); 391 } 392 else 393 { 394 return adaptor.is_open(); 395 } 396 } 397 void close() 398 { 399 if constexpr (std::is_same_v<Adaptor, 400 boost::beast::ssl_stream< 401 boost::asio::ip::tcp::socket>>) 402 { 403 adaptor.next_layer().close(); 404 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 405 if (userSession != nullptr) 406 { 407 BMCWEB_LOG_DEBUG 408 << this 409 << " Removing TLS session: " << userSession->uniqueId; 410 persistent_data::SessionStore::getInstance().removeSession( 411 userSession); 412 } 413 #endif // BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION 414 } 415 else 416 { 417 adaptor.close(); 418 } 419 } 420 421 void completeRequest() 422 { 423 BMCWEB_LOG_INFO << "Response: " << this << ' ' << req->url << ' ' 424 << res.resultInt() << " keepalive=" << req->keepAlive(); 425 426 addSecurityHeaders(*req, res); 427 428 crow::authorization::cleanupTempSession(*req); 429 430 if (!isAlive()) 431 { 432 // BMCWEB_LOG_DEBUG << this << " delete (socket is closed) " << 433 // isReading 434 // << ' ' << isWriting; 435 // delete this; 436 437 // delete lambda with self shared_ptr 438 // to enable connection destruction 439 res.setCompleteRequestHandler(nullptr); 440 return; 441 } 442 if (res.body().empty() && !res.jsonValue.empty()) 443 { 444 if (http_helpers::requestPrefersHtml(req->getHeaderValue("Accept"))) 445 { 446 prettyPrintJson(res); 447 } 448 else 449 { 450 res.jsonMode(); 451 res.body() = res.jsonValue.dump( 452 2, ' ', true, nlohmann::json::error_handler_t::replace); 453 } 454 } 455 456 if (res.resultInt() >= 400 && res.body().empty()) 457 { 458 res.body() = std::string(res.reason()); 459 } 460 461 if (res.result() == boost::beast::http::status::no_content) 462 { 463 // Boost beast throws if content is provided on a no-content 464 // response. Ideally, this would never happen, but in the case that 465 // it does, we don't want to throw. 466 BMCWEB_LOG_CRITICAL 467 << this << " Response content provided but code was no-content"; 468 res.body().clear(); 469 } 470 471 res.addHeader(boost::beast::http::field::date, getCachedDateStr()); 472 473 res.keepAlive(req->keepAlive()); 474 475 doWrite(); 476 477 // delete lambda with self shared_ptr 478 // to enable connection destruction 479 res.setCompleteRequestHandler(nullptr); 480 } 481 482 void readClientIp() 483 { 484 boost::asio::ip::address ip; 485 boost::system::error_code ec = getClientIp(ip); 486 if (ec) 487 { 488 return; 489 } 490 req->ipAddress = ip; 491 } 492 493 boost::system::error_code getClientIp(boost::asio::ip::address& ip) 494 { 495 boost::system::error_code ec; 496 BMCWEB_LOG_DEBUG << "Fetch the client IP address"; 497 boost::asio::ip::tcp::endpoint endpoint = 498 boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec); 499 500 if (ec) 501 { 502 // If remote endpoint fails keep going. "ClientOriginIPAddress" 503 // will be empty. 504 BMCWEB_LOG_ERROR << "Failed to get the client's IP Address. ec : " 505 << ec; 506 return ec; 507 } 508 ip = endpoint.address(); 509 return ec; 510 } 511 512 private: 513 void doReadHeaders() 514 { 515 BMCWEB_LOG_DEBUG << this << " doReadHeaders"; 516 517 // Clean up any previous Connection. 518 boost::beast::http::async_read_header( 519 adaptor, buffer, *parser, 520 [this, 521 self(shared_from_this())](const boost::system::error_code& ec, 522 std::size_t bytesTransferred) { 523 BMCWEB_LOG_ERROR << this << " async_read_header " 524 << bytesTransferred << " Bytes"; 525 bool errorWhileReading = false; 526 if (ec) 527 { 528 errorWhileReading = true; 529 BMCWEB_LOG_ERROR 530 << this << " Error while reading: " << ec.message(); 531 } 532 else 533 { 534 // if the adaptor isn't open anymore, and wasn't handed to a 535 // websocket, treat as an error 536 if (!isAlive() && 537 !boost::beast::websocket::is_upgrade(parser->get())) 538 { 539 errorWhileReading = true; 540 } 541 } 542 543 cancelDeadlineTimer(); 544 545 if (errorWhileReading) 546 { 547 close(); 548 BMCWEB_LOG_DEBUG << this << " from read(1)"; 549 return; 550 } 551 552 boost::beast::http::verb method = parser->get().method(); 553 readClientIp(); 554 boost::urls::error_code uriEc; 555 boost::urls::string_view uriStringView( 556 parser->get().target().data(), 557 parser->get().target().size()); 558 BMCWEB_LOG_DEBUG << "Parsing URI: " << uriStringView; 559 req->urlView = 560 boost::urls::parse_relative_ref(uriStringView, uriEc); 561 if (uriEc) 562 { 563 BMCWEB_LOG_ERROR << "Failed to parse URI " 564 << uriEc.message(); 565 return; 566 } 567 req->url = std::string_view(req->urlView.encoded_path().data(), 568 req->urlView.encoded_path().size()); 569 570 boost::asio::ip::address ip; 571 if (getClientIp(ip)) 572 { 573 BMCWEB_LOG_DEBUG << "Unable to get client IP"; 574 } 575 sessionIsFromTransport = false; 576 userSession = crow::authorization::authenticate( 577 req->url, ip, res, method, parser->get().base(), 578 userSession); 579 bool loggedIn = userSession != nullptr; 580 if (loggedIn) 581 { 582 startDeadline(loggedInAttempts); 583 BMCWEB_LOG_DEBUG << "Starting slow deadline"; 584 585 req->urlParams = req->urlView.query_params(); 586 587 #ifdef BMCWEB_ENABLE_DEBUG 588 std::string paramList = ""; 589 for (const auto param : req->urlParams) 590 { 591 paramList += param->key() + " " + param->value() + " "; 592 } 593 BMCWEB_LOG_DEBUG << "QueryParams: " << paramList; 594 #endif 595 } 596 else 597 { 598 const boost::optional<uint64_t> contentLength = 599 parser->content_length(); 600 if (contentLength && 601 *contentLength > loggedOutPostBodyLimit) 602 { 603 BMCWEB_LOG_DEBUG << "Content length greater than limit " 604 << *contentLength; 605 close(); 606 return; 607 } 608 609 startDeadline(loggedOutAttempts); 610 BMCWEB_LOG_DEBUG << "Starting quick deadline"; 611 } 612 doRead(); 613 }); 614 } 615 616 void doRead() 617 { 618 BMCWEB_LOG_DEBUG << this << " doRead"; 619 620 boost::beast::http::async_read( 621 adaptor, buffer, *parser, 622 [this, 623 self(shared_from_this())](const boost::system::error_code& ec, 624 std::size_t bytesTransferred) { 625 BMCWEB_LOG_DEBUG << this << " async_read " << bytesTransferred 626 << " Bytes"; 627 628 bool errorWhileReading = false; 629 if (ec) 630 { 631 BMCWEB_LOG_ERROR 632 << this << " Error while reading: " << ec.message(); 633 errorWhileReading = true; 634 } 635 else 636 { 637 if (isAlive()) 638 { 639 cancelDeadlineTimer(); 640 if (userSession != nullptr) 641 { 642 startDeadline(loggedInAttempts); 643 } 644 else 645 { 646 startDeadline(loggedOutAttempts); 647 } 648 } 649 else 650 { 651 errorWhileReading = true; 652 } 653 } 654 if (errorWhileReading) 655 { 656 cancelDeadlineTimer(); 657 close(); 658 BMCWEB_LOG_DEBUG << this << " from read(1)"; 659 return; 660 } 661 handle(); 662 }); 663 } 664 665 void doWrite() 666 { 667 bool loggedIn = req && req->session; 668 if (loggedIn) 669 { 670 startDeadline(loggedInAttempts); 671 } 672 else 673 { 674 startDeadline(loggedOutAttempts); 675 } 676 BMCWEB_LOG_DEBUG << this << " doWrite"; 677 res.preparePayload(); 678 serializer.emplace(*res.stringResponse); 679 boost::beast::http::async_write( 680 adaptor, *serializer, 681 [this, 682 self(shared_from_this())](const boost::system::error_code& ec, 683 std::size_t bytesTransferred) { 684 BMCWEB_LOG_DEBUG << this << " async_write " << bytesTransferred 685 << " bytes"; 686 687 cancelDeadlineTimer(); 688 689 if (ec) 690 { 691 BMCWEB_LOG_DEBUG << this << " from write(2)"; 692 return; 693 } 694 if (!res.keepAlive()) 695 { 696 close(); 697 BMCWEB_LOG_DEBUG << this << " from write(1)"; 698 return; 699 } 700 701 serializer.reset(); 702 BMCWEB_LOG_DEBUG << this << " Clearing response"; 703 res.clear(); 704 parser.emplace(std::piecewise_construct, std::make_tuple()); 705 parser->body_limit(httpReqBodyLimit); // reset body limit for 706 // newly created parser 707 buffer.consume(buffer.size()); 708 709 // If the session was built from the transport, we don't need to 710 // clear it. All other sessions are generated per request. 711 if (!sessionIsFromTransport) 712 { 713 userSession = nullptr; 714 } 715 716 req.emplace(parser->release()); 717 doReadHeaders(); 718 }); 719 } 720 721 void cancelDeadlineTimer() 722 { 723 if (timerCancelKey) 724 { 725 BMCWEB_LOG_DEBUG << this << " timer cancelled: " << &timerQueue 726 << ' ' << *timerCancelKey; 727 timerQueue.cancel(*timerCancelKey); 728 timerCancelKey.reset(); 729 } 730 } 731 732 void startDeadline(size_t timerIterations) 733 { 734 cancelDeadlineTimer(); 735 736 if (timerIterations) 737 { 738 timerIterations--; 739 } 740 741 timerCancelKey = 742 timerQueue.add([self(shared_from_this()), timerIterations, 743 readCount{parser->get().body().size()}] { 744 // Mark timer as not active to avoid canceling it during 745 // Connection destructor which leads to double free issue 746 self->timerCancelKey.reset(); 747 if (!self->isAlive()) 748 { 749 return; 750 } 751 752 bool loggedIn = self->req && self->req->session; 753 // allow slow uploads for logged in users 754 if (loggedIn && self->parser->get().body().size() > readCount) 755 { 756 BMCWEB_LOG_DEBUG << self.get() 757 << " restart timer - read in progress"; 758 self->startDeadline(timerIterations); 759 return; 760 } 761 762 // Threshold can be used to drop slow connections 763 // to protect against slow-rate DoS attack 764 if (timerIterations) 765 { 766 BMCWEB_LOG_DEBUG << self.get() << " restart timer"; 767 self->startDeadline(timerIterations); 768 return; 769 } 770 771 self->close(); 772 }); 773 774 if (!timerCancelKey) 775 { 776 close(); 777 return; 778 } 779 BMCWEB_LOG_DEBUG << this << " timer added: " << &timerQueue << ' ' 780 << *timerCancelKey; 781 } 782 783 private: 784 Adaptor adaptor; 785 Handler* handler; 786 // Making this a std::optional allows it to be efficiently destroyed and 787 // re-created on Connection reset 788 std::optional< 789 boost::beast::http::request_parser<boost::beast::http::string_body>> 790 parser; 791 792 boost::beast::flat_static_buffer<8192> buffer; 793 794 std::optional<boost::beast::http::response_serializer< 795 boost::beast::http::string_body>> 796 serializer; 797 798 std::optional<crow::Request> req; 799 crow::Response res; 800 801 bool sessionIsFromTransport = false; 802 std::shared_ptr<persistent_data::UserSession> userSession; 803 804 std::optional<size_t> timerCancelKey; 805 806 std::function<std::string()>& getCachedDateStr; 807 detail::TimerQueue& timerQueue; 808 809 using std::enable_shared_from_this< 810 Connection<Adaptor, Handler>>::shared_from_this; 811 }; 812 } // namespace crow 813