20#ifndef ORES_IAM_MESSAGING_BOOTSTRAP_HANDLER_HPP
21#define ORES_IAM_MESSAGING_BOOTSTRAP_HANDLER_HPP
25#include "ores.logging/make_logger.hpp"
26#include "ores.nats/domain/message.hpp"
27#include "ores.nats/service/client.hpp"
28#include "ores.database/domain/context.hpp"
29#include "ores.security/jwt/jwt_authenticator.hpp"
30#include "ores.service/messaging/handler_helpers.hpp"
31#include "ores.iam.api/messaging/bootstrap_protocol.hpp"
32#include "ores.iam.api/domain/account_party.hpp"
33#include "ores.iam.core/service/account_service.hpp"
34#include "ores.iam.core/service/account_party_service.hpp"
35#include "ores.iam.core/service/authorization_service.hpp"
36#include "ores.iam.core/service/bootstrap_mode_service.hpp"
37#include "ores.database/repository/bitemporal_operations.hpp"
38#include "ores.database/service/tenant_context.hpp"
39#include "ores.security/crypto/password_hasher.hpp"
40#include "ores.refdata.api/domain/party.hpp"
41#include "ores.refdata.core/repository/party_repository.hpp"
42#include "ores.utility/uuid/tenant_id.hpp"
43#include <boost/uuid/uuid_io.hpp>
45namespace ores::iam::messaging {
49inline auto& bootstrap_handler_lg() {
50 static auto instance = ores::logging::make_logger(
51 "ores.iam.messaging.bootstrap_handler");
57using ores::service::messaging::reply;
58using ores::service::messaging::decode;
59using ores::service::messaging::log_handler_entry;
62class bootstrap_handler {
67 : nats_(nats), ctx_(
std::move(ctx)), signer_(
std::move(signer)) {}
70 [[maybe_unused]]
const auto correlation_id =
71 log_handler_entry(bootstrap_handler_lg(), msg);
74 std::make_shared<service::authorization_service>(ctx_);
75 service::bootstrap_mode_service bms(
76 ctx_, ctx_.tenant_id().to_string(), auth_svc);
77 BOOST_LOG_SEV(bootstrap_handler_lg(), debug)
79 reply(nats_, msg, bootstrap_status_response{
80 .is_in_bootstrap_mode = bms.is_in_bootstrap_mode()});
81 }
catch (
const std::exception& e) {
82 BOOST_LOG_SEV(bootstrap_handler_lg(), error)
83 << msg.
subject <<
" failed: " << e.what();
84 reply(nats_, msg, bootstrap_status_response{
85 .is_in_bootstrap_mode =
false,
86 .message = e.what()});
91 [[maybe_unused]]
const auto correlation_id =
92 log_handler_entry(bootstrap_handler_lg(), msg);
93 auto req = decode<create_initial_admin_request>(msg);
95 BOOST_LOG_SEV(bootstrap_handler_lg(), warn)
96 <<
"Failed to decode: " << msg.
subject;
103 std::make_shared<service::authorization_service>(ctx_);
104 service::bootstrap_mode_service bms(
105 ctx_, ctx_.tenant_id().to_string(), auth_svc);
106 if (!bms.is_in_bootstrap_mode()) {
107 BOOST_LOG_SEV(bootstrap_handler_lg(), warn)
109 <<
": system is not in bootstrap mode";
110 reply(nats_, msg, create_initial_admin_response{
112 .error_message =
"System is not in bootstrap mode"});
118 using ores::database::repository::execute_parameterized_string_query;
121 const auto password_hash =
126 const auto results = execute_parameterized_string_query(ctx_,
127 "SELECT ores_iam_create_initial_admin_fn($1, $2, $3, $4)::text",
128 {req->principal, req->email, password_hash, req->principal},
129 bootstrap_handler_lg(),
"Creating initial admin");
131 if (results.empty()) {
132 reply(nats_, msg, create_initial_admin_response{
134 .error_message =
"Procedure returned no account_id"});
138 const auto& account_id_str = results[0];
139 BOOST_LOG_SEV(bootstrap_handler_lg(), debug)
140 <<
"Completed " << msg.
subject;
141 reply(nats_, msg, create_initial_admin_response{
143 .account_id = account_id_str,
144 .tenant_id = ctx_.tenant_id().to_string()});
145 }
catch (
const std::exception& e) {
146 BOOST_LOG_SEV(bootstrap_handler_lg(), error)
147 << msg.
subject <<
" failed: " << e.what();
148 reply(nats_, msg, create_initial_admin_response{
149 .success =
false, .error_message = e.what()});
154 [[maybe_unused]]
const auto correlation_id =
155 log_handler_entry(bootstrap_handler_lg(), msg);
156 auto req = decode<provision_tenant_request>(msg);
158 BOOST_LOG_SEV(bootstrap_handler_lg(), warn)
159 <<
"Failed to decode: " << msg.
subject;
164 using ores::database::repository::execute_parameterized_string_query;
170 auto sys_ctx = tenant_context::with_system_tenant(ctx_);
177 const auto results = execute_parameterized_string_query(sys_ctx,
178 "SELECT ores_iam_provision_tenant_fn($1, $2, $3, $4, $5, $6)::text",
179 {req->type, req->code, req->name, req->hostname,
180 req->description, ctx_.service_account()},
181 bootstrap_handler_lg(),
"Provisioning tenant");
183 if (results.empty()) {
184 reply(nats_, msg, provision_tenant_response{
186 .error_message =
"Provisioner returned no tenant_id"});
190 const auto& tenant_id_str = results[0];
191 BOOST_LOG_SEV(bootstrap_handler_lg(), info)
192 <<
"Provisioned tenant: " << req->code
193 <<
" (id: " << tenant_id_str <<
")";
196 auto tenant_ctx = tenant_context::with_tenant(ctx_, tenant_id_str);
197 service::account_service svc(tenant_ctx);
198 auto acct = svc.create_account(
199 req->principal, req->email, req->password,
200 ctx_.service_account());
204 auto system_parties = party_repo.read_system_party(tenant_id_str);
205 if (!system_parties.empty()) {
206 const auto& sys_party = system_parties.front();
207 domain::account_party ap;
208 ap.account_id = acct.id;
209 ap.party_id = sys_party.id;
210 ap.tenant_id = tenant_id_str;
211 ap.modified_by = req->principal;
212 ap.performed_by = req->principal;
213 ap.change_reason_code =
"system.initial_load";
214 ap.change_commentary =
215 "Provision tenant: associate admin with system party";
216 service::account_party_service ap_svc(tenant_ctx);
217 ap_svc.save_account_party(ap);
218 BOOST_LOG_SEV(bootstrap_handler_lg(), info)
219 <<
"Associated " << req->principal
220 <<
" with system party "
221 << boost::uuids::to_string(sys_party.id);
223 BOOST_LOG_SEV(bootstrap_handler_lg(), warn)
224 <<
"No system party found for tenant " << tenant_id_str
225 <<
"; account has no party context";
228 BOOST_LOG_SEV(bootstrap_handler_lg(), debug)
229 <<
"Completed " << msg.
subject;
230 reply(nats_, msg, provision_tenant_response{
232 .account_id = boost::uuids::to_string(acct.id),
233 .tenant_id = tenant_id_str});
234 }
catch (
const std::exception& e) {
235 BOOST_LOG_SEV(bootstrap_handler_lg(), error)
236 << msg.
subject <<
" failed: " << e.what();
237 reply(nats_, msg, provision_tenant_response{
238 .success =
false, .error_message = e.what()});
Implements logging infrastructure for ORE Studio.
Definition boost_severity.hpp:28
Context for the operations on a postgres database.
Definition context.hpp:47
Manages tenant context for multi-tenant database operations.
Definition tenant_context.hpp:37
A received NATS message.
Definition message.hpp:40
std::string subject
The subject the message was published to.
Definition message.hpp:44
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
static std::string hash(const std::string &password)
Creates a password hash from the given password.
Definition password_hasher.cpp:50
JWT authentication primitive.
Definition jwt_authenticator.hpp:45