20#ifndef ORES_IAM_MESSAGING_ACCOUNT_HANDLER_HPP
21#define ORES_IAM_MESSAGING_ACCOUNT_HANDLER_HPP
25#include <boost/uuid/string_generator.hpp>
26#include <boost/uuid/uuid_io.hpp>
27#include "ores.logging/make_logger.hpp"
28#include "ores.nats/domain/message.hpp"
29#include "ores.nats/service/client.hpp"
30#include "ores.database/domain/context.hpp"
31#include "ores.security/jwt/jwt_authenticator.hpp"
32#include "ores.security/jwt/jwt_claims.hpp"
33#include "ores.service/messaging/handler_helpers.hpp"
34#include "ores.service/messaging/workflow_helpers.hpp"
35#include "ores.iam.api/messaging/account_protocol.hpp"
36#include "ores.iam.api/messaging/account_history_protocol.hpp"
37#include "ores.iam.api/messaging/login_protocol.hpp"
38#include "ores.iam.api/domain/account_version.hpp"
39#include "ores.iam.api/domain/session.hpp"
40#include "ores.iam.core/repository/account_party_repository.hpp"
41#include "ores.iam.core/repository/tenant_repository.hpp"
42#include "ores.refdata.core/repository/party_repository.hpp"
43#include "ores.database/service/tenant_context.hpp"
44#include "ores.iam.core/service/account_service.hpp"
45#include "ores.iam.core/service/authorization_service.hpp"
46#include "ores.iam.core/service/account_setup_service.hpp"
47#include "ores.variability.core/service/system_settings_service.hpp"
48#include "ores.iam.core/domain/token_settings.hpp"
49#include "ores.service/service/request_context.hpp"
50#include "ores.utility/uuid/tenant_id.hpp"
52namespace ores::iam::messaging {
56inline auto& account_handler_lg() {
57 static auto instance = ores::logging::make_logger(
58 "ores.iam.messaging.account_handler");
63 auto it = msg.
headers.find(std::string(ores::nats::headers::authorization));
66 const auto& val = it->second;
67 if (!val.starts_with(ores::nats::headers::bearer_prefix))
69 return val.substr(ores::nats::headers::bearer_prefix.size());
72inline std::vector<boost::uuids::uuid> acct_compute_visible_party_ids(
74 const boost::uuids::uuid& party_id) {
76 refdata::repository::party_repository repo(ctx);
77 auto ids = repo.read_descendants(party_id);
81 }
catch (
const std::exception& e) {
83 BOOST_LOG_SEV(account_handler_lg(), warn)
84 <<
"Failed to compute visible party IDs: " << e.what();
89inline std::optional<refdata::domain::party> acct_lookup_party(
91 const boost::uuids::uuid& party_id) {
94 auto parties = repo.read_latest(party_id);
96 return parties.front();
97 }
catch (
const std::exception& e) {
99 BOOST_LOG_SEV(account_handler_lg(), warn)
100 <<
"Failed to look up party: " << e.what();
105inline std::string acct_lookup_tenant_name(
107 const boost::uuids::uuid& tenant_id) {
109 repository::tenant_repository repo(ctx);
110 auto tenants = repo.read_latest(tenant_id);
111 if (!tenants.empty())
112 return tenants.front().name;
113 }
catch (
const std::exception& e) {
115 BOOST_LOG_SEV(account_handler_lg(), warn)
116 <<
"Failed to look up tenant name: " << e.what();
123using ores::service::messaging::reply;
124using ores::service::messaging::decode;
125using ores::service::messaging::error_reply;
126using ores::service::messaging::has_permission;
127using ores::service::messaging::log_handler_entry;
130class account_handler {
135 : nats_(nats), ctx_(
std::move(ctx)), signer_(
std::move(signer)) {
136 reload_token_settings();
139 void reload_token_settings() {
144 }
catch (
const std::exception& e) {
146 BOOST_LOG_SEV(account_handler_lg(), warn)
147 <<
"Failed to load token settings, using defaults: " << e.what();
152 [[maybe_unused]]
const auto correlation_id =
153 log_handler_entry(account_handler_lg(), msg);
155 auto ctx_expected = ores::service::service::make_request_context(
156 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
158 error_reply(nats_, msg, ctx_expected.error());
161 const auto& ctx = *ctx_expected;
162 service::account_service svc(ctx);
163 auto accounts = svc.list_accounts();
164 get_accounts_response resp;
165 resp.total_available_count =
166 static_cast<int>(accounts.size());
167 resp.accounts = std::move(accounts);
168 BOOST_LOG_SEV(account_handler_lg(), debug)
169 <<
"Completed " << msg.
subject;
170 reply(nats_, msg, resp);
171 }
catch (
const std::exception& e) {
172 BOOST_LOG_SEV(account_handler_lg(), error)
173 << msg.
subject <<
" failed: " << e.what();
174 reply(nats_, msg, get_accounts_response{});
179 using ores::service::messaging::is_workflow_command;
180 using ores::service::messaging::extract_workflow_header;
181 using ores::service::messaging::publish_step_completion;
182 using ores::service::messaging::workflow_step_id_header;
183 using ores::service::messaging::workflow_instance_id_header;
186 if (is_workflow_command(msg)) {
187 using ores::service::messaging::workflow_tenant_id_header;
188 const auto step_id = extract_workflow_header(msg, workflow_step_id_header);
189 const auto inst_id = extract_workflow_header(msg, workflow_instance_id_header);
190 const auto tenant_id = extract_workflow_header(msg, workflow_tenant_id_header);
192 auto req = decode<save_account_request>(msg);
194 publish_step_completion(nats_, step_id, inst_id,
false,
"",
195 "Failed to decode save_account_request");
200 auto wf_ctx = tenant_context::with_tenant(ctx_, tenant_id);
203 std::string username = req->principal;
204 const auto at_pos = req->principal.rfind(
'@');
205 if (at_pos != std::string::npos)
206 username = req->principal.substr(0, at_pos);
208 service::account_service acct_svc(wf_ctx);
210 std::make_shared<service::authorization_service>(wf_ctx);
211 service::account_setup_service setup_svc(acct_svc, auth_svc);
212 auto acct = setup_svc.create_account(
213 username, req->email, req->password,
214 ctx_.service_account());
216 BOOST_LOG_SEV(account_handler_lg(), debug)
217 <<
"Workflow step completed: " << msg.
subject;
218 const auto resp = save_account_response{
220 .account_id = boost::uuids::to_string(acct.id)};
221 publish_step_completion(nats_, step_id, inst_id,
true,
222 rfl::json::write(resp),
"");
223 }
catch (
const std::exception& e) {
224 BOOST_LOG_SEV(account_handler_lg(), error)
225 <<
"Workflow step failed: " << msg.
subject
226 <<
" — " << e.what();
227 publish_step_completion(nats_, step_id, inst_id,
false,
"", e.what());
232 [[maybe_unused]]
const auto correlation_id =
233 log_handler_entry(account_handler_lg(), msg);
234 auto req = decode<save_account_request>(msg);
236 BOOST_LOG_SEV(account_handler_lg(), warn)
237 <<
"Failed to decode: " << msg.
subject;
241 auto base_ctx_expected = ores::service::service::make_request_context(
242 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
243 if (!base_ctx_expected) {
244 error_reply(nats_, msg, base_ctx_expected.error());
247 const auto& base_ctx = *base_ctx_expected;
248 if (!has_permission(base_ctx,
"iam::accounts:create")) {
256 std::string username = req->principal;
258 const auto at_pos = req->principal.rfind(
'@');
259 if (at_pos != std::string::npos) {
260 username = req->principal.substr(0, at_pos);
261 const auto hostname = req->principal.substr(at_pos + 1);
262 repository::tenant_repository tenant_repo(ctx_);
263 auto tenants = tenant_repo.read_latest_by_hostname(hostname);
264 if (!tenants.empty()) {
266 op_ctx = tenant_context::with_tenant(
267 ctx_, boost::uuids::to_string(tenants.front().id));
269 throw std::runtime_error(
270 "Tenant not found for hostname: " + hostname +
271 ". Cannot create account in an unknown tenant.");
275 service::account_service acct_svc(op_ctx);
277 std::make_shared<service::authorization_service>(op_ctx);
278 service::account_setup_service setup_svc(acct_svc, auth_svc);
279 auto acct = setup_svc.create_account(
280 username, req->email, req->password, base_ctx.actor());
281 BOOST_LOG_SEV(account_handler_lg(), debug)
282 <<
"Completed " << msg.
subject;
283 reply(nats_, msg, save_account_response{
285 .account_id = boost::uuids::to_string(acct.id)});
286 }
catch (
const std::exception& e) {
287 BOOST_LOG_SEV(account_handler_lg(), error)
288 << msg.
subject <<
" failed: " << e.what();
289 reply(nats_, msg, save_account_response{
290 .success =
false, .message = e.what()});
295 [[maybe_unused]]
const auto correlation_id =
296 log_handler_entry(account_handler_lg(), msg);
297 auto req = decode<delete_account_request>(msg);
299 BOOST_LOG_SEV(account_handler_lg(), warn)
300 <<
"Failed to decode: " << msg.
subject;
304 auto ctx_expected = ores::service::service::make_request_context(
305 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
307 error_reply(nats_, msg, ctx_expected.error());
310 const auto& ctx = *ctx_expected;
311 if (!has_permission(ctx,
"iam::accounts:delete")) {
315 service::account_service svc(ctx);
316 boost::uuids::string_generator sg;
317 svc.delete_account(sg(req->account_id));
318 BOOST_LOG_SEV(account_handler_lg(), debug)
319 <<
"Completed " << msg.
subject;
321 delete_account_response{.success =
true});
322 }
catch (
const std::exception& e) {
323 BOOST_LOG_SEV(account_handler_lg(), error)
324 << msg.
subject <<
" failed: " << e.what();
325 reply(nats_, msg, delete_account_response{
326 .success =
false, .message = e.what()});
331 [[maybe_unused]]
const auto correlation_id =
332 log_handler_entry(account_handler_lg(), msg);
333 auto req = decode<lock_account_request>(msg);
335 BOOST_LOG_SEV(account_handler_lg(), warn)
336 <<
"Failed to decode: " << msg.
subject;
339 lock_account_response resp;
340 auto ctx_expected = ores::service::service::make_request_context(
341 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
343 error_reply(nats_, msg, ctx_expected.error());
346 const auto& ctx = *ctx_expected;
347 if (!has_permission(ctx,
"iam::accounts:lock")) {
351 service::account_service svc(ctx);
352 boost::uuids::string_generator sg;
353 for (
const auto&
id : req->account_ids) {
355 svc.lock_account(sg(
id));
356 resp.results.push_back({.success =
true});
357 }
catch (
const std::exception& e) {
358 BOOST_LOG_SEV(account_handler_lg(), error)
359 << msg.
subject <<
" failed: " << e.what();
360 resp.results.push_back({
361 .success =
false, .message = e.what()});
364 BOOST_LOG_SEV(account_handler_lg(), debug)
365 <<
"Completed " << msg.
subject;
366 reply(nats_, msg, resp);
370 [[maybe_unused]]
const auto correlation_id =
371 log_handler_entry(account_handler_lg(), msg);
372 auto req = decode<unlock_account_request>(msg);
374 BOOST_LOG_SEV(account_handler_lg(), warn)
375 <<
"Failed to decode: " << msg.
subject;
378 unlock_account_response resp;
379 auto ctx_expected = ores::service::service::make_request_context(
380 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
382 error_reply(nats_, msg, ctx_expected.error());
385 const auto& ctx = *ctx_expected;
386 if (!has_permission(ctx,
"iam::accounts:unlock")) {
390 service::account_service svc(ctx);
391 boost::uuids::string_generator sg;
392 for (
const auto&
id : req->account_ids) {
394 svc.unlock_account(sg(
id));
395 resp.results.push_back({.success =
true});
396 }
catch (
const std::exception& e) {
397 BOOST_LOG_SEV(account_handler_lg(), error)
398 << msg.
subject <<
" failed: " << e.what();
399 resp.results.push_back({
400 .success =
false, .message = e.what()});
403 BOOST_LOG_SEV(account_handler_lg(), debug)
404 <<
"Completed " << msg.
subject;
405 reply(nats_, msg, resp);
409 [[maybe_unused]]
const auto correlation_id =
410 log_handler_entry(account_handler_lg(), msg);
412 auto ctx_expected = ores::service::service::make_request_context(
413 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
415 error_reply(nats_, msg, ctx_expected.error());
418 const auto& ctx = *ctx_expected;
419 service::account_service svc(ctx);
420 auto infos = svc.list_login_info();
421 BOOST_LOG_SEV(account_handler_lg(), debug)
422 <<
"Completed " << msg.
subject;
424 list_login_info_response{
425 .login_infos = std::move(infos)});
426 }
catch (
const std::exception& e) {
427 BOOST_LOG_SEV(account_handler_lg(), error)
428 << msg.
subject <<
" failed: " << e.what();
429 reply(nats_, msg, list_login_info_response{});
434 [[maybe_unused]]
const auto correlation_id =
435 log_handler_entry(account_handler_lg(), msg);
436 auto req = decode<reset_password_request>(msg);
438 BOOST_LOG_SEV(account_handler_lg(), warn)
439 <<
"Failed to decode: " << msg.
subject;
442 reset_password_response resp;
443 auto ctx_expected = ores::service::service::make_request_context(
444 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
446 error_reply(nats_, msg, ctx_expected.error());
449 const auto& ctx = *ctx_expected;
450 if (!has_permission(ctx,
"iam::accounts:reset_password")) {
454 service::account_service svc(ctx);
455 boost::uuids::string_generator sg;
456 for (
const auto& id_str : req->account_ids) {
458 auto account_id = sg(id_str);
459 auto err = svc.change_password(
460 account_id, req->new_password);
462 resp.results.push_back({.success =
true});
464 resp.results.push_back({
465 .success =
false, .message = err});
467 }
catch (
const std::exception& e) {
468 BOOST_LOG_SEV(account_handler_lg(), error)
469 << msg.
subject <<
" failed: " << e.what();
470 resp.results.push_back({
471 .success =
false, .message = e.what()});
475 BOOST_LOG_SEV(account_handler_lg(), debug)
476 <<
"Completed " << msg.
subject;
477 reply(nats_, msg, resp);
481 [[maybe_unused]]
const auto correlation_id =
482 log_handler_entry(account_handler_lg(), msg);
483 auto req = decode<change_password_request>(msg);
485 BOOST_LOG_SEV(account_handler_lg(), warn)
486 <<
"Failed to decode: " << msg.
subject;
490 auto token = acct_extract_bearer_token(msg);
492 reply(nats_, msg, change_password_response{
494 .message =
"Missing authorization token"});
497 auto claims_result = signer_.validate(token);
498 if (!claims_result) {
499 reply(nats_, msg, change_password_response{
501 .message =
"Invalid or expired token"});
504 boost::uuids::string_generator sg;
505 auto account_id = sg(claims_result->subject);
506 auto ctx_expected = ores::service::service::make_request_context(
507 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
509 error_reply(nats_, msg, ctx_expected.error());
512 const auto& ctx = *ctx_expected;
513 service::account_service svc(ctx);
514 auto err = svc.change_password(account_id,
517 BOOST_LOG_SEV(account_handler_lg(), debug)
518 <<
"Completed " << msg.
subject;
519 reply(nats_, msg, change_password_response{
522 reply(nats_, msg, change_password_response{
523 .success =
false, .message = err});
525 }
catch (
const std::exception& e) {
526 BOOST_LOG_SEV(account_handler_lg(), error)
527 << msg.
subject <<
" failed: " << e.what();
528 reply(nats_, msg, change_password_response{
529 .success =
false, .message = e.what()});
534 [[maybe_unused]]
const auto correlation_id =
535 log_handler_entry(account_handler_lg(), msg);
536 auto req = decode<update_account_request>(msg);
538 BOOST_LOG_SEV(account_handler_lg(), warn)
539 <<
"Failed to decode: " << msg.
subject;
543 auto ctx_expected = ores::service::service::make_request_context(
544 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
546 error_reply(nats_, msg, ctx_expected.error());
549 const auto& ctx = *ctx_expected;
550 if (!has_permission(ctx,
"iam::accounts:update")) {
554 service::account_service svc(ctx);
555 boost::uuids::string_generator sg;
556 svc.update_account(sg(req->account_id), req->email,
557 ctx.
actor(), req->change_reason_code,
558 req->change_commentary);
559 BOOST_LOG_SEV(account_handler_lg(), debug)
560 <<
"Completed " << msg.
subject;
562 update_account_response{.success =
true});
563 }
catch (
const std::exception& e) {
564 BOOST_LOG_SEV(account_handler_lg(), error)
565 << msg.
subject <<
" failed: " << e.what();
566 reply(nats_, msg, update_account_response{
567 .success =
false, .message = e.what()});
572 [[maybe_unused]]
const auto correlation_id =
573 log_handler_entry(account_handler_lg(), msg);
574 auto req = decode<update_my_email_request>(msg);
576 BOOST_LOG_SEV(account_handler_lg(), warn)
577 <<
"Failed to decode: " << msg.
subject;
581 auto token = acct_extract_bearer_token(msg);
583 reply(nats_, msg, update_my_email_response{
585 .message =
"Missing authorization token"});
588 auto claims_result = signer_.validate(token);
589 if (!claims_result) {
590 reply(nats_, msg, update_my_email_response{
592 .message =
"Invalid or expired token"});
595 boost::uuids::string_generator sg;
596 auto account_id = sg(claims_result->subject);
597 auto ctx_expected = ores::service::service::make_request_context(
598 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
600 error_reply(nats_, msg, ctx_expected.error());
603 const auto& ctx = *ctx_expected;
604 service::account_service svc(ctx);
605 auto err = svc.update_my_email(account_id, req->email);
607 BOOST_LOG_SEV(account_handler_lg(), debug)
608 <<
"Completed " << msg.
subject;
609 reply(nats_, msg, update_my_email_response{
612 reply(nats_, msg, update_my_email_response{
613 .success =
false, .message = err});
615 }
catch (
const std::exception& e) {
616 BOOST_LOG_SEV(account_handler_lg(), error)
617 << msg.
subject <<
" failed: " << e.what();
618 reply(nats_, msg, update_my_email_response{
619 .success =
false, .message = e.what()});
624 [[maybe_unused]]
const auto correlation_id =
625 log_handler_entry(account_handler_lg(), msg);
626 auto req = decode<select_party_request>(msg);
628 BOOST_LOG_SEV(account_handler_lg(), warn)
629 <<
"Failed to decode: " << msg.
subject;
633 auto token = acct_extract_bearer_token(msg);
635 reply(nats_, msg, select_party_response{
637 .message =
"Missing authorization token"});
641 auto claims_result = signer_.validate(token);
642 if (!claims_result) {
643 BOOST_LOG_SEV(account_handler_lg(), warn)
644 <<
"select_party: JWT validation failed";
645 reply(nats_, msg, select_party_response{
647 .message =
"Invalid or expired token"});
650 if (claims_result->audience !=
"select_party_only") {
651 BOOST_LOG_SEV(account_handler_lg(), warn)
652 <<
"select_party: unexpected token audience: "
653 << claims_result->audience;
654 reply(nats_, msg, select_party_response{
656 .message =
"Invalid or expired token"});
660 boost::uuids::string_generator sg;
661 auto account_id = sg(claims_result->subject);
662 BOOST_LOG_SEV(account_handler_lg(), debug)
663 <<
"select_party: account=" << claims_result->subject
664 <<
" requested_party=" << req->party_id;
670 const auto tenant_id_str = claims_result->tenant_id.value_or(
"");
671 if (tenant_id_str.empty()) {
672 BOOST_LOG_SEV(account_handler_lg(), warn)
673 <<
"select_party: token has no tenant_id";
674 reply(nats_, msg, select_party_response{
676 .message =
"Invalid token: missing tenant"});
682 BOOST_LOG_SEV(account_handler_lg(), warn)
683 <<
"select_party: invalid tenant_id in token: "
685 reply(nats_, msg, select_party_response{
687 .message =
"Invalid token: malformed tenant"});
691 *tid_result, claims_result->username.value_or(
""));
693 boost::uuids::uuid requested_party_id;
695 requested_party_id = sg(req->party_id);
696 }
catch (
const std::exception&) {
697 BOOST_LOG_SEV(account_handler_lg(), warn)
698 <<
"select_party: invalid party_id: " << req->party_id;
699 reply(nats_, msg, select_party_response{
701 .message =
"Invalid party_id format"});
704 repository::account_party_repository ap_repo(ctx);
705 auto parties = ap_repo.read_latest_by_account(account_id);
707 BOOST_LOG_SEV(account_handler_lg(), debug)
708 <<
"select_party: account has " << parties.size()
709 <<
" party membership(s)";
711 bool is_member =
false;
712 for (
const auto& ap : parties) {
713 if (ap.party_id == requested_party_id) {
720 BOOST_LOG_SEV(account_handler_lg(), warn)
721 <<
"select_party: party " << req->party_id
722 <<
" not in account's party list (account has "
723 << parties.size() <<
" parties)";
724 reply(nats_, msg, select_party_response{
726 .message =
"User is not a member of requested party"});
730 auto visible = acct_compute_visible_party_ids(
731 ctx, requested_party_id);
734 new_claims.
subject = claims_result->subject;
735 new_claims.
issued_at = std::chrono::system_clock::now();
737 new_claims.
issued_at + std::chrono::seconds(
738 token_settings_.access_lifetime_s);
739 new_claims.
username = claims_result->username;
740 new_claims.
email = claims_result->email;
743 boost::uuids::to_string(requested_party_id);
746 new_claims.
session_id = claims_result->session_id;
748 for (
const auto& vid : visible)
749 new_claims.visible_party_ids.push_back(
750 boost::uuids::to_string(vid));
753 signer_.create_token(new_claims).value_or(
"");
758 auto tid = sg(tenant_id_str);
759 t_name = acct_lookup_tenant_name(ctx, tid);
760 }
catch (
const std::exception& e) {
761 BOOST_LOG_SEV(account_handler_lg(), warn)
762 <<
"Failed to look up tenant name during "
763 "party selection: " << e.what();
765 bool party_setup_required =
false;
766 if (
const auto p = acct_lookup_party(ctx, requested_party_id)) {
767 p_name = p->full_name;
768 party_setup_required = p->status ==
"Inactive";
771 BOOST_LOG_SEV(account_handler_lg(), debug)
772 <<
"Completed " << msg.
subject;
773 reply(nats_, msg, select_party_response{
775 .message =
"Party selected",
777 .username = claims_result->username.value_or(
""),
778 .tenant_name = t_name,
779 .party_name = p_name,
780 .party_setup_required = party_setup_required
782 }
catch (
const std::exception& e) {
783 BOOST_LOG_SEV(account_handler_lg(), error)
784 << msg.
subject <<
" failed: " << e.what();
785 reply(nats_, msg, select_party_response{
786 .success =
false, .message = e.what()});
791 [[maybe_unused]]
const auto correlation_id =
792 log_handler_entry(account_handler_lg(), msg);
793 auto req = decode<get_account_history_request>(msg);
795 BOOST_LOG_SEV(account_handler_lg(), warn)
796 <<
"Failed to decode: " << msg.
subject;
800 auto ctx_expected = ores::service::service::make_request_context(
801 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
803 error_reply(nats_, msg, ctx_expected.error());
806 const auto& ctx = *ctx_expected;
807 service::account_service svc(ctx);
808 auto accounts = svc.get_account_history(req->username);
809 account_version_history avh;
810 int vnum =
static_cast<int>(accounts.size());
811 for (
const auto& a : accounts) {
817 avh.versions.push_back(std::move(av));
819 BOOST_LOG_SEV(account_handler_lg(), debug)
820 <<
"Completed " << msg.
subject;
821 reply(nats_, msg, get_account_history_response{
823 .history = std::move(avh)});
824 }
catch (
const std::exception& e) {
825 BOOST_LOG_SEV(account_handler_lg(), error)
826 << msg.
subject <<
" failed: " << e.what();
827 reply(nats_, msg, get_account_history_response{
828 .success =
false, .message = e.what()});
836 domain::token_settings token_settings_;
Implements logging infrastructure for ORE Studio.
Definition boost_severity.hpp:28
@ forbidden
The caller is authenticated but lacks the required permission.
Context for the operations on a postgres database.
Definition context.hpp:47
const std::string & actor() const
Gets the current actor (end-user) for this context.
Definition context.hpp:119
context with_tenant(utility::uuid::tenant_id tenant_id, std::string actor) const
Creates a new context with a different tenant ID (no party).
Definition context.hpp:162
Manages tenant context for multi-tenant database operations.
Definition tenant_context.hpp:37
Represents a specific version of an account with metadata.
Definition account_version.hpp:32
std::string modified_by
Username of the person who recorded this version in the system.
Definition account_version.hpp:46
int version_number
Version number (1-based, higher is newer).
Definition account_version.hpp:41
std::chrono::system_clock::time_point recorded_at
Timestamp when this version was recorded in the system.
Definition account_version.hpp:51
account data
The account data at this version.
Definition account_version.hpp:36
static token_settings load(variability::service::system_settings_service &svc)
Loads token settings from the system settings service.
Definition token_settings.cpp:24
A received NATS message.
Definition message.hpp:40
std::string subject
The subject the message was published to.
Definition message.hpp:44
std::unordered_map< std::string, std::string > headers
NATS message headers (NATS 2.2+).
Definition message.hpp:66
NATS client: connection, pub/sub, request/reply, and JetStream.
Definition client.hpp:73
Reads and writes parties to data storage.
Definition party_repository.hpp:37
JWT authentication primitive.
Definition jwt_authenticator.hpp:45
Represents the claims extracted from a JWT token.
Definition jwt_claims.hpp:33
std::string subject
Subject claim - typically the account ID.
Definition jwt_claims.hpp:37
std::optional< std::string > session_id
Optional session ID for tracking sessions.
Definition jwt_claims.hpp:81
std::chrono::system_clock::time_point issued_at
Time when the token was issued.
Definition jwt_claims.hpp:57
std::optional< std::string > tenant_id
Optional tenant ID (UUID string).
Definition jwt_claims.hpp:97
std::optional< std::string > party_id
Optional party ID (UUID string, nil UUID if no party selected).
Definition jwt_claims.hpp:104
std::optional< std::chrono::system_clock::time_point > session_start_time
Optional session start time for efficient database updates.
Definition jwt_claims.hpp:90
std::optional< std::string > username
Optional username claim.
Definition jwt_claims.hpp:67
std::chrono::system_clock::time_point expires_at
Time when the token expires.
Definition jwt_claims.hpp:52
std::optional< std::string > email
Optional email claim.
Definition jwt_claims.hpp:72
static std::expected< tenant_id, std::string > from_string(std::string_view str)
Creates a tenant_id from a string representation.
Definition tenant_id.cpp:57
Service for managing typed system settings.
Definition system_settings_service.hpp:41