whoa, i forgot to commit and made another ton of stuff

This commit is contained in:
c0repwn3r 2023-04-02 20:57:33 -04:00
parent 1453509dc4
commit 1e66d14710
Signed by: core
GPG Key ID: FDBF740DADDCEECF
24 changed files with 837 additions and 5 deletions

35
Cargo.lock generated
View File

@ -465,6 +465,12 @@ dependencies = [
"syn 1.0.107", "syn 1.0.107",
] ]
[[package]]
name = "base32"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa"
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.13.1" version = "0.13.1"
@ -788,6 +794,12 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b"
[[package]]
name = "constant_time_eq"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b"
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.4.0" version = "0.4.0"
@ -3313,6 +3325,22 @@ dependencies = [
"winnow", "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]] [[package]]
name = "tower-service" name = "tower-service"
version = "0.3.2" version = "0.3.2"
@ -3381,6 +3409,7 @@ dependencies = [
"serde", "serde",
"simple_logger", "simple_logger",
"toml 0.7.3", "toml 0.7.3",
"totp-rs",
"trifid_api_entities", "trifid_api_entities",
"trifid_api_migration", "trifid_api_migration",
] ]
@ -3497,6 +3526,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "urlencoding"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.3.0" version = "1.3.0"

View File

@ -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_migration = { version = "0.1.0", path = "trifid_api_migration" } # Database
trifid_api_entities = { version = "0.1.0", path = "trifid_api_entities" } # Database trifid_api_entities = { version = "0.1.0", path = "trifid_api_entities" } # Database
rand = "0.8" # Misc. rand = "0.8" # Misc.
hex = "0.4" # Misc. hex = "0.4" # Misc.
totp-rs = { version = "5.0.1", features = ["gen_secret", "otpauth"] } # Misc.

View File

@ -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<String>,
pub organization: String
}
pub struct AuthTokenInfo {
pub token: String,
pub session_info: SessionTokenInfo
}
pub async fn enforce_session(req: &HttpRequest, db: &DatabaseConnection) -> Result<TokenInfo, Box<dyn Error>> {
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<TokenInfo, Box<dyn Error>> {
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<TokenInfo, Box<dyn Error>> {
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::Model> = 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,
}))
}

View File

@ -59,7 +59,9 @@ pub struct TrifidConfigTokens {
#[serde(default = "magic_link_expiry_time")] #[serde(default = "magic_link_expiry_time")]
pub magic_link_expiry_time_seconds: u64, pub magic_link_expiry_time_seconds: u64,
#[serde(default = "session_token_expiry_time")] #[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 } fn max_connections_default() -> u32 { 100 }
@ -69,3 +71,4 @@ fn sqlx_logging_default() -> bool { true }
fn socketaddr_8080() -> SocketAddr { SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080)) } 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 magic_link_expiry_time() -> u64 { 3600 } // 1 hour
fn session_token_expiry_time() -> u64 { 15780000 } // 6 months fn session_token_expiry_time() -> u64 { 15780000 } // 6 months
fn totp_setup_timeout_time() -> u64 { 600 } // 10 minutes

View File

@ -16,6 +16,7 @@ pub mod error;
pub mod tokens; pub mod tokens;
pub mod timers; pub mod timers;
pub mod magic_link; pub mod magic_link;
pub mod auth_tokens;
pub struct AppState { pub struct AppState {
pub conn: DatabaseConnection pub conn: DatabaseConnection
@ -64,6 +65,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
.service(routes::v1::auth::magic_link::magic_link_request) .service(routes::v1::auth::magic_link::magic_link_request)
.service(routes::v1::signup::signup_request) .service(routes::v1::signup::signup_request)
.service(routes::v1::auth::verify_magic_link::verify_magic_link_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?; }).bind(CONFIG.server.bind)?.run().await?;
Ok(()) Ok(())

View File

@ -1,2 +1,3 @@
pub mod auth; pub mod auth;
pub mod signup; pub mod signup;
pub mod totp_authenticators;

View File

@ -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<AppState>, req_data: HttpRequest, req: Json<TotpAuthenticatorsRequest>) -> 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 {},
})
}

View File

@ -31,3 +31,7 @@ fn random_with_charset(len: u32, charset: &[u8]) -> String {
charset[idx] as char charset[idx] as char
}).collect() }).collect()
} }
pub fn get_token_type(token: &str) -> Option<&str> {
token.split('-').collect::<Vec<&str>>().get(0).copied()
}

View File

@ -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<super::api_key_scope::Entity> for Entity {
fn to() -> RelationDef {
Relation::ApiKeyScope.def()
}
}
impl Related<super::organization::Entity> for Entity {
fn to() -> RelationDef {
Relation::Organization.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -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<super::api_key::Entity> for Entity {
fn to() -> RelationDef {
Relation::ApiKey.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -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<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -2,6 +2,12 @@
pub mod prelude; pub mod prelude;
pub mod api_key;
pub mod api_key_scope;
pub mod auth_token;
pub mod magic_link; pub mod magic_link;
pub mod network;
pub mod organization;
pub mod session_token; pub mod session_token;
pub mod totp_authenticator;
pub mod user; pub mod user;

View File

@ -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<super::organization::Entity> for Entity {
fn to() -> RelationDef {
Relation::Organization.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -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<super::api_key::Entity> for Entity {
fn to() -> RelationDef {
Relation::ApiKey.def()
}
}
impl Related<super::network::Entity> for Entity {
fn to() -> RelationDef {
Relation::Network.def()
}
}
impl Related<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -1,5 +1,11 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 //! `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::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::session_token::Entity as SessionToken;
pub use super::totp_authenticator::Entity as TotpAuthenticator;
pub use super::user::Entity as User; pub use super::user::Entity as User;

View File

@ -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<super::user::Entity> for Entity {
fn to() -> RelationDef {
Relation::User.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -13,10 +13,22 @@ pub struct Model {
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation { pub enum Relation {
#[sea_orm(has_many = "super::auth_token::Entity")]
AuthToken,
#[sea_orm(has_many = "super::magic_link::Entity")] #[sea_orm(has_many = "super::magic_link::Entity")]
MagicLink, MagicLink,
#[sea_orm(has_one = "super::organization::Entity")]
Organization,
#[sea_orm(has_many = "super::session_token::Entity")] #[sea_orm(has_many = "super::session_token::Entity")]
SessionToken, SessionToken,
#[sea_orm(has_one = "super::totp_authenticator::Entity")]
TotpAuthenticator,
}
impl Related<super::auth_token::Entity> for Entity {
fn to() -> RelationDef {
Relation::AuthToken.def()
}
} }
impl Related<super::magic_link::Entity> for Entity { impl Related<super::magic_link::Entity> for Entity {
@ -25,10 +37,22 @@ impl Related<super::magic_link::Entity> for Entity {
} }
} }
impl Related<super::organization::Entity> for Entity {
fn to() -> RelationDef {
Relation::Organization.def()
}
}
impl Related<super::session_token::Entity> for Entity { impl Related<super::session_token::Entity> for Entity {
fn to() -> RelationDef { fn to() -> RelationDef {
Relation::SessionToken.def() Relation::SessionToken.def()
} }
} }
impl Related<super::totp_authenticator::Entity> for Entity {
fn to() -> RelationDef {
Relation::TotpAuthenticator.def()
}
}
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}

View File

@ -5,6 +5,12 @@ pub struct Migrator;
pub mod m20230402_162601_create_table_users; pub mod m20230402_162601_create_table_users;
pub mod m20230402_183515_create_table_magic_links; pub mod m20230402_183515_create_table_magic_links;
pub mod m20230402_213712_create_table_session_tokens; 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] #[async_trait::async_trait]
impl MigratorTrait for Migrator { impl MigratorTrait for Migrator {
@ -13,6 +19,12 @@ impl MigratorTrait for Migrator {
Box::new(m20230402_162601_create_table_users::Migration), Box::new(m20230402_162601_create_table_users::Migration),
Box::new(m20230402_183515_create_table_magic_links::Migration), Box::new(m20230402_183515_create_table_magic_links::Migration),
Box::new(m20230402_213712_create_table_session_tokens::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),
] ]
} }
} }

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}