xref: /openbmc/bmcweb/http/http_connection.hpp (revision dfa3fdc3)
1 #pragma once
2 #include "bmcweb_config.h"
3 
4 #include "authentication.hpp"
5 #include "http_response.hpp"
6 #include "http_utility.hpp"
7 #include "logging.hpp"
8 #include "utility.hpp"
9 
10 #include <boost/algorithm/string/predicate.hpp>
11 #include <boost/asio/io_context.hpp>
12 #include <boost/asio/ip/tcp.hpp>
13 #include <boost/asio/ssl/stream.hpp>
14 #include <boost/asio/steady_timer.hpp>
15 #include <boost/beast/core/flat_static_buffer.hpp>
16 #include <boost/beast/http/parser.hpp>
17 #include <boost/beast/http/read.hpp>
18 #include <boost/beast/http/serializer.hpp>
19 #include <boost/beast/http/write.hpp>
20 #include <boost/beast/ssl/ssl_stream.hpp>
21 #include <boost/beast/websocket.hpp>
22 #include <boost/url/url_view.hpp>
23 #include <json_html_serializer.hpp>
24 #include <security_headers.hpp>
25 #include <ssl_key_handler.hpp>
26 
27 #include <atomic>
28 #include <chrono>
29 #include <vector>
30 
31 namespace crow
32 {
33 
34 inline void prettyPrintJson(crow::Response& res)
35 {
36     json_html_util::dumpHtml(res.body(), res.jsonValue);
37 
38     res.addHeader(boost::beast::http::field::content_type,
39                   "text/html;charset=UTF-8");
40 }
41 
42 static int connectionCount = 0;
43 
44 // request body limit size set by the bmcwebHttpReqBodyLimitMb option
45 constexpr uint64_t httpReqBodyLimit =
46     1024UL * 1024UL * bmcwebHttpReqBodyLimitMb;
47 
48 constexpr uint64_t loggedOutPostBodyLimit = 4096;
49 
50 constexpr uint32_t httpHeaderLimit = 8192;
51 
52 template <typename Adaptor, typename Handler>
53 class Connection :
54     public std::enable_shared_from_this<Connection<Adaptor, Handler>>
55 {
56   public:
57     Connection(Handler* handlerIn, boost::asio::steady_timer&& timerIn,
58                std::function<std::string()>& getCachedDateStrF,
59                Adaptor adaptorIn) :
60         adaptor(std::move(adaptorIn)),
61         handler(handlerIn), timer(std::move(timerIn)),
62         getCachedDateStr(getCachedDateStrF)
63     {
64         parser.emplace(std::piecewise_construct, std::make_tuple());
65         parser->body_limit(httpReqBodyLimit);
66         parser->header_limit(httpHeaderLimit);
67 
68 #ifdef BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
69         prepareMutualTls();
70 #endif // BMCWEB_ENABLE_MUTUAL_TLS_AUTHENTICATION
71 
72         connectionCount++;
73 
74         BMCWEB_LOG_DEBUG << this << " Connection open, total "
75                          << connectionCount;
76     }
77 
78     ~Connection()
79     {
80         res.setCompleteRequestHandler(nullptr);
81         cancelDeadlineTimer();
82 
83         connectionCount--;
84         BMCWEB_LOG_DEBUG << this << " Connection closed, total "
85                          << connectionCount;
86     }
87 
88     Connection(const Connection&) = delete;
89     Connection(Connection&&) = delete;
90     Connection& operator=(const Connection&) = delete;
91     Connection& operator=(Connection&&) = delete;
92 
93     void prepareMutualTls()
94     {
95         std::error_code error;
96         std::filesystem::path caPath(ensuressl::trustStorePath);
97         auto caAvailable = !std::filesystem::is_empty(caPath, error);
98         caAvailable = caAvailable && !error;
99         if (caAvailable && persistent_data::SessionStore::getInstance()
100                                .getAuthMethodsConfig()
101                                .tls)
102         {
103             adaptor.set_verify_mode(boost::asio::ssl::verify_peer);
104             std::string id = "bmcweb";
105 
106             const char* cStr = id.c_str();
107             // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
108             const auto* idC = reinterpret_cast<const unsigned char*>(cStr);
109             int ret = SSL_set_session_id_context(
110                 adaptor.native_handle(), idC,
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(
119             [this](bool preverified, 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 ctxError = X509_STORE_CTX_get_error(cts);
155             if (ctxError != X509_V_OK)
156             {
157                 BMCWEB_LOG_INFO << this << " Last TLS error is: " << ctxError;
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                 // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
190                 unsigned char usageChar = usage->data[i];
191                 if (KU_DIGITAL_SIGNATURE & usageChar)
192                 {
193                     isKeyUsageDigitalSignature = true;
194                 }
195                 if (KU_KEY_AGREEMENT & usageChar)
196                 {
197                     isKeyUsageKeyAgreement = true;
198                 }
199             }
200             ASN1_BIT_STRING_free(usage);
201 
202             if (!isKeyUsageDigitalSignature || !isKeyUsageKeyAgreement)
203             {
204                 BMCWEB_LOG_DEBUG << this
205                                  << " Certificate ExtendedKeyUsage does "
206                                     "not allow provided certificate to "
207                                     "be used for user authentication";
208                 return true;
209             }
210 
211             // Determine that ExtendedKeyUsage includes Client Auth
212 
213             stack_st_ASN1_OBJECT* extUsage =
214                 static_cast<stack_st_ASN1_OBJECT*>(X509_get_ext_d2i(
215                     peerCert, NID_ext_key_usage, nullptr, nullptr));
216 
217             if (extUsage == nullptr)
218             {
219                 BMCWEB_LOG_DEBUG << this << " TLS extUsage is null";
220                 return true;
221             }
222 
223             bool isExKeyUsageClientAuth = false;
224             for (int i = 0; i < sk_ASN1_OBJECT_num(extUsage); i++)
225             {
226                 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
227                 int nid = OBJ_obj2nid(sk_ASN1_OBJECT_value(extUsage, i));
228                 if (NID_client_auth == nid)
229                 {
230                     isExKeyUsageClientAuth = true;
231                     break;
232                 }
233             }
234             sk_ASN1_OBJECT_free(extUsage);
235 
236             // Certificate has to have proper key usages set
237             if (!isExKeyUsageClientAuth)
238             {
239                 BMCWEB_LOG_DEBUG << this
240                                  << " Certificate ExtendedKeyUsage does "
241                                     "not allow provided certificate to "
242                                     "be used for user authentication";
243                 return true;
244             }
245             std::string sslUser;
246             // Extract username contained in CommonName
247             sslUser.resize(256, '\0');
248 
249             int status = X509_NAME_get_text_by_NID(
250                 X509_get_subject_name(peerCert), NID_commonName, sslUser.data(),
251                 static_cast<int>(sslUser.size()));
252 
253             if (status == -1)
254             {
255                 BMCWEB_LOG_DEBUG
256                     << this << " TLS cannot get username to create session";
257                 return true;
258             }
259 
260             size_t lastChar = sslUser.find('\0');
261             if (lastChar == std::string::npos || lastChar == 0)
262             {
263                 BMCWEB_LOG_DEBUG << this << " Invalid TLS user name";
264                 return true;
265             }
266             sslUser.resize(lastChar);
267             sessionIsFromTransport = true;
268             userSession = persistent_data::SessionStore::getInstance()
269                               .generateUserSession(
270                                   sslUser, req->ipAddress, std::nullopt,
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         if (connectionCount >= 100)
290         {
291             BMCWEB_LOG_CRITICAL << this << "Max connection count exceeded.";
292             return;
293         }
294 
295         startDeadline();
296 
297         // TODO(ed) Abstract this to a more clever class with the idea of an
298         // asynchronous "start"
299         if constexpr (std::is_same_v<Adaptor,
300                                      boost::beast::ssl_stream<
301                                          boost::asio::ip::tcp::socket>>)
302         {
303             adaptor.async_handshake(boost::asio::ssl::stream_base::server,
304                                     [this, self(shared_from_this())](
305                                         const boost::system::error_code& ec) {
306                 if (ec)
307                 {
308                     return;
309                 }
310                 doReadHeaders();
311             });
312         }
313         else
314         {
315             doReadHeaders();
316         }
317     }
318 
319     void handle()
320     {
321         std::error_code reqEc;
322         crow::Request& thisReq = req.emplace(parser->release(), reqEc);
323         if (reqEc)
324         {
325             BMCWEB_LOG_DEBUG << "Request failed to construct" << reqEc;
326             return;
327         }
328         thisReq.session = userSession;
329 
330         // Fetch the client IP address
331         readClientIp();
332 
333         // Check for HTTP version 1.1.
334         if (thisReq.version() == 11)
335         {
336             if (thisReq.getHeaderValue(boost::beast::http::field::host).empty())
337             {
338                 res.result(boost::beast::http::status::bad_request);
339                 completeRequest(res);
340                 return;
341             }
342         }
343 
344         BMCWEB_LOG_INFO << "Request: "
345                         << " " << this << " HTTP/" << thisReq.version() / 10
346                         << "." << thisReq.version() % 10 << ' '
347                         << thisReq.methodString() << " " << thisReq.target()
348                         << " " << thisReq.ipAddress.to_string();
349 
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(res);
358             return;
359         }
360 #ifndef BMCWEB_INSECURE_DISABLE_AUTHX
361         if (!crow::authentication::isOnAllowlist(req->url, req->method()) &&
362             thisReq.session == nullptr)
363         {
364             BMCWEB_LOG_WARNING << "Authentication failed";
365             forward_unauthorized::sendUnauthorized(
366                 req->url, req->getHeaderValue("X-Requested-With"),
367                 req->getHeaderValue("Accept"), res);
368             completeRequest(res);
369             return;
370         }
371 #endif // BMCWEB_INSECURE_DISABLE_AUTHX
372         auto asyncResp = std::make_shared<bmcweb::AsyncResp>();
373         BMCWEB_LOG_DEBUG << "Setting completion handler";
374         asyncResp->res.setCompleteRequestHandler(
375             [self(shared_from_this())](crow::Response& thisRes) {
376             self->completeRequest(thisRes);
377         });
378 
379         if (thisReq.isUpgrade() &&
380             boost::iequals(
381                 thisReq.getHeaderValue(boost::beast::http::field::upgrade),
382                 "websocket"))
383         {
384             handler->handleUpgrade(thisReq, res, std::move(adaptor));
385             // delete lambda with self shared_ptr
386             // to enable connection destruction
387             asyncResp->res.setCompleteRequestHandler(nullptr);
388             return;
389         }
390         std::string_view expected =
391             req->getHeaderValue(boost::beast::http::field::if_none_match);
392         if (!expected.empty())
393         {
394             res.setExpectedHash(expected);
395         }
396         handler->handle(thisReq, asyncResp);
397     }
398 
399     bool isAlive()
400     {
401         if constexpr (std::is_same_v<Adaptor,
402                                      boost::beast::ssl_stream<
403                                          boost::asio::ip::tcp::socket>>)
404         {
405             return adaptor.next_layer().is_open();
406         }
407         else
408         {
409             return adaptor.is_open();
410         }
411     }
412     void close()
413     {
414         if constexpr (std::is_same_v<Adaptor,
415                                      boost::beast::ssl_stream<
416                                          boost::asio::ip::tcp::socket>>)
417         {
418             adaptor.next_layer().close();
419             if (sessionIsFromTransport && userSession != nullptr)
420             {
421                 BMCWEB_LOG_DEBUG
422                     << this
423                     << " Removing TLS session: " << userSession->uniqueId;
424                 persistent_data::SessionStore::getInstance().removeSession(
425                     userSession);
426             }
427         }
428         else
429         {
430             adaptor.close();
431         }
432     }
433 
434     void completeRequest(crow::Response& thisRes)
435     {
436         if (!req)
437         {
438             return;
439         }
440         res = std::move(thisRes);
441         BMCWEB_LOG_INFO << "Response: " << this << ' ' << req->url << ' '
442                         << res.resultInt() << " keepalive=" << req->keepAlive();
443 
444         addSecurityHeaders(*req, res);
445 
446         crow::authentication::cleanupTempSession(*req);
447 
448         if (!isAlive())
449         {
450             // BMCWEB_LOG_DEBUG << this << " delete (socket is closed) " <<
451             // isReading
452             // << ' ' << isWriting;
453             // delete this;
454 
455             // delete lambda with self shared_ptr
456             // to enable connection destruction
457             res.setCompleteRequestHandler(nullptr);
458             return;
459         }
460 
461         res.setHashAndHandleNotModified();
462 
463         if (res.body().empty() && !res.jsonValue.empty())
464         {
465             using http_helpers::ContentType;
466             std::array<ContentType, 2> allowed{ContentType::JSON,
467                                                ContentType::HTML};
468             ContentType prefered =
469                 getPreferedContentType(req->getHeaderValue("Accept"), allowed);
470 
471             if (prefered == ContentType::HTML)
472             {
473                 prettyPrintJson(res);
474             }
475             else
476             {
477                 // Technically prefered could also be NoMatch here, but we'd
478                 // like to default to something rather than return 400 for
479                 // backward compatibility.
480                 res.addHeader(boost::beast::http::field::content_type,
481                               "application/json");
482                 res.body() = res.jsonValue.dump(
483                     2, ' ', true, nlohmann::json::error_handler_t::replace);
484             }
485         }
486 
487         if (res.resultInt() >= 400 && res.body().empty())
488         {
489             res.body() = std::string(res.reason());
490         }
491 
492         if (res.result() == boost::beast::http::status::no_content)
493         {
494             // Boost beast throws if content is provided on a no-content
495             // response.  Ideally, this would never happen, but in the case that
496             // it does, we don't want to throw.
497             BMCWEB_LOG_CRITICAL
498                 << this << " Response content provided but code was no-content";
499             res.body().clear();
500         }
501 
502         res.addHeader(boost::beast::http::field::date, getCachedDateStr());
503 
504         res.keepAlive(req->keepAlive());
505 
506         doWrite(res);
507 
508         // delete lambda with self shared_ptr
509         // to enable connection destruction
510         res.setCompleteRequestHandler(nullptr);
511     }
512 
513     void readClientIp()
514     {
515         boost::asio::ip::address ip;
516         boost::system::error_code ec = getClientIp(ip);
517         if (ec)
518         {
519             return;
520         }
521         req->ipAddress = ip;
522     }
523 
524     boost::system::error_code getClientIp(boost::asio::ip::address& ip)
525     {
526         boost::system::error_code ec;
527         BMCWEB_LOG_DEBUG << "Fetch the client IP address";
528         boost::asio::ip::tcp::endpoint endpoint =
529             boost::beast::get_lowest_layer(adaptor).remote_endpoint(ec);
530 
531         if (ec)
532         {
533             // If remote endpoint fails keep going. "ClientOriginIPAddress"
534             // will be empty.
535             BMCWEB_LOG_ERROR << "Failed to get the client's IP Address. ec : "
536                              << ec;
537             return ec;
538         }
539         ip = endpoint.address();
540         return ec;
541     }
542 
543   private:
544     void doReadHeaders()
545     {
546         BMCWEB_LOG_DEBUG << this << " doReadHeaders";
547 
548         // Clean up any previous Connection.
549         boost::beast::http::async_read_header(
550             adaptor, buffer, *parser,
551             [this,
552              self(shared_from_this())](const boost::system::error_code& ec,
553                                        std::size_t bytesTransferred) {
554             BMCWEB_LOG_DEBUG << this << " async_read_header "
555                              << bytesTransferred << " Bytes";
556             bool errorWhileReading = false;
557             if (ec)
558             {
559                 errorWhileReading = true;
560                 if (ec == boost::asio::error::eof)
561                 {
562                     BMCWEB_LOG_WARNING
563                         << this << " Error while reading: " << ec.message();
564                 }
565                 else
566                 {
567                     BMCWEB_LOG_ERROR
568                         << this << " Error while reading: " << ec.message();
569                 }
570             }
571             else
572             {
573                 // if the adaptor isn't open anymore, and wasn't handed to a
574                 // websocket, treat as an error
575                 if (!isAlive() &&
576                     !boost::beast::websocket::is_upgrade(parser->get()))
577                 {
578                     errorWhileReading = true;
579                 }
580             }
581 
582             cancelDeadlineTimer();
583 
584             if (errorWhileReading)
585             {
586                 close();
587                 BMCWEB_LOG_DEBUG << this << " from read(1)";
588                 return;
589             }
590 
591             readClientIp();
592 
593             boost::asio::ip::address ip;
594             if (getClientIp(ip))
595             {
596                 BMCWEB_LOG_DEBUG << "Unable to get client IP";
597             }
598             sessionIsFromTransport = false;
599 #ifndef BMCWEB_INSECURE_DISABLE_AUTHX
600             boost::beast::http::verb method = parser->get().method();
601             userSession = crow::authentication::authenticate(
602                 ip, res, method, parser->get().base(), userSession);
603 
604             bool loggedIn = userSession != nullptr;
605             if (!loggedIn)
606             {
607                 const boost::optional<uint64_t> contentLength =
608                     parser->content_length();
609                 if (contentLength && *contentLength > loggedOutPostBodyLimit)
610                 {
611                     BMCWEB_LOG_DEBUG << "Content length greater than limit "
612                                      << *contentLength;
613                     close();
614                     return;
615                 }
616 
617                 BMCWEB_LOG_DEBUG << "Starting quick deadline";
618             }
619 #endif // BMCWEB_INSECURE_DISABLE_AUTHX
620 
621             doRead();
622             });
623     }
624 
625     void doRead()
626     {
627         BMCWEB_LOG_DEBUG << this << " doRead";
628         startDeadline();
629         boost::beast::http::async_read(adaptor, buffer, *parser,
630                                        [this, self(shared_from_this())](
631                                            const boost::system::error_code& ec,
632                                            std::size_t bytesTransferred) {
633             BMCWEB_LOG_DEBUG << this << " async_read " << bytesTransferred
634                              << " Bytes";
635             cancelDeadlineTimer();
636             if (ec)
637             {
638                 BMCWEB_LOG_ERROR << this
639                                  << " Error while reading: " << ec.message();
640                 close();
641                 BMCWEB_LOG_DEBUG << this << " from read(1)";
642                 return;
643             }
644             handle();
645         });
646     }
647 
648     void doWrite(crow::Response& thisRes)
649     {
650         BMCWEB_LOG_DEBUG << this << " doWrite";
651         thisRes.preparePayload();
652         serializer.emplace(*thisRes.stringResponse);
653         startDeadline();
654         boost::beast::http::async_write(adaptor, *serializer,
655                                         [this, self(shared_from_this())](
656                                             const boost::system::error_code& ec,
657                                             std::size_t bytesTransferred) {
658             BMCWEB_LOG_DEBUG << this << " async_write " << bytesTransferred
659                              << " bytes";
660 
661             cancelDeadlineTimer();
662 
663             if (ec)
664             {
665                 BMCWEB_LOG_DEBUG << this << " from write(2)";
666                 return;
667             }
668             if (!res.keepAlive())
669             {
670                 close();
671                 BMCWEB_LOG_DEBUG << this << " from write(1)";
672                 return;
673             }
674 
675             serializer.reset();
676             BMCWEB_LOG_DEBUG << this << " Clearing response";
677             res.clear();
678             parser.emplace(std::piecewise_construct, std::make_tuple());
679             parser->body_limit(httpReqBodyLimit); // reset body limit for
680                                                   // newly created parser
681             buffer.consume(buffer.size());
682 
683             // If the session was built from the transport, we don't need to
684             // clear it.  All other sessions are generated per request.
685             if (!sessionIsFromTransport)
686             {
687                 userSession = nullptr;
688             }
689 
690             // Destroy the Request via the std::optional
691             req.reset();
692             doReadHeaders();
693         });
694     }
695 
696     void cancelDeadlineTimer()
697     {
698         timer.cancel();
699     }
700 
701     void startDeadline()
702     {
703         cancelDeadlineTimer();
704 
705         std::chrono::seconds timeout(15);
706         // allow slow uploads for logged in users
707         bool loggedIn = userSession != nullptr;
708         if (loggedIn)
709         {
710             timeout = std::chrono::seconds(60);
711             return;
712         }
713 
714         std::weak_ptr<Connection<Adaptor, Handler>> weakSelf = weak_from_this();
715         timer.expires_after(timeout);
716         timer.async_wait([weakSelf](const boost::system::error_code ec) {
717             // Note, we are ignoring other types of errors here;  If the timer
718             // failed for any reason, we should still close the connection
719 
720             std::shared_ptr<Connection<Adaptor, Handler>> self =
721                 weakSelf.lock();
722             if (!self)
723             {
724                 BMCWEB_LOG_CRITICAL << self << " Failed to capture connection";
725                 return;
726             }
727             if (ec == boost::asio::error::operation_aborted)
728             {
729                 // Canceled wait means the path succeeeded.
730                 return;
731             }
732             if (ec)
733             {
734                 BMCWEB_LOG_CRITICAL << self << " timer failed " << ec;
735             }
736 
737             BMCWEB_LOG_WARNING << self << "Connection timed out, closing";
738 
739             self->close();
740         });
741 
742         BMCWEB_LOG_DEBUG << this << " timer started";
743     }
744 
745     Adaptor adaptor;
746     Handler* handler;
747     // Making this a std::optional allows it to be efficiently destroyed and
748     // re-created on Connection reset
749     std::optional<
750         boost::beast::http::request_parser<boost::beast::http::string_body>>
751         parser;
752 
753     boost::beast::flat_static_buffer<8192> buffer;
754 
755     std::optional<boost::beast::http::response_serializer<
756         boost::beast::http::string_body>>
757         serializer;
758 
759     std::optional<crow::Request> req;
760     crow::Response res;
761 
762     bool sessionIsFromTransport = false;
763     std::shared_ptr<persistent_data::UserSession> userSession;
764 
765     boost::asio::steady_timer timer;
766 
767     std::function<std::string()>& getCachedDateStr;
768 
769     using std::enable_shared_from_this<
770         Connection<Adaptor, Handler>>::shared_from_this;
771 
772     using std::enable_shared_from_this<
773         Connection<Adaptor, Handler>>::weak_from_this;
774 };
775 } // namespace crow
776