diff --git a/.env.template b/.env.template index 56c3fd1..c2b42c5 100644 --- a/.env.template +++ b/.env.template @@ -1 +1 @@ -DATABASE_URL="mysql://iam:secret@localhost:3306/iam" +DATABASE_URL="postgres://iam:secret@localhost:3306/iam" diff --git a/Cargo.lock b/Cargo.lock index 9d5ce86..aaf8630 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1302,6 +1302,7 @@ dependencies = [ name = "iam-entity" version = "0.1.0" dependencies = [ + "chrono", "sea-orm", ] diff --git a/Cargo.toml b/Cargo.toml index df40b3f..94a60a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,7 @@ iam-macros = { path = "./iam-macros" } jsonwebtoken = "9.3.0" mime = "0.3.17" rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"] } -sea-orm = { version = "1.0.0", default-features = false, features = ["macros", "runtime-actix-rustls", "sqlx-mysql", "with-chrono"] } +sea-orm = { version = "1.0.0", default-features = false, features = ["macros", "runtime-actix-rustls", "sqlx-postgres", "with-chrono"] } serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread", "signal"] } diff --git a/compose.yaml b/compose.yaml index 5b2b137..1d09436 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,11 +1,9 @@ services: - database: - image: docker.io/mysql:8.0 + db: + image: docker.io/postgres:16 environment: - MYSQL_DATABASE: iam - MYSQL_USER: iam - MYSQL_PASSWORD: secret - MYSQL_ROOT_PASSWORD: secret - TZ: Europe/Budapest + POSTGRES_USER: iam + POSTGRES_PASSWORD: secret + POSTGRES_DB: iam ports: - - "3306:3306" + - 5432:5432 diff --git a/iam-cli/commands/setup.rs b/iam-cli/commands/setup.rs index bbc5ad2..567a4f5 100644 --- a/iam-cli/commands/setup.rs +++ b/iam-cli/commands/setup.rs @@ -12,11 +12,10 @@ use rand::{ }; use sea_orm::Database; use std::collections::BTreeMap; -use url::Url; pub fn command() -> Command { Command::new("setup") - .about("Creates mysql password and admin user") + .about("Creates admin user") .arg( Arg::new("database") .long("database") @@ -40,52 +39,11 @@ pub fn command() -> Command { pub async fn run(matches: &ArgMatches) -> anyhow::Result<()> { let client = Client::try_default().await?; - generate_mysql_password(client.clone()).await?; create_admin_user(matches, client).await?; Ok(()) } -const MYSQL_SECRET_NAME: &str = "mysql"; -const MYSQL_SECRET_KEY: &str = "MYSQL_ROOT_PASSWORD"; - -async fn generate_mysql_password(client: Client) -> anyhow::Result<()> { - let secrets: Api = Api::default_namespaced(client); - - if secrets - .get_opt(MYSQL_SECRET_NAME) - .await - .context("Failed to query secret")? - .is_some() - { - println!("Mysql password already exists."); - return Ok(()); - } - - let mysql_password = Alphanumeric.sample_string(&mut OsRng, 64); - - secrets - .create( - &PostParams::default(), - &Secret { - metadata: ObjectMeta { - name: Some(MYSQL_SECRET_NAME.to_owned()), - ..Default::default() - }, - string_data: Some({ - let mut map = BTreeMap::new(); - map.insert(MYSQL_SECRET_KEY.to_owned(), mysql_password); - map - }), - ..Default::default() - }, - ) - .await - .context("Failed to create secret")?; - - Ok(()) -} - async fn create_admin_user(matches: &ArgMatches, client: Client) -> anyhow::Result<()> { const SECRET_NAME: &str = "iam"; const ADMIN_EMAIL: &str = "admin@admin.admin"; @@ -105,31 +63,6 @@ async fn create_admin_user(matches: &ArgMatches, client: Client) -> anyhow::Resu let iam_url = matches.get_one::("iam").unwrap(); let database_url = matches.get_one::("database").unwrap(); - let database_password = { - let secret = secrets - .get_opt(MYSQL_SECRET_NAME) - .await - .context("Failed to query secret")? - .context("No mysql secret")? - .data - .unwrap(); - - String::from_utf8( - secret - .get(MYSQL_SECRET_KEY) - .context("No mysql password")? - .0 - .clone(), - ) - .context("Not utf8 from kube rs")? - }; - - let database_url = { - let mut url = Url::parse(database_url).context("invalid url")?; - url.set_password(Some(&database_password)).unwrap(); - url - }; - let iam = Iam::new(iam_url); let db = Database::connect(database_url.as_str()).await?; diff --git a/iam-entity/Cargo.toml b/iam-entity/Cargo.toml index 4b91aa2..38caad6 100644 --- a/iam-entity/Cargo.toml +++ b/iam-entity/Cargo.toml @@ -8,4 +8,5 @@ license-file.workspace = true path = "./lib.rs" [dependencies] +chrono.workspace = true sea-orm.workspace = true diff --git a/iam-entity/actions.rs b/iam-entity/actions.rs index 1880090..2bace68 100644 --- a/iam-entity/actions.rs +++ b/iam-entity/actions.rs @@ -1,5 +1,7 @@ +use async_trait::async_trait; +use chrono::Utc; use sea_orm::entity::prelude::*; -use sea_orm::{JoinType, QuerySelect}; +use sea_orm::{JoinType, QuerySelect, Set}; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "actions")] @@ -43,7 +45,16 @@ impl Related for Entity { } } -impl ActiveModelBehavior for ActiveModel {} +#[async_trait] +impl ActiveModelBehavior for ActiveModel { + async fn before_save(mut self, _db: &C, _insert: bool) -> Result + where + C: ConnectionTrait, + { + self.updated_at = Set(Utc::now().naive_utc()); + Ok(self) + } +} impl Entity { pub fn get_actions_for_user_id_through_groups(id: &str) -> Select { diff --git a/iam-entity/apps.rs b/iam-entity/apps.rs index 92b90fa..4fd7765 100644 --- a/iam-entity/apps.rs +++ b/iam-entity/apps.rs @@ -1,4 +1,6 @@ -use sea_orm::entity::prelude::*; +use async_trait::async_trait; +use chrono::Utc; +use sea_orm::{entity::prelude::*, Set}; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "apps")] @@ -15,8 +17,6 @@ pub struct Model { #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} -impl ActiveModelBehavior for ActiveModel {} - impl Related for Entity { fn to() -> RelationDef { super::pivot_apps_actions::Relation::Action.def() @@ -36,3 +36,14 @@ impl Related for Entity { Some(super::pivot_apps_groups::Relation::App.def()) } } + +#[async_trait] +impl ActiveModelBehavior for ActiveModel { + async fn before_save(mut self, _db: &C, _insert: bool) -> Result + where + C: ConnectionTrait, + { + self.updated_at = Set(Utc::now().naive_utc()); + Ok(self) + } +} diff --git a/iam-entity/groups.rs b/iam-entity/groups.rs index adfcd81..327fd62 100644 --- a/iam-entity/groups.rs +++ b/iam-entity/groups.rs @@ -1,4 +1,6 @@ -use sea_orm::entity::prelude::*; +use async_trait::async_trait; +use chrono::Utc; +use sea_orm::{entity::prelude::*, Set}; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "groups")] @@ -41,4 +43,13 @@ impl Related for Entity { } } -impl ActiveModelBehavior for ActiveModel {} +#[async_trait] +impl ActiveModelBehavior for ActiveModel { + async fn before_save(mut self, _db: &C, _insert: bool) -> Result + where + C: ConnectionTrait, + { + self.updated_at = Set(Utc::now().naive_utc()); + Ok(self) + } +} diff --git a/iam-entity/users.rs b/iam-entity/users.rs index 7dc4a8d..90f61ee 100644 --- a/iam-entity/users.rs +++ b/iam-entity/users.rs @@ -1,4 +1,6 @@ -use sea_orm::entity::prelude::*; +use async_trait::async_trait; +use chrono::Utc; +use sea_orm::{entity::prelude::*, Set}; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "users")] @@ -43,4 +45,13 @@ impl Related for Entity { } } -impl ActiveModelBehavior for ActiveModel {} +#[async_trait] +impl ActiveModelBehavior for ActiveModel { + async fn before_save(mut self, _db: &C, _insert: bool) -> Result + where + C: ConnectionTrait, + { + self.updated_at = Set(Utc::now().naive_utc()); + Ok(self) + } +} diff --git a/iam-migration/m20220311_151913_create_users.rs b/iam-migration/m20220311_151913_create_users.rs index b627462..ac140ff 100644 --- a/iam-migration/m20220311_151913_create_users.rs +++ b/iam-migration/m20220311_151913_create_users.rs @@ -26,14 +26,13 @@ impl MigrationTrait for Migration { ColumnDef::new(Column::CreatedAt) .date_time() .not_null() - .extra("DEFAULT CURRENT_TIMESTAMP"), + .default(Expr::current_timestamp()), ) .col( ColumnDef::new(Column::UpdatedAt) .date_time() .not_null() - .extra("DEFAULT CURRENT_TIMESTAMP") - .extra("ON UPDATE CURRENT_TIMESTAMP"), + .default(Expr::current_timestamp()), ) .col( ColumnDef::new(Column::DeletedAt) diff --git a/iam-migration/m20220311_152016_create_actions.rs b/iam-migration/m20220311_152016_create_actions.rs index c8a5836..d580588 100644 --- a/iam-migration/m20220311_152016_create_actions.rs +++ b/iam-migration/m20220311_152016_create_actions.rs @@ -25,14 +25,13 @@ impl MigrationTrait for Migration { ColumnDef::new(Column::CreatedAt) .date_time() .not_null() - .extra("DEFAULT CURRENT_TIMESTAMP"), + .default(Expr::current_timestamp()), ) .col( ColumnDef::new(Column::UpdatedAt) .date_time() .not_null() - .extra("DEFAULT CURRENT_TIMESTAMP") - .extra("ON UPDATE CURRENT_TIMESTAMP"), + .default(Expr::current_timestamp()), ) .col( ColumnDef::new(Column::DeletedAt) diff --git a/iam-migration/m20220416_053618_create_groups.rs b/iam-migration/m20220416_053618_create_groups.rs index 1f4c677..944ae78 100644 --- a/iam-migration/m20220416_053618_create_groups.rs +++ b/iam-migration/m20220416_053618_create_groups.rs @@ -24,14 +24,13 @@ impl MigrationTrait for Migration { ColumnDef::new(Column::CreatedAt) .date_time() .not_null() - .extra("DEFAULT CURRENT_TIMESTAMP"), + .default(Expr::current_timestamp()), ) .col( ColumnDef::new(Column::UpdatedAt) .date_time() .not_null() - .extra("DEFAULT CURRENT_TIMESTAMP") - .extra("ON UPDATE CURRENT_TIMESTAMP"), + .default(Expr::current_timestamp()), ) .col( ColumnDef::new(Column::DeletedAt) diff --git a/iam-migration/m20220416_060135_add_iam_actions.rs b/iam-migration/m20220416_060135_add_iam_actions.rs index 1dbb989..96fe30c 100644 --- a/iam-migration/m20220416_060135_add_iam_actions.rs +++ b/iam-migration/m20220416_060135_add_iam_actions.rs @@ -1,5 +1,6 @@ use iam_common::Id; -use iam_entity::actions::{Column, Entity}; +use iam_entity::actions::{self, Entity}; +use sea_orm::{ActiveModelTrait, Set}; use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] @@ -8,99 +9,38 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .exec_stmt( - Query::insert() - .into_table(Entity) - .columns(vec![Column::Id, Column::Name, Column::Secure]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.action.add".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.action.get".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.action.update".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.action.list".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.action.delete".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.group.add".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.group.get".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.group.list".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.group.delete".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.group.edit".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.user.add".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.user.get".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.user.list".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.user.update".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.user.invite".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.user.delete".into(), - true.into(), - ]) - .values_panic(vec![ - Id::new_action().to_string().into(), - "iam.policy.assign".into(), - true.into(), - ]) - .to_owned(), - ) - .await + let actions = [ + ("iam.action.add", true), + ("iam.action.get", true), + ("iam.action.update", true), + ("iam.action.list", true), + ("iam.action.delete", true), + ("iam.group.add", true), + ("iam.group.get", true), + ("iam.group.list", true), + ("iam.group.delete", true), + ("iam.group.edit", true), + ("iam.user.add", true), + ("iam.user.get", true), + ("iam.user.list", true), + ("iam.user.update", true), + ("iam.user.invite", true), + ("iam.user.delete", true), + ("iam.policy.assign", true), + ]; + + for (name, secure) in actions { + let model = actions::ActiveModel { + id: Set(Id::new_action().to_string()), + name: Set(name.to_string()), + secure: Set(secure), + ..Default::default() + }; + + model.insert(manager.get_connection()).await?; + } + + Ok(()) } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { diff --git a/iam-migration/m20221007_103449_create_app.rs b/iam-migration/m20221007_103449_create_app.rs index 5ace17a..46f6b0e 100644 --- a/iam-migration/m20221007_103449_create_app.rs +++ b/iam-migration/m20221007_103449_create_app.rs @@ -23,14 +23,13 @@ impl MigrationTrait for Migration { ColumnDef::new(Column::CreatedAt) .date_time() .not_null() - .extra("DEFAULT CURRENT_TIMESTAMP"), + .default(Expr::current_timestamp()), ) .col( ColumnDef::new(Column::UpdatedAt) .date_time() .not_null() - .extra("DEFAULT CURRENT_TIMESTAMP") - .extra("ON UPDATE CURRENT_TIMESTAMP"), + .default(Expr::current_timestamp()), ) .col( ColumnDef::new(Column::DeletedAt) diff --git a/iam/Cargo.toml b/iam/Cargo.toml index ef7f941..b0b7007 100644 --- a/iam/Cargo.toml +++ b/iam/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" publish = false license-file.workspace = true +default-run = "iam" [lib] path = "./lib.rs" diff --git a/iam/handlers/v1/users/login.rs b/iam/handlers/v1/users/login.rs index 5273ce0..09fc94b 100644 --- a/iam/handlers/v1/users/login.rs +++ b/iam/handlers/v1/users/login.rs @@ -32,10 +32,8 @@ pub struct LoginResponse { pub async fn login( Extension(shared): Extension, - ValidatedJson(mut req): ValidatedJson, + ValidatedJson(req): ValidatedJson, ) -> Result> { - req.email = req.email.to_lowercase(); - let res = users::Entity::find() .filter(users::Column::Email.eq(req.email.clone())) .one(shared.db())