From 1e66d14710b694f273ea39cf119db15d62e10bf9 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Sun, 2 Apr 2023 20:57:33 -0400 Subject: [PATCH] whoa, i forgot to commit and made another ton of stuff --- Cargo.lock | 35 +++++ trifid-api/Cargo.toml | 5 +- trifid-api/src/auth_tokens.rs | 136 +++++++++++++++++ trifid-api/src/config.rs | 7 +- trifid-api/src/main.rs | 2 + trifid-api/src/routes/v1/mod.rs | 3 +- .../src/routes/v1/totp_authenticators.rs | 141 ++++++++++++++++++ trifid-api/src/tokens.rs | 4 + .../trifid_api_entities/src/entity/api_key.rs | 41 +++++ .../src/entity/api_key_scope.rs | 32 ++++ .../src/entity/auth_token.rs | 32 ++++ .../trifid_api_entities/src/entity/mod.rs | 6 + .../trifid_api_entities/src/entity/network.rs | 33 ++++ .../src/entity/organization.rs | 49 ++++++ .../trifid_api_entities/src/entity/prelude.rs | 6 + .../src/entity/totp_authenticator.rs | 38 +++++ .../trifid_api_entities/src/entity/user.rs | 24 +++ trifid-api/trifid_api_migration/src/lib.rs | 12 ++ ...30402_232316_create_table_organizations.rs | 37 +++++ .../m20230402_232323_create_table_networks.rs | 38 +++++ .../m20230402_233043_create_table_api_keys.rs | 39 +++++ ...402_233047_create_table_api_keys_scopes.rs | 38 +++++ ...234025_create_table_totp_authenticators.rs | 44 ++++++ ...0230403_002256_create_table_auth_tokens.rs | 40 +++++ 24 files changed, 837 insertions(+), 5 deletions(-) create mode 100644 trifid-api/src/auth_tokens.rs create mode 100644 trifid-api/src/routes/v1/totp_authenticators.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/api_key.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/api_key_scope.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/auth_token.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/network.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/organization.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230402_232316_create_table_organizations.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230402_232323_create_table_networks.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230402_233043_create_table_api_keys.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230402_233047_create_table_api_keys_scopes.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230402_234025_create_table_totp_authenticators.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230403_002256_create_table_auth_tokens.rs diff --git a/Cargo.lock b/Cargo.lock index 0ca5eee..b132d83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -465,6 +465,12 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + [[package]] name = "base64" version = "0.13.1" @@ -788,6 +794,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" +[[package]] +name = "constant_time_eq" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" + [[package]] name = "convert_case" version = "0.4.0" @@ -3313,6 +3325,22 @@ dependencies = [ "winnow", ] +[[package]] +name = "totp-rs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332e333b188e843cb4cc477b2911160a533bcfc6e9e488d7bef25011f9e2ba1b" +dependencies = [ + "base32", + "constant_time_eq", + "hmac", + "rand", + "sha1", + "sha2", + "url", + "urlencoding", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -3381,6 +3409,7 @@ dependencies = [ "serde", "simple_logger", "toml 0.7.3", + "totp-rs", "trifid_api_entities", "trifid_api_migration", ] @@ -3497,6 +3526,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" + [[package]] name = "uuid" version = "1.3.0" diff --git a/trifid-api/Cargo.toml b/trifid-api/Cargo.toml index ab1f364..5b08380 100644 --- a/trifid-api/Cargo.toml +++ b/trifid-api/Cargo.toml @@ -21,5 +21,6 @@ sea-orm = { version = "^0", features = [ "sqlx-postgres", "runtime-actix-rustls" trifid_api_migration = { version = "0.1.0", path = "trifid_api_migration" } # Database trifid_api_entities = { version = "0.1.0", path = "trifid_api_entities" } # Database -rand = "0.8" # Misc. -hex = "0.4" # Misc. \ No newline at end of file +rand = "0.8" # Misc. +hex = "0.4" # Misc. +totp-rs = { version = "5.0.1", features = ["gen_secret", "otpauth"] } # Misc. \ No newline at end of file diff --git a/trifid-api/src/auth_tokens.rs b/trifid-api/src/auth_tokens.rs new file mode 100644 index 0000000..167b3cd --- /dev/null +++ b/trifid-api/src/auth_tokens.rs @@ -0,0 +1,136 @@ +use std::error::Error; +use actix_web::HttpRequest; +use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, QueryFilter}; +use crate::tokens::get_token_type; +use trifid_api_entities::entity::{auth_token, session_token}; +use trifid_api_entities::entity::api_key; +use trifid_api_entities::entity::api_key_scope; +use trifid_api_entities::entity::user; +use crate::timers::expired; + +pub enum TokenInfo { + SessionToken(SessionTokenInfo), + AuthToken(AuthTokenInfo), + ApiToken(ApiTokenInfo) +} + +pub struct SessionTokenInfo { + pub token: String, + pub user: SessionTokenUser, + pub expires_at: i64 +} + +pub struct SessionTokenUser { + pub id: String, + pub email: String +} + +pub struct ApiTokenInfo { + pub scopes: Vec, + pub organization: String +} + +pub struct AuthTokenInfo { + pub token: String, + pub session_info: SessionTokenInfo +} + +pub async fn enforce_session(req: &HttpRequest, db: &DatabaseConnection) -> Result> { + let header = req.headers().get("Authorization").ok_or("Missing authorization header")?; + let authorization = header.to_str()?; + + let authorization_split: Vec<&str> = authorization.split(' ').collect(); + if authorization_split[0] != "Bearer" { + return Err("Not a bearer token".into()) + } + let tokens = &authorization_split[1..]; + + let sess_token = tokens.iter().find(|i| get_token_type(**i).unwrap_or("n-sess") == "sess").copied().ok_or("Missing session token")?; + + let token: session_token::Model = session_token::Entity::find().filter(session_token::Column::Id.eq(sess_token)).one(db).await?.ok_or("Invalid session token")?; + + if expired(token.expires_on as u64) { + return Err("Token expired".into()); + } + + let user: user::Model = user::Entity::find().filter(user::Column::Id.eq(token.user)).one(db).await?.ok_or("Session token has a nonexistent user")?; + + Ok(TokenInfo::SessionToken(SessionTokenInfo { + token: token.id, + user: SessionTokenUser { + id: user.id, + email: user.email + }, + expires_at: token.expires_on, + })) +} + +pub async fn enforce_2fa(req: &HttpRequest, db: &DatabaseConnection) -> Result> { + let session_data = match enforce_session(req, db).await? { + TokenInfo::SessionToken(i) => i, + _ => unreachable!() + }; + + let header = req.headers().get("Authorization").ok_or("Missing authorization header")?; + let authorization = header.to_str()?; + + let authorization_split: Vec<&str> = authorization.split(' ').collect(); + if authorization_split[0] != "Bearer" { + return Err("Not a bearer token".into()) + } + let tokens = &authorization_split[1..]; + + let auth_token = tokens.iter().find(|i| get_token_type(**i).unwrap_or("n-auth") == "auth").copied().ok_or("Missing auth token")?; + + let token: auth_token::Model = auth_token::Entity::find().filter(auth_token::Column::Id.eq(auth_token)).one(db).await?.ok_or("Invalid session token")?; + + if expired(token.expires_on as u64) { + return Err("Token expired".into()); + } + + Ok(TokenInfo::AuthToken(AuthTokenInfo { + token: token.id, + session_info: session_data, + })) +} + +pub async fn enforce_api_token(req: &HttpRequest, scopes: &[&str], db: &DatabaseConnection) -> Result> { + let header = req.headers().get("Authorization").ok_or("Missing authorization header")?; + let authorization = header.to_str()?; + + let authorization_split: Vec<&str> = authorization.split(' ').collect(); + if authorization_split[0] != "Bearer" { + return Err("Not a bearer token".into()) + } + let tokens = &authorization_split[1..]; + + let api_token = tokens.iter().find(|i| get_token_type(**i).unwrap_or("n-tfkey") == "tfkey").copied().ok_or("Missing api token")?; + + // API tokens are special and have a different form than other keys. + // They follow the form: + // tfkey-[ID]-[TOKEN] + + let api_token_split: Vec<&str> = api_token.split('-').collect(); + if api_token_split.len() != 3 { + return Err("API token is missing key".into()); + } + let token_id = format!("{}-{}", api_token_split[0], api_token_split[1]); + let token_key = api_token_split[2].to_string(); + + let token: api_key::Model = api_key::Entity::find().filter( + Condition::all().add(api_key::Column::Id.eq(token_id)).add(api_key::Column::Key.eq(token_key)) + ).one(db).await?.ok_or("Invalid api token")?; + let token_scopes: Vec = api_key_scope::Entity::find().filter(api_key_scope::Column::ApiKey.eq(api_token)).all(db).await?; + let token_scopes: Vec<&str> = token_scopes.iter().map(|i| i.scope.as_str()).collect(); + + for scope in scopes { + if !token_scopes.contains(scope) { + return Err(format!("API token is missing scope {}", scope).into()); + } + } + + Ok(TokenInfo::ApiToken(ApiTokenInfo { + scopes: token_scopes.iter().map(|i| i.to_string()).collect(), + organization: token.organization, + })) +} \ No newline at end of file diff --git a/trifid-api/src/config.rs b/trifid-api/src/config.rs index 1a8d1d5..b9a8420 100644 --- a/trifid-api/src/config.rs +++ b/trifid-api/src/config.rs @@ -59,7 +59,9 @@ pub struct TrifidConfigTokens { #[serde(default = "magic_link_expiry_time")] pub magic_link_expiry_time_seconds: u64, #[serde(default = "session_token_expiry_time")] - pub session_token_expiry_time_seconds: u64 + pub session_token_expiry_time_seconds: u64, + #[serde(default = "totp_setup_timeout_time")] + pub totp_setup_timeout_time_seconds: u64 } fn max_connections_default() -> u32 { 100 } @@ -68,4 +70,5 @@ fn time_defaults() -> u64 { 8 } fn sqlx_logging_default() -> bool { true } fn socketaddr_8080() -> SocketAddr { SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080)) } fn magic_link_expiry_time() -> u64 { 3600 } // 1 hour -fn session_token_expiry_time() -> u64 { 15780000 } // 6 months \ No newline at end of file +fn session_token_expiry_time() -> u64 { 15780000 } // 6 months +fn totp_setup_timeout_time() -> u64 { 600 } // 10 minutes \ No newline at end of file diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 18cd637..4218f86 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -16,6 +16,7 @@ pub mod error; pub mod tokens; pub mod timers; pub mod magic_link; +pub mod auth_tokens; pub struct AppState { pub conn: DatabaseConnection @@ -64,6 +65,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::auth::magic_link::magic_link_request) .service(routes::v1::signup::signup_request) .service(routes::v1::auth::verify_magic_link::verify_magic_link_request) + .service(routes::v1::totp_authenticators::totp_authenticators_request) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/mod.rs b/trifid-api/src/routes/v1/mod.rs index becd02a..65f6347 100644 --- a/trifid-api/src/routes/v1/mod.rs +++ b/trifid-api/src/routes/v1/mod.rs @@ -1,2 +1,3 @@ pub mod auth; -pub mod signup; \ No newline at end of file +pub mod signup; +pub mod totp_authenticators; \ No newline at end of file diff --git a/trifid-api/src/routes/v1/totp_authenticators.rs b/trifid-api/src/routes/v1/totp_authenticators.rs new file mode 100644 index 0000000..8df66d2 --- /dev/null +++ b/trifid-api/src/routes/v1/totp_authenticators.rs @@ -0,0 +1,141 @@ +use serde::{Serialize, Deserialize}; +use actix_web::{HttpRequest, HttpResponse, post}; +use actix_web::web::{Data, Json}; +use log::error; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter}; +use totp_rs::{Algorithm, Secret, TOTP}; +use crate::AppState; +use crate::auth_tokens::{enforce_2fa, enforce_session, TokenInfo}; +use crate::error::{APIError, APIErrorsResponse}; +use trifid_api_entities::entity::totp_authenticator; +use crate::config::CONFIG; +use crate::timers::expires_in_seconds; +use crate::tokens::random_id; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TotpAuthenticatorsRequest {} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TotpAuthenticatorsResponseMetadata {} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TotpAuthenticatorsResponseData { + #[serde(rename = "totpToken")] + pub totp_token: String, + pub secret: String, + pub url: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TotpAuthenticatorsResponse { + pub data: TotpAuthenticatorsResponseData, + pub metadata: TotpAuthenticatorsResponseMetadata, +} + +#[post("/v1/totp-authenticators")] +pub async fn totp_authenticators_request(db: Data, req_data: HttpRequest, req: Json) -> HttpResponse { + // require a user session + let session_token = match enforce_session(&req_data, &db.conn).await { + Ok(r) => { + match r { + TokenInfo::SessionToken(i) => i, + _ => unreachable!() + } + } + Err(e) => { + error!("error enforcing session: {}", e); + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "Unauthorized".to_string(), + path: None, + } + ], + }); + } + }; + + // determine if the user has a totp authenticator + let auther = match totp_authenticator::Entity::find().filter(totp_authenticator::Column::User.eq(&session_token.user.id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + if auther.is_some() { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_ALREADY_HAS_TOTP".to_string(), + message: "This user already has a totp authenticator".to_string(), + path: None, + } + ] + }); + } + + let secret = Secret::generate_secret(); + let totpmachine = match TOTP::new(Algorithm::SHA1, 6, 1, 30, secret.to_bytes().expect("Invalid randomized data"), Some("trifid-api".to_string()), session_token.user.email) { + Ok(m) => m, + Err(e) => { + error!("totp machine create error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_SECRET_ERR".to_string(), + message: "There was an error configuring the authenticator, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + let model = totp_authenticator::Model { + id: random_id("totp"), + secret: Secret::Raw(totpmachine.secret.clone()).to_encoded().to_string(), + url: totpmachine.get_url(), + verified: false, + expires_on: expires_in_seconds(CONFIG.tokens.totp_setup_timeout_time_seconds) as i64, + user: session_token.user.id, + }; + let id = model.id.clone(); + let secret = model.secret.clone(); + let url = model.url.clone(); + + let active_model = model.into_active_model(); + match active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database request, please try again later.".to_string(), + path: None, + } + ], + }) + } + } + + HttpResponse::Ok().json(TotpAuthenticatorsResponse { + data: TotpAuthenticatorsResponseData { + totp_token: id, + secret, + url, + }, + metadata: TotpAuthenticatorsResponseMetadata {}, + }) +} \ No newline at end of file diff --git a/trifid-api/src/tokens.rs b/trifid-api/src/tokens.rs index a1c2475..e441f9a 100644 --- a/trifid-api/src/tokens.rs +++ b/trifid-api/src/tokens.rs @@ -30,4 +30,8 @@ fn random_with_charset(len: u32, charset: &[u8]) -> String { let idx = rand::thread_rng().gen_range(0..charset.len()); charset[idx] as char }).collect() +} + +pub fn get_token_type(token: &str) -> Option<&str> { + token.split('-').collect::>().get(0).copied() } \ No newline at end of file diff --git a/trifid-api/trifid_api_entities/src/entity/api_key.rs b/trifid-api/trifid_api_entities/src/entity/api_key.rs new file mode 100644 index 0000000..04aa795 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/api_key.rs @@ -0,0 +1,41 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "api_key")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + #[sea_orm(unique)] + pub key: String, + pub organization: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::api_key_scope::Entity")] + ApiKeyScope, + #[sea_orm( + belongs_to = "super::organization::Entity", + from = "Column::Organization", + to = "super::organization::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Organization, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ApiKeyScope.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Organization.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/api_key_scope.rs b/trifid-api/trifid_api_entities/src/entity/api_key_scope.rs new file mode 100644 index 0000000..1deffbc --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/api_key_scope.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "api_key_scope")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub scope: String, + pub api_key: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::api_key::Entity", + from = "Column::ApiKey", + to = "super::api_key::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + ApiKey, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ApiKey.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/auth_token.rs b/trifid-api/trifid_api_entities/src/entity/auth_token.rs new file mode 100644 index 0000000..db14039 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/auth_token.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "auth_token")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub user: String, + pub expires_on: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::User", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/mod.rs b/trifid-api/trifid_api_entities/src/entity/mod.rs index fa37cff..82f32b1 100644 --- a/trifid-api/trifid_api_entities/src/entity/mod.rs +++ b/trifid-api/trifid_api_entities/src/entity/mod.rs @@ -2,6 +2,12 @@ pub mod prelude; +pub mod api_key; +pub mod api_key_scope; +pub mod auth_token; pub mod magic_link; +pub mod network; +pub mod organization; pub mod session_token; +pub mod totp_authenticator; pub mod user; diff --git a/trifid-api/trifid_api_entities/src/entity/network.rs b/trifid-api/trifid_api_entities/src/entity/network.rs new file mode 100644 index 0000000..93232d1 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/network.rs @@ -0,0 +1,33 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "network")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + #[sea_orm(unique)] + pub organization: String, + pub ip_block: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::organization::Entity", + from = "Column::Organization", + to = "super::organization::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Organization, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Organization.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/organization.rs b/trifid-api/trifid_api_entities/src/entity/organization.rs new file mode 100644 index 0000000..f5f5026 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/organization.rs @@ -0,0 +1,49 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "organization")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub name: String, + #[sea_orm(unique)] + pub owner: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::api_key::Entity")] + ApiKey, + #[sea_orm(has_one = "super::network::Entity")] + Network, + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::Owner", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ApiKey.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Network.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/prelude.rs b/trifid-api/trifid_api_entities/src/entity/prelude.rs index b5b9410..d201e1a 100644 --- a/trifid-api/trifid_api_entities/src/entity/prelude.rs +++ b/trifid-api/trifid_api_entities/src/entity/prelude.rs @@ -1,5 +1,11 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 +pub use super::api_key::Entity as ApiKey; +pub use super::api_key_scope::Entity as ApiKeyScope; +pub use super::auth_token::Entity as AuthToken; pub use super::magic_link::Entity as MagicLink; +pub use super::network::Entity as Network; +pub use super::organization::Entity as Organization; pub use super::session_token::Entity as SessionToken; +pub use super::totp_authenticator::Entity as TotpAuthenticator; pub use super::user::Entity as User; diff --git a/trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs b/trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs new file mode 100644 index 0000000..5437596 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs @@ -0,0 +1,38 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "totp_authenticator")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + #[sea_orm(unique)] + pub secret: String, + #[sea_orm(unique)] + pub url: String, + pub verified: bool, + pub expires_on: i64, + #[sea_orm(unique)] + pub user: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::User", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/user.rs b/trifid-api/trifid_api_entities/src/entity/user.rs index 1ca5914..0d7b61f 100644 --- a/trifid-api/trifid_api_entities/src/entity/user.rs +++ b/trifid-api/trifid_api_entities/src/entity/user.rs @@ -13,10 +13,22 @@ pub struct Model { #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { + #[sea_orm(has_many = "super::auth_token::Entity")] + AuthToken, #[sea_orm(has_many = "super::magic_link::Entity")] MagicLink, + #[sea_orm(has_one = "super::organization::Entity")] + Organization, #[sea_orm(has_many = "super::session_token::Entity")] SessionToken, + #[sea_orm(has_one = "super::totp_authenticator::Entity")] + TotpAuthenticator, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::AuthToken.def() + } } impl Related for Entity { @@ -25,10 +37,22 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Organization.def() + } +} + impl Related for Entity { fn to() -> RelationDef { Relation::SessionToken.def() } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::TotpAuthenticator.def() + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/src/lib.rs b/trifid-api/trifid_api_migration/src/lib.rs index 5992ac1..68d4c03 100644 --- a/trifid-api/trifid_api_migration/src/lib.rs +++ b/trifid-api/trifid_api_migration/src/lib.rs @@ -5,6 +5,12 @@ pub struct Migrator; pub mod m20230402_162601_create_table_users; pub mod m20230402_183515_create_table_magic_links; pub mod m20230402_213712_create_table_session_tokens; +pub mod m20230402_232316_create_table_organizations; +pub mod m20230402_232323_create_table_networks; +pub mod m20230402_233043_create_table_api_keys; +pub mod m20230402_233047_create_table_api_keys_scopes; +mod m20230402_234025_create_table_totp_authenticators; +mod m20230403_002256_create_table_auth_tokens; #[async_trait::async_trait] impl MigratorTrait for Migrator { @@ -13,6 +19,12 @@ impl MigratorTrait for Migrator { Box::new(m20230402_162601_create_table_users::Migration), Box::new(m20230402_183515_create_table_magic_links::Migration), Box::new(m20230402_213712_create_table_session_tokens::Migration), + Box::new(m20230402_232316_create_table_organizations::Migration), + Box::new(m20230402_232323_create_table_networks::Migration), + Box::new(m20230402_233043_create_table_api_keys::Migration), + Box::new(m20230402_233047_create_table_api_keys_scopes::Migration), + Box::new(m20230402_234025_create_table_totp_authenticators::Migration), + Box::new(m20230403_002256_create_table_auth_tokens::Migration), ] } } diff --git a/trifid-api/trifid_api_migration/src/m20230402_232316_create_table_organizations.rs b/trifid-api/trifid_api_migration/src/m20230402_232316_create_table_organizations.rs new file mode 100644 index 0000000..86d18cd --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230402_232316_create_table_organizations.rs @@ -0,0 +1,37 @@ +use sea_orm_migration::prelude::*; +use crate::m20230402_162601_create_table_users::User; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create().table(Organization::Table) + .col(ColumnDef::new(Organization::Id).string().not_null().primary_key()) + .col(ColumnDef::new(Organization::Name).string().not_null()) + .col(ColumnDef::new(Organization::Owner).string().not_null().unique_key()) + .foreign_key( + ForeignKey::create() + .from(Organization::Table, Organization::Owner) + .to(User::Table, User::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ).to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(Organization::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum Organization { + Table, + Id, + Name, + Owner +} diff --git a/trifid-api/trifid_api_migration/src/m20230402_232323_create_table_networks.rs b/trifid-api/trifid_api_migration/src/m20230402_232323_create_table_networks.rs new file mode 100644 index 0000000..668fdd4 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230402_232323_create_table_networks.rs @@ -0,0 +1,38 @@ +use sea_orm_migration::prelude::*; +use crate::m20230402_232316_create_table_organizations::Organization; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(Network::Table) + .col(ColumnDef::new(Network::Id).string().not_null().primary_key()) + .col(ColumnDef::new(Network::Organization).string().not_null().unique_key()) + .col(ColumnDef::new(Network::IpBlock).string().not_null()) + .foreign_key( + ForeignKey::create() + .from(Network::Table, Network::Organization) + .to(Organization::Table, Organization::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ).to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(Network::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum Network { + Table, + Id, + Organization, + IpBlock +} diff --git a/trifid-api/trifid_api_migration/src/m20230402_233043_create_table_api_keys.rs b/trifid-api/trifid_api_migration/src/m20230402_233043_create_table_api_keys.rs new file mode 100644 index 0000000..c0d36c3 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230402_233043_create_table_api_keys.rs @@ -0,0 +1,39 @@ +use sea_orm_migration::prelude::*; +use crate::m20230402_232316_create_table_organizations::Organization; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(ApiKey::Table) + .col(ColumnDef::new(ApiKey::Id).string().not_null().primary_key()) + .col(ColumnDef::new(ApiKey::Key).string().not_null().unique_key()) + .col(ColumnDef::new(ApiKey::Organization).string().not_null()) + .foreign_key( + ForeignKey::create() + .from(ApiKey::Table, ApiKey::Organization) + .to(Organization::Table, Organization::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ) + .to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(ApiKey::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum ApiKey { + Table, + Id, + Key, + Organization +} diff --git a/trifid-api/trifid_api_migration/src/m20230402_233047_create_table_api_keys_scopes.rs b/trifid-api/trifid_api_migration/src/m20230402_233047_create_table_api_keys_scopes.rs new file mode 100644 index 0000000..359aa24 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230402_233047_create_table_api_keys_scopes.rs @@ -0,0 +1,38 @@ +use sea_orm_migration::prelude::*; +use crate::m20230402_233043_create_table_api_keys::ApiKey; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(ApiKeyScope::Table) + .col(ColumnDef::new(ApiKeyScope::Id).string().not_null().primary_key()) + .col(ColumnDef::new(ApiKeyScope::Scope).string().not_null()) + .col(ColumnDef::new(ApiKeyScope::ApiKey).string().not_null()) + .foreign_key( + ForeignKey::create() + .from(ApiKeyScope::Table, ApiKeyScope::ApiKey) + .to(ApiKey::Table, ApiKey::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ).to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(ApiKeyScope::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum ApiKeyScope { + Table, + Id, + Scope, + ApiKey +} diff --git a/trifid-api/trifid_api_migration/src/m20230402_234025_create_table_totp_authenticators.rs b/trifid-api/trifid_api_migration/src/m20230402_234025_create_table_totp_authenticators.rs new file mode 100644 index 0000000..72b2379 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230402_234025_create_table_totp_authenticators.rs @@ -0,0 +1,44 @@ +use sea_orm_migration::prelude::*; +use crate::m20230402_162601_create_table_users::User; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(TotpAuthenticator::Table) + .col(ColumnDef::new(TotpAuthenticator::Id).string().not_null().primary_key()) + .col(ColumnDef::new(TotpAuthenticator::Secret).string().not_null().unique_key()) + .col(ColumnDef::new(TotpAuthenticator::Url).string().not_null().unique_key()) + .col(ColumnDef::new(TotpAuthenticator::Verified).boolean().not_null()) + .col(ColumnDef::new(TotpAuthenticator::ExpiresOn).big_integer().not_null()) + .col(ColumnDef::new(TotpAuthenticator::User).string().not_null().unique_key()) + .foreign_key( + ForeignKey::create() + .from(TotpAuthenticator::Table, TotpAuthenticator::User) + .to(User::Table, User::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ).to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(TotpAuthenticator::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum TotpAuthenticator { + Table, + Id, + Secret, + Url, + Verified, + ExpiresOn, + User +} diff --git a/trifid-api/trifid_api_migration/src/m20230403_002256_create_table_auth_tokens.rs b/trifid-api/trifid_api_migration/src/m20230403_002256_create_table_auth_tokens.rs new file mode 100644 index 0000000..12a0a67 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230403_002256_create_table_auth_tokens.rs @@ -0,0 +1,40 @@ +use sea_orm_migration::prelude::*; +use crate::m20230402_162601_create_table_users::User; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(AuthToken::Table) + .if_not_exists() + .col(ColumnDef::new(AuthToken::Id).string().not_null().primary_key()) + .col(ColumnDef::new(AuthToken::User).string().not_null()) + .col(ColumnDef::new(AuthToken::ExpiresOn).big_integer().not_null()) + .foreign_key( + ForeignKey::create() + .from(AuthToken::Table, AuthToken::User) + .to(User::Table, User::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ) + .to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(AuthToken::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum AuthToken { + Table, + Id, + User, + ExpiresOn +}