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