Create user using kanidm

This commit is contained in:
Adrian Woźniak 2024-06-25 16:08:27 +02:00
parent 918a906b64
commit edcce5e05b
15 changed files with 1000 additions and 30 deletions

4
.env
View File

@ -7,7 +7,7 @@ ORDER_DATABASE_URL=postgres://postgres@localhost/myco_orders
TOKEN_DATABASE_URL=postgres://postgres@localhost/myco_tokens TOKEN_DATABASE_URL=postgres://postgres@localhost/myco_tokens
PASS_SALT=18CHwV7eGFAea16z+qMKZg PASS_SALT=18CHwV7eGFAea16z+qMKZg
RUST_LOG=debug RUST_LOG=hyper=error,debug
SESSION_SECRET="NEPJs#8jjn8SK8GC7QEC^*P844UgsyEbQB8mRWXkT%3mPrwewZoc25MMby9H#R*w2KzaQgMkk#Pif$kxrLy*N5L!Ch%jxbWoa%gb" SESSION_SECRET="NEPJs#8jjn8SK8GC7QEC^*P844UgsyEbQB8mRWXkT%3mPrwewZoc25MMby9H#R*w2KzaQgMkk#Pif$kxrLy*N5L!Ch%jxbWoa%gb"
JWT_SECRET="42^iFq&ZnQbUf!hwGWXd&CpyY6QQyJmkPU%esFCvne5&Ejcb3nJ4&GyHZp!MArZLf^9*5c6!!VgM$iZ8T%d#&bWTi&xbZk2S@4RN" JWT_SECRET="42^iFq&ZnQbUf!hwGWXd&CpyY6QQyJmkPU%esFCvne5&Ejcb3nJ4&GyHZp!MArZLf^9*5c6!!VgM$iZ8T%d#&bWTi&xbZk2S@4RN"
SIGNATURE=David SIGNATURE=David
@ -38,3 +38,5 @@ SONIC_SEARCH_PASS=SecretPassword
SONIC_INGEST_ADDR=[::1]:1491 SONIC_INGEST_ADDR=[::1]:1491
SONIC_INGEST_PASS=SecretPassword SONIC_INGEST_PASS=SecretPassword
SEARCH_ACTIVE=true SEARCH_ACTIVE=true
KANIDM_IDM_ADMIN_PASS=2MScM5Cr2ANyLRps4ccGZjSJdx8bth6yXEwKJDqYU5ZdNfKN

1
.gitignore vendored
View File

@ -7,3 +7,4 @@ web/dist
web/tmp web/tmp
adapters adapters
plugins plugins
.env

758
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ members = [
"crates/testx", "crates/testx",
"crates/db-utils", "crates/db-utils",
# actors # actors
# "crates/account_manager", "crates/account_manager",
# "crates/cart_manager", # "crates/cart_manager",
# "crates/database_manager", # "crates/database_manager",
# "crates/email_manager", # "crates/email_manager",

View File

@ -53,7 +53,6 @@ db_path = "/data/kanidm.db"
tls_chain = "/data/ca.pem" tls_chain = "/data/ca.pem"
tls_key = "/data/ca.key" tls_key = "/data/ca.key"
verify_ca = false
# #
# The log level of the server. May be one of info, debug, trace # The log level of the server. May be one of info, debug, trace
@ -61,7 +60,7 @@ verify_ca = false
# NOTE: this can be overridden by the environment variable # NOTE: this can be overridden by the environment variable
# `KANIDM_LOG_LEVEL` at runtime # `KANIDM_LOG_LEVEL` at runtime
# Defaults to "info" # Defaults to "info"
# log_level = "info" log_level = "info"
# #
# The DNS domain name of the server. This is used in a # The DNS domain name of the server. This is used in a
# number of security-critical contexts # number of security-critical contexts

View File

@ -16,6 +16,8 @@ dotenv = { version = "0" }
futures = { version = "0" } futures = { version = "0" }
gumdrop = { version = "0" } gumdrop = { version = "0" }
json = { version = "0" } json = { version = "0" }
kanidm_client = "1.2.2"
kanidm_proto = "1.2.2"
model = { path = "../model", features = ['db'] } model = { path = "../model", features = ['db'] }
rumqttc = { version = "*" } rumqttc = { version = "*" }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
@ -25,6 +27,7 @@ tarpc = { version = "0", features = ["tokio1", "serde-transport-bincode", "serde
thiserror = { version = "1" } thiserror = { version = "1" }
tokio = { version = "1", features = ['full'] } tokio = { version = "1", features = ['full'] }
tracing = { version = "0" } tracing = { version = "0" }
uuid = { workspace = true, features = ["v4"] }
[dev-dependencies] [dev-dependencies]
fake = { version = "2" } fake = { version = "2" }

View File

@ -4,8 +4,7 @@ pub mod addresses;
pub use accounts::*; pub use accounts::*;
pub use addresses::*; pub use addresses::*;
use config::SharedAppConfig; use config::SharedAppConfig;
use sqlx_core::pool::Pool; use sqlx::{Pool, Postgres};
use sqlx_core::postgres::Postgres;
#[derive(Clone)] #[derive(Clone)]
pub struct Database { pub struct Database {

View File

@ -0,0 +1,126 @@
use kanidm_client::{ClientError, KanidmClient};
use kanidm_proto::internal::CUStatus;
use kanidm_proto::v1::Entry;
pub async fn refresh_token(kanidm: &KanidmClient) -> Result<(), ClientError> {
kanidm
.auth_simple_password(
"idm_admin",
&std::env::var("KANIDM_IDM_ADMIN_PASS")
.expect("idm_admin password is requied, please set KANIDM_IDM_ADMIN_PASS"),
)
.await?;
Ok(())
}
pub async fn create_account_with_password(
kanidm: &KanidmClient,
login: &str,
display_name: &str,
email: &str,
password: &str,
) -> Result<(), ClientError> {
refresh_token(kanidm).await?;
let _person_created = kanidm
.idm_person_account_create(login, display_name)
.await
.ok();
let accounts = accounts(kanidm).await?;
let uid = find_account(&accounts, FindBy::Name(login)).await?;
let id = uid.to_string();
kanidm
.idm_person_account_update(&id, None, None, None, Some(&[email.to_string()]))
.await?;
let (session_token, status) = kanidm.idm_account_credential_update_begin(&id).await?;
tracing::debug!(
"Begin update credentials ({can_commit}): {status:?}",
can_commit = status.can_commit
);
kanidm
.idm_account_credential_update_set_password(&session_token, password)
.await?;
let status = kanidm
.idm_account_credential_update_status(&session_token)
.await?;
tracing::debug!(
"Set password ({can_commit}): {status:?}",
can_commit = status.can_commit
);
let status = kanidm
.idm_account_credential_update_init_totp(&session_token)
.await?;
tracing::debug!(
"Init TOTP ({can_commit}): {status:?}",
can_commit = status.can_commit
);
// let status = kanidm
// .idm_account_credential_update_check_totp(&session_token, totp_chal,
// label) .await?;
tracing::debug!(
"TOTP check ({can_commit}): {status:?}",
can_commit = status.can_commit
);
kanidm
.idm_account_credential_update_commit(&session_token)
.await?;
let status = kanidm
.idm_account_credential_update_status(&session_token)
.await?;
tracing::debug!(
"Commit ({can_commit}): {status:?}",
can_commit = status.can_commit
);
Ok(())
}
pub async fn accounts(kanidm: &KanidmClient) -> Result<Vec<Entry>, ClientError> {
refresh_token(kanidm).await?;
kanidm.idm_person_account_list().await
}
#[derive(Debug)]
pub enum FindBy<'s> {
Email(&'s str),
Name(&'s str),
}
impl<'s> FindBy<'s> {
fn key(&self) -> &'static str {
match self {
Self::Email(..) => "mail",
Self::Name(..) => "name",
}
}
fn as_str(&self) -> &'s str {
match self {
Self::Email(s) => s,
Self::Name(s) => s,
}
}
}
pub async fn find_account(list: &[Entry], find_by: FindBy<'_>) -> Result<uuid::Uuid, ClientError> {
list.iter()
.find_map(|entra| {
tracing::debug!("compare {find_by:?} with {entra:?}");
let attrs = &entra.attrs;
attrs.get(find_by.key()).filter(|v| {
tracing::debug!("compare value {v:?} with {s}", s = find_by.as_str());
v.iter().any(|s| s == find_by.as_str())
})?;
let id = attrs.get("uuid").and_then(|v| v.first())?;
id.parse::<uuid::Uuid>().ok()
})
.ok_or_else(|| {
tracing::info!("User not found");
ClientError::Unauthorized
})
}

View File

@ -4,6 +4,7 @@ use config::UpdateConfig;
pub mod actions; pub mod actions;
pub mod db; pub mod db;
pub mod idp;
pub mod mqtt; pub mod mqtt;
pub mod rpc; pub mod rpc;
@ -38,6 +39,23 @@ async fn main() {
let db = db::Database::build(config.clone()).await; let db = db::Database::build(config.clone()).await;
let kanidm = kanidm_client::KanidmClientBuilder::new()
.address(config.lock().account_manager().idm_url().to_owned())
.danger_accept_invalid_certs(cfg!(debug_assertions))
.connect_timeout(2)
.build()
.unwrap();
idp::accounts(&kanidm).await.unwrap();
idp::create_account_with_password(
&kanidm,
"eraden",
"Adrian Woźniak",
"adrian.wozniak@ita-prog.pl",
"n59GmOOdcpVUJqJ1",
)
.await
.unwrap();
let mqtt_client = mqtt::start(config.clone(), db.clone()).await; let mqtt_client = mqtt::start(config.clone(), db.clone()).await;
rpc::start(config.clone(), db.clone(), mqtt_client.clone()).await; rpc::start(config.clone(), db.clone(), mqtt_client.clone()).await;
} }

View File

@ -29,7 +29,6 @@ struct AccountsServer {
mqtt_client: AsyncClient, mqtt_client: AsyncClient,
} }
#[tarpc::server]
impl Accounts for AccountsServer { impl Accounts for AccountsServer {
async fn me(self, _: context::Context, input: me::Input) -> me::Output { async fn me(self, _: context::Context, input: me::Input) -> me::Output {
let res = actions::me(input.account_id, self.db).await; let res = actions::me(input.account_id, self.db).await;

View File

@ -8,10 +8,9 @@ use tarpc::tokio_serde::formats::Bincode;
pub async fn start<Server, Req, Build>(name: &str, port: u16, build: Build) pub async fn start<Server, Req, Build>(name: &str, port: u16, build: Build)
where where
Server: Serve<Req> + Send + 'static + Clone, Server: Serve<Req = Req> + Send + 'static + Clone,
Build: Fn() -> Server, Build: Fn() -> Server,
<Server as Serve<Req>>::Fut: Send, Server::Resp: serde::Serialize + Send + 'static,
<Server as Serve<Req>>::Resp: serde::Serialize + Send + 'static,
Req: Send + 'static, Req: Send + 'static,
Req: for<'l> serde::Deserialize<'l>, Req: for<'l> serde::Deserialize<'l>,
{ {
@ -33,7 +32,7 @@ where
// the generated World trait. // the generated World trait.
.map(|channel| channel.execute(build())) .map(|channel| channel.execute(build()))
// Max 10 channels. // Max 10 channels.
.buffer_unordered(10) // .buffer_unordered(10)
.for_each(|_| async {}) .for_each(|_| async {})
.await; .await;
tracing::info!("RPC channel closed"); tracing::info!("RPC channel closed");

View File

@ -354,6 +354,7 @@ pub struct AccountManagerConfig {
pub mqtt_port: u16, pub mqtt_port: u16,
pub mqtt_bind: String, pub mqtt_bind: String,
pub database_url: String, pub database_url: String,
pub idm_url: String,
} }
impl Default for AccountManagerConfig { impl Default for AccountManagerConfig {
@ -364,6 +365,7 @@ impl Default for AccountManagerConfig {
mqtt_port: 1883, mqtt_port: 1883,
mqtt_bind: "0.0.0.0".into(), mqtt_bind: "0.0.0.0".into(),
database_url: "postgres://postgres@localhost/myco_accounts".into(), database_url: "postgres://postgres@localhost/myco_accounts".into(),
idm_url: "https://localhost:8443".into(),
} }
} }
} }
@ -378,6 +380,10 @@ impl AccountManagerConfig {
pub fn mqtt_addr(&self) -> (&str, u16) { pub fn mqtt_addr(&self) -> (&str, u16) {
(&self.mqtt_bind, self.mqtt_port) (&self.mqtt_bind, self.mqtt_port)
} }
pub fn idm_url(&self) -> &str {
&self.idm_url
}
} }
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]

View File

@ -149,7 +149,7 @@ impl From<(crate::Order, Vec<crate::OrderItem>)> for Order {
checkout_notes, checkout_notes,
address_id, address_id,
}, },
mut items, items,
): (crate::Order, Vec<crate::OrderItem>), ): (crate::Order, Vec<crate::OrderItem>),
) -> Self { ) -> Self {
Order { Order {

View File

@ -10,7 +10,13 @@ services:
ports: ports:
- 636:3636 - 636:3636
- 443:8443 - 443:8443
- 8443:8443
- 8400:80 - 8400:80
rumqqtd:
image: bytebeamio/rumqttd
ports:
- 1883:1883
- 1884:1884
quickwit: quickwit:
image: quickwit/quickwit:v0.5.2 image: quickwit/quickwit:v0.5.2
command: run command: run

90
myco.toml Normal file
View File

@ -0,0 +1,90 @@
[account_manager]
rpc_port = 19329
rpc_bind = "0.0.0.0"
mqtt_port = 1883
mqtt_bind = "0.0.0.0"
database_url = "postgres://postgres@localhost/myco_accounts"
idm_url = "https://localhost:8443"
[cart_manager]
rpc_port = 19330
rpc_bind = "0.0.0.0"
mqtt_port = 1884
mqtt_bind = "0.0.0.0"
database_url = "postgres://postgres@localhost/myco_carts"
[database]
url = "postgres://postgres@localhost/myco"
[email_sender]
rpc_port = 19331
rpc_bind = "0.0.0.0"
mqtt_port = 1885
mqtt_bind = "0.0.0.0"
database_url = "postgres://postgres@localhost/myco_emails"
[files]
public_path = "/uploads"
local_path = "/var/local/myco"
[mail]
sendgrid_secret = "Create sendgrid account and copy credentials here"
sendgrid_api_key = "Create sendgrid account and copy credentials here"
smtp_from = "Valid sendgrid authorized email address. Example: contact@example.com"
[order_manager]
rpc_port = 19334
rpc_bind = "0.0.0.0"
mqtt_port = 1887
mqtt_bind = "0.0.0.0"
database_url = "postgres://postgres@localhost/myco_orders"
[payment]
rpc_port = 19335
rpc_bind = "0.0.0.0"
mqtt_port = 1888
mqtt_bind = "0.0.0.0"
adapters_path = "./adapters"
optional_payment = true
[payment.pay_u]
client_id = "Create payu account and copy here client_id"
client_secret = "Create payu account and copy here client_secret"
merchant_id = "Create payu account and copy here merchant id"
example2 = "custom value2"
example1 = "custom value1"
[search]
sonic_search_addr = "[::1]:1491"
sonic_search_pass = "SecretPassword"
sonic_ingest_addr = "[::1]:1491"
sonic_ingest_pass = "SecretPassword"
rpc_port = 19332
rpc_bind = "0.0.0.0"
search_active = true
[stocks]
rpc_port = 19333
rpc_bind = "0.0.0.0"
mqtt_port = 1886
mqtt_bind = "0.0.0.0"
database_url = "postgres://postgres@localhost/myco_stocks"
[tokens]
rpc_port = 19336
rpc_bind = "0.0.0.0"
mqtt_port = 1889
mqtt_bind = "0.0.0.0"
database_url = "postgres://postgres@localhost/myco_tokens"
[web]
host = "https://your.comain.com"
pass_salt = "Generate it with myco generate-hash"
session_secret = "100 characters long random string"
jwt_secret = "100 characters long random string"
bind = "0.0.0.0"
port = 8080
signature = "John Doe"
service_name = "myco"
[plugin]