ORE Studio 0.0.4
Loading...
Searching...
No Matches
account_handler.hpp
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 *
3 * Copyright (C) 2026 Marco Craveiro <marco.craveiro@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU General Public License as published by the Free Software
7 * Foundation; either version 3 of the License, or (at your option) any later
8 * version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13 * details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 51
17 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 *
19 */
20#ifndef ORES_IAM_MESSAGING_ACCOUNT_HANDLER_HPP
21#define ORES_IAM_MESSAGING_ACCOUNT_HANDLER_HPP
22
23#include <chrono>
24#include <stdexcept>
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/service/request_context.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
50namespace ores::iam::messaging {
51
52namespace {
53
54inline auto& account_handler_lg() {
55 static auto instance = ores::logging::make_logger(
56 "ores.iam.messaging.account_handler");
57 return instance;
58}
59
60inline std::string acct_extract_bearer_token(const ores::nats::message& msg) {
61 auto it = msg.headers.find("Authorization");
62 if (it == msg.headers.end())
63 return {};
64 const auto& val = it->second;
65 constexpr std::string_view prefix = "Bearer ";
66 if (!val.starts_with(prefix))
67 return {};
68 return val.substr(prefix.size());
69}
70
71inline std::vector<boost::uuids::uuid> acct_compute_visible_party_ids(
72 const ores::database::context& ctx,
73 const boost::uuids::uuid& party_id) {
74 try {
75 refdata::repository::party_repository repo(ctx);
76 auto ids = repo.read_descendants(party_id);
77 if (ids.empty())
78 return {party_id};
79 return ids;
80 } catch (const std::exception& e) {
81 using namespace ores::logging;
82 BOOST_LOG_SEV(account_handler_lg(), warn)
83 << "Failed to compute visible party IDs: " << e.what();
84 return {party_id};
85 }
86}
87
88inline std::optional<refdata::domain::party> acct_lookup_party(
89 const ores::database::context& ctx,
90 const boost::uuids::uuid& party_id) {
91 try {
93 auto parties = repo.read_latest(party_id);
94 if (!parties.empty())
95 return parties.front();
96 } catch (const std::exception& e) {
97 using namespace ores::logging;
98 BOOST_LOG_SEV(account_handler_lg(), warn)
99 << "Failed to look up party: " << e.what();
100 }
101 return std::nullopt;
102}
103
104inline std::string acct_lookup_tenant_name(
105 const ores::database::context& ctx,
106 const boost::uuids::uuid& tenant_id) {
107 try {
108 repository::tenant_repository repo(ctx);
109 auto tenants = repo.read_latest(tenant_id);
110 if (!tenants.empty())
111 return tenants.front().name;
112 } catch (const std::exception& e) {
113 using namespace ores::logging;
114 BOOST_LOG_SEV(account_handler_lg(), warn)
115 << "Failed to look up tenant name: " << e.what();
116 }
117 return {};
118}
119
120} // namespace
121
122using ores::service::messaging::reply;
123using ores::service::messaging::decode;
124using ores::service::messaging::error_reply;
125using ores::service::messaging::has_permission;
126
127class account_handler {
128public:
129 account_handler(ores::nats::service::client& nats,
132 : nats_(nats), ctx_(std::move(ctx)), signer_(std::move(signer)) {
133 reload_token_settings();
134 }
135
136 void reload_token_settings() {
137 try {
139 svc.refresh();
140 token_settings_ = domain::token_settings::load(svc);
141 } catch (const std::exception& e) {
142 using namespace ores::logging;
143 BOOST_LOG_SEV(account_handler_lg(), warn)
144 << "Failed to load token settings, using defaults: " << e.what();
145 }
146 }
147
148 void list(ores::nats::message msg) {
149 using namespace ores::logging;
150 BOOST_LOG_SEV(account_handler_lg(), debug)
151 << "Handling " << msg.subject;
152 try {
153 auto ctx_expected = ores::service::service::make_request_context(
154 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
155 if (!ctx_expected) {
156 error_reply(nats_, msg, ctx_expected.error());
157 return;
158 }
159 const auto& ctx = *ctx_expected;
160 service::account_service svc(ctx);
161 auto accounts = svc.list_accounts();
162 get_accounts_response resp;
163 resp.total_available_count =
164 static_cast<int>(accounts.size());
165 resp.accounts = std::move(accounts);
166 BOOST_LOG_SEV(account_handler_lg(), debug)
167 << "Completed " << msg.subject;
168 reply(nats_, msg, resp);
169 } catch (const std::exception& e) {
170 BOOST_LOG_SEV(account_handler_lg(), error)
171 << msg.subject << " failed: " << e.what();
172 reply(nats_, msg, get_accounts_response{});
173 }
174 }
175
176 void save(ores::nats::message msg) {
177 using namespace ores::logging;
178 BOOST_LOG_SEV(account_handler_lg(), debug)
179 << "Handling " << msg.subject;
180 auto req = decode<save_account_request>(msg);
181 if (!req) {
182 BOOST_LOG_SEV(account_handler_lg(), warn)
183 << "Failed to decode: " << msg.subject;
184 return;
185 }
186 try {
187 auto base_ctx_expected = ores::service::service::make_request_context(
188 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
189 if (!base_ctx_expected) {
190 error_reply(nats_, msg, base_ctx_expected.error());
191 return;
192 }
193 const auto& base_ctx = *base_ctx_expected;
194 if (!has_permission(base_ctx, "iam::accounts:create")) {
195 error_reply(nats_, msg, ores::service::error_code::forbidden);
196 return;
197 }
198
199 // Parse principal: if username@hostname, route to that tenant's
200 // context so accounts can be created in any tenant by a system
201 // admin whose JWT is in the system tenant.
202 std::string username = req->principal;
203 ores::database::context op_ctx = base_ctx;
204 const auto at_pos = req->principal.rfind('@');
205 if (at_pos != std::string::npos) {
206 username = req->principal.substr(0, at_pos);
207 const auto hostname = req->principal.substr(at_pos + 1);
208 repository::tenant_repository tenant_repo(ctx_);
209 auto tenants = tenant_repo.read_latest_by_hostname(hostname);
210 if (!tenants.empty()) {
212 op_ctx = tenant_context::with_tenant(
213 ctx_, boost::uuids::to_string(tenants.front().id));
214 } else {
215 throw std::runtime_error(
216 "Tenant not found for hostname: " + hostname +
217 ". Cannot create account in an unknown tenant.");
218 }
219 }
220
221 service::account_service acct_svc(op_ctx);
222 auto auth_svc =
223 std::make_shared<service::authorization_service>(op_ctx);
224 service::account_setup_service setup_svc(acct_svc, auth_svc);
225 auto acct = setup_svc.create_account(
226 username, req->email, req->password, base_ctx.actor());
227 BOOST_LOG_SEV(account_handler_lg(), debug)
228 << "Completed " << msg.subject;
229 reply(nats_, msg, save_account_response{
230 .success = true,
231 .account_id = boost::uuids::to_string(acct.id)});
232 } catch (const std::exception& e) {
233 BOOST_LOG_SEV(account_handler_lg(), error)
234 << msg.subject << " failed: " << e.what();
235 reply(nats_, msg, save_account_response{
236 .success = false, .message = e.what()});
237 }
238 }
239
240 void del(ores::nats::message msg) {
241 using namespace ores::logging;
242 BOOST_LOG_SEV(account_handler_lg(), debug)
243 << "Handling " << msg.subject;
244 auto req = decode<delete_account_request>(msg);
245 if (!req) {
246 BOOST_LOG_SEV(account_handler_lg(), warn)
247 << "Failed to decode: " << msg.subject;
248 return;
249 }
250 try {
251 auto ctx_expected = ores::service::service::make_request_context(
252 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
253 if (!ctx_expected) {
254 error_reply(nats_, msg, ctx_expected.error());
255 return;
256 }
257 const auto& ctx = *ctx_expected;
258 if (!has_permission(ctx, "iam::accounts:delete")) {
259 error_reply(nats_, msg, ores::service::error_code::forbidden);
260 return;
261 }
262 service::account_service svc(ctx);
263 boost::uuids::string_generator sg;
264 svc.delete_account(sg(req->account_id));
265 BOOST_LOG_SEV(account_handler_lg(), debug)
266 << "Completed " << msg.subject;
267 reply(nats_, msg,
268 delete_account_response{.success = true});
269 } catch (const std::exception& e) {
270 BOOST_LOG_SEV(account_handler_lg(), error)
271 << msg.subject << " failed: " << e.what();
272 reply(nats_, msg, delete_account_response{
273 .success = false, .message = e.what()});
274 }
275 }
276
277 void lock(ores::nats::message msg) {
278 using namespace ores::logging;
279 BOOST_LOG_SEV(account_handler_lg(), debug)
280 << "Handling " << msg.subject;
281 auto req = decode<lock_account_request>(msg);
282 if (!req) {
283 BOOST_LOG_SEV(account_handler_lg(), warn)
284 << "Failed to decode: " << msg.subject;
285 return;
286 }
287 lock_account_response resp;
288 auto ctx_expected = ores::service::service::make_request_context(
289 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
290 if (!ctx_expected) {
291 error_reply(nats_, msg, ctx_expected.error());
292 return;
293 }
294 const auto& ctx = *ctx_expected;
295 if (!has_permission(ctx, "iam::accounts:lock")) {
296 error_reply(nats_, msg, ores::service::error_code::forbidden);
297 return;
298 }
299 service::account_service svc(ctx);
300 boost::uuids::string_generator sg;
301 for (const auto& id : req->account_ids) {
302 try {
303 svc.lock_account(sg(id));
304 resp.results.push_back({.success = true});
305 } catch (const std::exception& e) {
306 BOOST_LOG_SEV(account_handler_lg(), error)
307 << msg.subject << " failed: " << e.what();
308 resp.results.push_back({
309 .success = false, .message = e.what()});
310 }
311 }
312 BOOST_LOG_SEV(account_handler_lg(), debug)
313 << "Completed " << msg.subject;
314 reply(nats_, msg, resp);
315 }
316
317 void unlock(ores::nats::message msg) {
318 using namespace ores::logging;
319 BOOST_LOG_SEV(account_handler_lg(), debug)
320 << "Handling " << msg.subject;
321 auto req = decode<unlock_account_request>(msg);
322 if (!req) {
323 BOOST_LOG_SEV(account_handler_lg(), warn)
324 << "Failed to decode: " << msg.subject;
325 return;
326 }
327 unlock_account_response resp;
328 auto ctx_expected = ores::service::service::make_request_context(
329 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
330 if (!ctx_expected) {
331 error_reply(nats_, msg, ctx_expected.error());
332 return;
333 }
334 const auto& ctx = *ctx_expected;
335 if (!has_permission(ctx, "iam::accounts:unlock")) {
336 error_reply(nats_, msg, ores::service::error_code::forbidden);
337 return;
338 }
339 service::account_service svc(ctx);
340 boost::uuids::string_generator sg;
341 for (const auto& id : req->account_ids) {
342 try {
343 svc.unlock_account(sg(id));
344 resp.results.push_back({.success = true});
345 } catch (const std::exception& e) {
346 BOOST_LOG_SEV(account_handler_lg(), error)
347 << msg.subject << " failed: " << e.what();
348 resp.results.push_back({
349 .success = false, .message = e.what()});
350 }
351 }
352 BOOST_LOG_SEV(account_handler_lg(), debug)
353 << "Completed " << msg.subject;
354 reply(nats_, msg, resp);
355 }
356
357 void login_info(ores::nats::message msg) {
358 using namespace ores::logging;
359 BOOST_LOG_SEV(account_handler_lg(), debug)
360 << "Handling " << msg.subject;
361 try {
362 auto ctx_expected = ores::service::service::make_request_context(
363 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
364 if (!ctx_expected) {
365 error_reply(nats_, msg, ctx_expected.error());
366 return;
367 }
368 const auto& ctx = *ctx_expected;
369 service::account_service svc(ctx);
370 auto infos = svc.list_login_info();
371 BOOST_LOG_SEV(account_handler_lg(), debug)
372 << "Completed " << msg.subject;
373 reply(nats_, msg,
374 list_login_info_response{
375 .login_infos = std::move(infos)});
376 } catch (const std::exception& e) {
377 BOOST_LOG_SEV(account_handler_lg(), error)
378 << msg.subject << " failed: " << e.what();
379 reply(nats_, msg, list_login_info_response{});
380 }
381 }
382
383 void reset_password(ores::nats::message msg) {
384 using namespace ores::logging;
385 BOOST_LOG_SEV(account_handler_lg(), debug)
386 << "Handling " << msg.subject;
387 auto req = decode<reset_password_request>(msg);
388 if (!req) {
389 BOOST_LOG_SEV(account_handler_lg(), warn)
390 << "Failed to decode: " << msg.subject;
391 return;
392 }
393 reset_password_response resp;
394 auto ctx_expected = ores::service::service::make_request_context(
395 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
396 if (!ctx_expected) {
397 error_reply(nats_, msg, ctx_expected.error());
398 return;
399 }
400 const auto& ctx = *ctx_expected;
401 if (!has_permission(ctx, "iam::accounts:reset_password")) {
402 error_reply(nats_, msg, ores::service::error_code::forbidden);
403 return;
404 }
405 service::account_service svc(ctx);
406 boost::uuids::string_generator sg;
407 for (const auto& id_str : req->account_ids) {
408 try {
409 auto account_id = sg(id_str);
410 auto err = svc.change_password(
411 account_id, req->new_password);
412 if (err.empty()) {
413 resp.results.push_back({.success = true});
414 } else {
415 resp.results.push_back({
416 .success = false, .message = err});
417 }
418 } catch (const std::exception& e) {
419 BOOST_LOG_SEV(account_handler_lg(), error)
420 << msg.subject << " failed: " << e.what();
421 resp.results.push_back({
422 .success = false, .message = e.what()});
423 }
424 }
425 resp.success = true;
426 BOOST_LOG_SEV(account_handler_lg(), debug)
427 << "Completed " << msg.subject;
428 reply(nats_, msg, resp);
429 }
430
431 void change_password(ores::nats::message msg) {
432 using namespace ores::logging;
433 BOOST_LOG_SEV(account_handler_lg(), debug)
434 << "Handling " << msg.subject;
435 auto req = decode<change_password_request>(msg);
436 if (!req) {
437 BOOST_LOG_SEV(account_handler_lg(), warn)
438 << "Failed to decode: " << msg.subject;
439 return;
440 }
441 try {
442 auto token = acct_extract_bearer_token(msg);
443 if (token.empty()) {
444 reply(nats_, msg, change_password_response{
445 .success = false,
446 .message = "Missing authorization token"});
447 return;
448 }
449 auto claims_result = signer_.validate(token);
450 if (!claims_result) {
451 reply(nats_, msg, change_password_response{
452 .success = false,
453 .message = "Invalid or expired token"});
454 return;
455 }
456 boost::uuids::string_generator sg;
457 auto account_id = sg(claims_result->subject);
458 auto ctx_expected = ores::service::service::make_request_context(
459 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
460 if (!ctx_expected) {
461 error_reply(nats_, msg, ctx_expected.error());
462 return;
463 }
464 const auto& ctx = *ctx_expected;
465 service::account_service svc(ctx);
466 auto err = svc.change_password(account_id,
467 req->new_password);
468 if (err.empty()) {
469 BOOST_LOG_SEV(account_handler_lg(), debug)
470 << "Completed " << msg.subject;
471 reply(nats_, msg, change_password_response{
472 .success = true});
473 } else {
474 reply(nats_, msg, change_password_response{
475 .success = false, .message = err});
476 }
477 } catch (const std::exception& e) {
478 BOOST_LOG_SEV(account_handler_lg(), error)
479 << msg.subject << " failed: " << e.what();
480 reply(nats_, msg, change_password_response{
481 .success = false, .message = e.what()});
482 }
483 }
484
485 void update(ores::nats::message msg) {
486 using namespace ores::logging;
487 BOOST_LOG_SEV(account_handler_lg(), debug)
488 << "Handling " << msg.subject;
489 auto req = decode<update_account_request>(msg);
490 if (!req) {
491 BOOST_LOG_SEV(account_handler_lg(), warn)
492 << "Failed to decode: " << msg.subject;
493 return;
494 }
495 try {
496 auto ctx_expected = ores::service::service::make_request_context(
497 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
498 if (!ctx_expected) {
499 error_reply(nats_, msg, ctx_expected.error());
500 return;
501 }
502 const auto& ctx = *ctx_expected;
503 if (!has_permission(ctx, "iam::accounts:update")) {
504 error_reply(nats_, msg, ores::service::error_code::forbidden);
505 return;
506 }
507 service::account_service svc(ctx);
508 boost::uuids::string_generator sg;
509 svc.update_account(sg(req->account_id), req->email,
510 ctx.actor(), req->change_reason_code,
511 req->change_commentary);
512 BOOST_LOG_SEV(account_handler_lg(), debug)
513 << "Completed " << msg.subject;
514 reply(nats_, msg,
515 update_account_response{.success = true});
516 } catch (const std::exception& e) {
517 BOOST_LOG_SEV(account_handler_lg(), error)
518 << msg.subject << " failed: " << e.what();
519 reply(nats_, msg, update_account_response{
520 .success = false, .message = e.what()});
521 }
522 }
523
524 void update_email(ores::nats::message msg) {
525 using namespace ores::logging;
526 BOOST_LOG_SEV(account_handler_lg(), debug)
527 << "Handling " << msg.subject;
528 auto req = decode<update_my_email_request>(msg);
529 if (!req) {
530 BOOST_LOG_SEV(account_handler_lg(), warn)
531 << "Failed to decode: " << msg.subject;
532 return;
533 }
534 try {
535 auto token = acct_extract_bearer_token(msg);
536 if (token.empty()) {
537 reply(nats_, msg, update_my_email_response{
538 .success = false,
539 .message = "Missing authorization token"});
540 return;
541 }
542 auto claims_result = signer_.validate(token);
543 if (!claims_result) {
544 reply(nats_, msg, update_my_email_response{
545 .success = false,
546 .message = "Invalid or expired token"});
547 return;
548 }
549 boost::uuids::string_generator sg;
550 auto account_id = sg(claims_result->subject);
551 auto ctx_expected = ores::service::service::make_request_context(
552 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
553 if (!ctx_expected) {
554 error_reply(nats_, msg, ctx_expected.error());
555 return;
556 }
557 const auto& ctx = *ctx_expected;
558 service::account_service svc(ctx);
559 auto err = svc.update_my_email(account_id, req->email);
560 if (err.empty()) {
561 BOOST_LOG_SEV(account_handler_lg(), debug)
562 << "Completed " << msg.subject;
563 reply(nats_, msg, update_my_email_response{
564 .success = true});
565 } else {
566 reply(nats_, msg, update_my_email_response{
567 .success = false, .message = err});
568 }
569 } catch (const std::exception& e) {
570 BOOST_LOG_SEV(account_handler_lg(), error)
571 << msg.subject << " failed: " << e.what();
572 reply(nats_, msg, update_my_email_response{
573 .success = false, .message = e.what()});
574 }
575 }
576
577 void select_party(ores::nats::message msg) {
578 using namespace ores::logging;
579 BOOST_LOG_SEV(account_handler_lg(), debug)
580 << "Handling " << msg.subject;
581 auto req = decode<select_party_request>(msg);
582 if (!req) {
583 BOOST_LOG_SEV(account_handler_lg(), warn)
584 << "Failed to decode: " << msg.subject;
585 return;
586 }
587 try {
588 auto token = acct_extract_bearer_token(msg);
589 if (token.empty()) {
590 reply(nats_, msg, select_party_response{
591 .success = false,
592 .message = "Missing authorization token"});
593 return;
594 }
595
596 auto claims_result = signer_.validate(token);
597 if (!claims_result ||
598 claims_result->audience != "select_party_only") {
599 reply(nats_, msg, select_party_response{
600 .success = false,
601 .message = "Invalid or expired token"});
602 return;
603 }
604
605 boost::uuids::string_generator sg;
606 auto account_id = sg(claims_result->subject);
607
608 auto ctx_expected = ores::service::service::make_request_context(
609 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
610 if (!ctx_expected) {
611 error_reply(nats_, msg, ctx_expected.error());
612 return;
613 }
614 const auto& ctx = *ctx_expected;
615 boost::uuids::uuid requested_party_id =
616 sg(req->party_id);
617 repository::account_party_repository ap_repo(ctx);
618 auto parties =
619 ap_repo.read_latest_by_account(account_id);
620
621 bool is_member = false;
622 for (const auto& ap : parties) {
623 if (ap.party_id == requested_party_id) {
624 is_member = true;
625 break;
626 }
627 }
628
629 if (!is_member) {
630 reply(nats_, msg, select_party_response{
631 .success = false,
632 .message =
633 "User is not a member of requested party"});
634 return;
635 }
636
637 auto tenant_id_str = claims_result->tenant_id.value_or(
638 ctx_.tenant_id().to_string());
639 auto visible = acct_compute_visible_party_ids(
640 ctx, requested_party_id);
641
642 security::jwt::jwt_claims new_claims;
643 new_claims.subject = claims_result->subject;
644 new_claims.issued_at = std::chrono::system_clock::now();
645 new_claims.expires_at =
646 new_claims.issued_at + std::chrono::seconds(
647 token_settings_.access_lifetime_s);
648 new_claims.username = claims_result->username;
649 new_claims.email = claims_result->email;
650 new_claims.tenant_id = tenant_id_str;
651 new_claims.party_id =
652 boost::uuids::to_string(requested_party_id);
653 // Carry the session identifiers forward so logout can end
654 // the session record created at login.
655 new_claims.session_id = claims_result->session_id;
656 new_claims.session_start_time = claims_result->session_start_time;
657 for (const auto& vid : visible)
658 new_claims.visible_party_ids.push_back(
659 boost::uuids::to_string(vid));
660
661 auto new_token =
662 signer_.create_token(new_claims).value_or("");
663
664 std::string t_name;
665 std::string p_name;
666 try {
667 auto tid = sg(tenant_id_str);
668 t_name = acct_lookup_tenant_name(ctx, tid);
669 } catch (const std::exception& e) {
670 BOOST_LOG_SEV(account_handler_lg(), warn)
671 << "Failed to look up tenant name during "
672 "party selection: " << e.what();
673 }
674 if (const auto p = acct_lookup_party(ctx, requested_party_id))
675 p_name = p->full_name;
676
677 BOOST_LOG_SEV(account_handler_lg(), debug)
678 << "Completed " << msg.subject;
679 reply(nats_, msg, select_party_response{
680 .success = true,
681 .message = "Party selected",
682 .token = new_token,
683 .username = claims_result->username.value_or(""),
684 .tenant_name = t_name,
685 .party_name = p_name
686 });
687 } catch (const std::exception& e) {
688 BOOST_LOG_SEV(account_handler_lg(), error)
689 << msg.subject << " failed: " << e.what();
690 reply(nats_, msg, select_party_response{
691 .success = false, .message = e.what()});
692 }
693 }
694
695 void history(ores::nats::message msg) {
696 using namespace ores::logging;
697 BOOST_LOG_SEV(account_handler_lg(), debug)
698 << "Handling " << msg.subject;
699 auto req = decode<get_account_history_request>(msg);
700 if (!req) {
701 BOOST_LOG_SEV(account_handler_lg(), warn)
702 << "Failed to decode: " << msg.subject;
703 return;
704 }
705 try {
706 auto ctx_expected = ores::service::service::make_request_context(
707 ctx_, msg, std::optional<ores::security::jwt::jwt_authenticator>{signer_});
708 if (!ctx_expected) {
709 error_reply(nats_, msg, ctx_expected.error());
710 return;
711 }
712 const auto& ctx = *ctx_expected;
713 service::account_service svc(ctx);
714 auto accounts = svc.get_account_history(req->username);
715 account_version_history avh;
716 int vnum = static_cast<int>(accounts.size());
717 for (const auto& a : accounts) {
719 av.data = a;
720 av.version_number = vnum--;
721 av.modified_by = a.modified_by;
722 av.recorded_at = a.recorded_at;
723 avh.versions.push_back(std::move(av));
724 }
725 BOOST_LOG_SEV(account_handler_lg(), debug)
726 << "Completed " << msg.subject;
727 reply(nats_, msg, get_account_history_response{
728 .success = true,
729 .history = std::move(avh)});
730 } catch (const std::exception& e) {
731 BOOST_LOG_SEV(account_handler_lg(), error)
732 << msg.subject << " failed: " << e.what();
733 reply(nats_, msg, get_account_history_response{
734 .success = false, .message = e.what()});
735 }
736 }
737
738private:
742 domain::token_settings token_settings_;
743};
744
745} // namespace ores::iam::messaging
746#endif
STL namespace.
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:118
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
Service for managing typed system settings.
Definition system_settings_service.hpp:41