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;
60class bootstrap_handler {
65 : nats_(nats), ctx_(
std::move(ctx)), signer_(
std::move(signer)) {}
69 BOOST_LOG_SEV(bootstrap_handler_lg(), debug)
73 std::make_shared<service::authorization_service>(ctx_);
74 service::bootstrap_mode_service bms(
75 ctx_, ctx_.tenant_id().to_string(), auth_svc);
76 BOOST_LOG_SEV(bootstrap_handler_lg(), debug)
78 reply(nats_, msg, bootstrap_status_response{
79 .is_in_bootstrap_mode = bms.is_in_bootstrap_mode()});
80 }
catch (
const std::exception& e) {
81 BOOST_LOG_SEV(bootstrap_handler_lg(), error)
82 << msg.
subject <<
" failed: " << e.what();
83 reply(nats_, msg, bootstrap_status_response{
84 .is_in_bootstrap_mode =
false,
85 .message = e.what()});
91 BOOST_LOG_SEV(bootstrap_handler_lg(), debug)
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()});
155 BOOST_LOG_SEV(bootstrap_handler_lg(), debug)
157 auto req = decode<provision_tenant_request>(msg);
159 BOOST_LOG_SEV(bootstrap_handler_lg(), warn)
160 <<
"Failed to decode: " << msg.
subject;
165 using ores::database::repository::execute_parameterized_string_query;
171 auto sys_ctx = tenant_context::with_system_tenant(ctx_);
178 const auto results = execute_parameterized_string_query(sys_ctx,
179 "SELECT ores_iam_provision_tenant_fn($1, $2, $3, $4, $5, $6)::text",
180 {req->type, req->code, req->name, req->hostname,
181 req->description, ctx_.service_account()},
182 bootstrap_handler_lg(),
"Provisioning tenant");
184 if (results.empty()) {
185 reply(nats_, msg, provision_tenant_response{
187 .error_message =
"Provisioner returned no tenant_id"});
191 const auto& tenant_id_str = results[0];
192 BOOST_LOG_SEV(bootstrap_handler_lg(), info)
193 <<
"Provisioned tenant: " << req->code
194 <<
" (id: " << tenant_id_str <<
")";
197 auto tenant_ctx = tenant_context::with_tenant(ctx_, tenant_id_str);
198 service::account_service svc(tenant_ctx);
199 auto acct = svc.create_account(
200 req->principal, req->email, req->password,
201 ctx_.service_account());
205 auto system_parties = party_repo.read_system_party(tenant_id_str);
206 if (!system_parties.empty()) {
207 const auto& sys_party = system_parties.front();
208 domain::account_party ap;
209 ap.account_id = acct.id;
210 ap.party_id = sys_party.id;
211 ap.tenant_id = tenant_id_str;
212 ap.modified_by = req->principal;
213 ap.performed_by = req->principal;
214 ap.change_reason_code =
"system.initial_load";
215 ap.change_commentary =
216 "Provision tenant: associate admin with system party";
217 service::account_party_service ap_svc(tenant_ctx);
218 ap_svc.save_account_party(ap);
219 BOOST_LOG_SEV(bootstrap_handler_lg(), info)
220 <<
"Associated " << req->principal
221 <<
" with system party "
222 << boost::uuids::to_string(sys_party.id);
224 BOOST_LOG_SEV(bootstrap_handler_lg(), warn)
225 <<
"No system party found for tenant " << tenant_id_str
226 <<
"; account has no party context";
229 BOOST_LOG_SEV(bootstrap_handler_lg(), debug)
230 <<
"Completed " << msg.
subject;
231 reply(nats_, msg, provision_tenant_response{
233 .account_id = boost::uuids::to_string(acct.id),
234 .tenant_id = tenant_id_str});
235 }
catch (
const std::exception& e) {
236 BOOST_LOG_SEV(bootstrap_handler_lg(), error)
237 << msg.
subject <<
" failed: " << e.what();
238 reply(nats_, msg, provision_tenant_response{
239 .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