org work
This commit is contained in:
parent
d00df353d8
commit
bba39afc6b
|
@ -4,6 +4,7 @@
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<sourceFolder url="file://$MODULE_DIR$/tfclient/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/tfclient/src" isTestSource="false" />
|
||||||
<sourceFolder url="file://$MODULE_DIR$/trifid-api/src" isTestSource="false" />
|
<sourceFolder url="file://$MODULE_DIR$/trifid-api/src" isTestSource="false" />
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/trifid-pki/src" isTestSource="false" />
|
||||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
|
|
|
@ -2205,11 +2205,14 @@ dependencies = [
|
||||||
name = "trifid-api"
|
name = "trifid-api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"aes-gcm",
|
||||||
"base64 0.21.0",
|
"base64 0.21.0",
|
||||||
"chrono",
|
"chrono",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
"hex",
|
||||||
"log",
|
"log",
|
||||||
"paste",
|
"paste",
|
||||||
|
"rand",
|
||||||
"rocket",
|
"rocket",
|
||||||
"serde",
|
"serde",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
|
@ -2221,6 +2224,10 @@ dependencies = [
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "trifid-pki"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "try-lock"
|
name = "try-lock"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"trifid-api",
|
"trifid-api",
|
||||||
"tfclient"
|
"tfclient",
|
||||||
|
"trifid-pki"
|
||||||
]
|
]
|
|
@ -0,0 +1,2 @@
|
||||||
|
[build]
|
||||||
|
rustc-wrapper = "sccache"
|
|
@ -19,4 +19,7 @@ totp-rs = { version = "4.2.0", features = ["qr", "otpauth", "gen_secret"]}
|
||||||
uuid = { version = "1.3.0", features = ["v4", "fast-rng", "macro-diagnostics"]}
|
uuid = { version = "1.3.0", features = ["v4", "fast-rng", "macro-diagnostics"]}
|
||||||
url = { version = "2.3.1", features = ["serde"] }
|
url = { version = "2.3.1", features = ["serde"] }
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
chrono = "0.4.23"
|
chrono = "0.4.23"
|
||||||
|
aes-gcm = "0.10.1"
|
||||||
|
hex = "0.4.3"
|
||||||
|
rand = "0.8.5"
|
|
@ -0,0 +1,7 @@
|
||||||
|
CREATE TABLE organization_authorized_users (
|
||||||
|
id SERIAL NOT NULL PRIMARY KEY,
|
||||||
|
user_id SERIAL NOT NULL REFERENCES users(id),
|
||||||
|
org_id SERIAL NOT NULL REFERENCES organizations(id)
|
||||||
|
);
|
||||||
|
CREATE INDEX idx_organization_authorized_users_user ON organization_authorized_users(user_id);
|
||||||
|
CREATE INDEX idx_organization_authorized_users_org ON organization_authorized_users(org_id);
|
|
@ -0,0 +1,21 @@
|
||||||
|
use std::error::Error;
|
||||||
|
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
|
||||||
|
use aes_gcm::aead::{Aead, Payload};
|
||||||
|
use crate::config::TFConfig;
|
||||||
|
|
||||||
|
pub fn get_cipher_from_config(config: &TFConfig) -> Result<Aes256Gcm, Box<dyn Error>> {
|
||||||
|
let key_slice = hex::decode(&config.data_key)?;
|
||||||
|
Ok(Aes256Gcm::new_from_slice(&key_slice)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn encrypt_with_nonce(plaintext: &[u8], nonce: [u8; 12], cipher: &Aes256Gcm) -> Result<Vec<u8>, aes_gcm::Error> {
|
||||||
|
let nonce = Nonce::from_slice(&nonce);
|
||||||
|
let ciphertext = cipher.encrypt(nonce, plaintext)?;
|
||||||
|
Ok(ciphertext)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn decrypt_with_nonce(ciphertext: &[u8], nonce: [u8; 12], cipher: &Aes256Gcm) -> Result<Vec<u8>, aes_gcm::Error> {
|
||||||
|
let nonce = Nonce::from_slice(&nonce);
|
||||||
|
let plaintext = cipher.decrypt(nonce, Payload::from(ciphertext))?;
|
||||||
|
Ok(plaintext)
|
||||||
|
}
|
|
@ -19,6 +19,8 @@ pub mod config;
|
||||||
pub mod tokens;
|
pub mod tokens;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod crypto;
|
||||||
|
pub mod org;
|
||||||
|
|
||||||
static MIGRATOR: Migrator = sqlx::migrate!();
|
static MIGRATOR: Migrator = sqlx::migrate!();
|
||||||
|
|
||||||
|
@ -118,7 +120,13 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
crate::routes::v1::auth::check_session::options,
|
crate::routes::v1::auth::check_session::options,
|
||||||
crate::routes::v1::auth::check_session::options_auth,
|
crate::routes::v1::auth::check_session::options_auth,
|
||||||
crate::routes::v2::whoami::whoami_request,
|
crate::routes::v2::whoami::whoami_request,
|
||||||
crate::routes::v2::whoami::options
|
crate::routes::v2::whoami::options,
|
||||||
|
|
||||||
|
crate::routes::v1::organization::options,
|
||||||
|
crate::routes::v1::organization::orgidoptions,
|
||||||
|
crate::routes::v1::organization::orginfo_req,
|
||||||
|
crate::routes::v1::organization::orglist_req,
|
||||||
|
crate::routes::v1::organization::create_org
|
||||||
])
|
])
|
||||||
.register("/", catchers![
|
.register("/", catchers![
|
||||||
crate::routes::handler_400,
|
crate::routes::handler_400,
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
use std::error::Error;
|
||||||
|
use rocket::form::validate::Contains;
|
||||||
|
use sqlx::PgPool;
|
||||||
|
|
||||||
|
pub async fn get_org_by_owner_id(user: i32, db: &PgPool) -> Result<Option<i32>, Box<dyn Error>> {
|
||||||
|
Ok(match sqlx::query!("SELECT id FROM organizations WHERE owner = $1", user).fetch_optional(db).await? {
|
||||||
|
Some(r) => Some(r.id),
|
||||||
|
None => None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_orgs_by_assoc_id(user: i32, db: &PgPool) -> Result<Vec<i32>, Box<dyn Error>> {
|
||||||
|
let res: Vec<_> = sqlx::query!("SELECT org_id FROM organization_authorized_users WHERE user_id = $1", user).fetch_all(db).await?;
|
||||||
|
|
||||||
|
let mut ret = vec![];
|
||||||
|
|
||||||
|
for i in res {
|
||||||
|
ret.push(i.org_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_users_by_assoc_org(org: i32, db: &PgPool) -> Result<Vec<i32>, Box<dyn Error>> {
|
||||||
|
let res: Vec<_> = sqlx::query!("SELECT user_id FROM organization_authorized_users WHERE org_id = $1", org).fetch_all(db).await?;
|
||||||
|
|
||||||
|
let mut ret = vec![];
|
||||||
|
|
||||||
|
for i in res {
|
||||||
|
ret.push(i.org_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_associated_orgs(user: i32, db: &PgPool) -> Result<Vec<i32>, Box<dyn Error>> {
|
||||||
|
let mut assoc_orgs = vec![];
|
||||||
|
|
||||||
|
if let Some(owned_org) = get_org_by_owner_id(user, db).await? {
|
||||||
|
assoc_orgs.push(owned_org);
|
||||||
|
}
|
||||||
|
|
||||||
|
assoc_orgs.append(&mut get_orgs_by_assoc_id(user, db).await?);
|
||||||
|
|
||||||
|
Ok(assoc_orgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn user_has_org_assoc(user: i32, org: i32, db: &PgPool) -> Result<bool, Box<dyn Error>> {
|
||||||
|
Ok(get_associated_orgs(user, db).await?.contains(org))
|
||||||
|
}
|
|
@ -2,4 +2,5 @@ pub mod auth;
|
||||||
pub mod signup;
|
pub mod signup;
|
||||||
|
|
||||||
pub mod totp_authenticators;
|
pub mod totp_authenticators;
|
||||||
pub mod verify_totp_authenticator;
|
pub mod verify_totp_authenticator;
|
||||||
|
pub mod organization;
|
|
@ -0,0 +1,84 @@
|
||||||
|
use std::slice::from_raw_parts_mut;
|
||||||
|
use rocket::{get, post, options, State};
|
||||||
|
use rocket::http::{ContentType, Status};
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
use sqlx::PgPool;
|
||||||
|
use crate::auth::TOTPAuthenticatedUserInfo;
|
||||||
|
use crate::config::TFConfig;
|
||||||
|
use crate::org::{get_associated_orgs, get_org_by_owner_id, get_users_by_assoc_org, user_has_org_assoc};
|
||||||
|
|
||||||
|
#[options("/v1/orgs")]
|
||||||
|
pub fn options() -> &'static str {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
#[options("/v1/org/<_id>")]
|
||||||
|
pub fn orgidoptions(_id: i32) -> &'static str {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
pub struct OrglistStruct {
|
||||||
|
pub org_ids: Vec<i32>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[get("/v1/orgs")]
|
||||||
|
pub async fn orglist_req(user: TOTPAuthenticatedUserInfo, db: &State<PgPool>) -> Result<(ContentType, Json<OrglistStruct>), (Status, String)> {
|
||||||
|
// this endpoint lists the associated organizations this user has access to
|
||||||
|
let associated_orgs = match get_associated_orgs(user.user_id, db.inner()).await {
|
||||||
|
Ok(r) => r,
|
||||||
|
Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_DB_QUERY_FAILED", "an error occurred while running the database query")))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((ContentType::JSON, Json(OrglistStruct {
|
||||||
|
org_ids: associated_orgs
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(crate = "rocket::serde")]
|
||||||
|
pub struct OrginfoStruct {
|
||||||
|
pub org_id: i32,
|
||||||
|
pub owner_id: i32,
|
||||||
|
pub ca_crt: String,
|
||||||
|
pub authorized_users: Vec<i32>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/v1/org/<orgid>")]
|
||||||
|
pub async fn orginfo_req(orgid: i32, user: TOTPAuthenticatedUserInfo, db: &State<PgPool>) -> Result<(ContentType, Json<OrginfoStruct>), (Status, String)> {
|
||||||
|
if !user_has_org_assoc(orgid, user.user_id, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))? {
|
||||||
|
return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_MISSING_ORG_AUTHORIZATION", "this user does not have permission to access this org")));
|
||||||
|
}
|
||||||
|
|
||||||
|
let org = sqlx::query!("SELECT id, owner, ca_crt FROM organizations WHERE id = $1", orgid).fetch_one(orgid).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?;
|
||||||
|
let authorized_users = get_users_by_assoc_org(orgid, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?;
|
||||||
|
|
||||||
|
Ok((ContentType::JSON, Json(
|
||||||
|
OrginfoStruct {
|
||||||
|
org_id: orgid,
|
||||||
|
owner_id: org.owner,
|
||||||
|
ca_crt: org.ca_crt,
|
||||||
|
authorized_users,
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/v1/org")]
|
||||||
|
pub async fn create_org(user: TOTPAuthenticatedUserInfo, db: &State<PgPool>, config: &State<TFConfig>) -> Result<(ContentType, Json<OrginfoStruct>), (Status, String)> {
|
||||||
|
if get_org_by_owner_id(user.user_id, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?.is_some() {
|
||||||
|
return Err((Status::Conflict, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_USER_OWNS_ORG", "a user can only own one organization at a time")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to generate a keypair and CA certificate for this new org
|
||||||
|
|
||||||
|
Ok((ContentType::JSON, Json(
|
||||||
|
OrginfoStruct {
|
||||||
|
org_id: orgid,
|
||||||
|
owner_id: org.owner,
|
||||||
|
ca_crt: org.ca_crt,
|
||||||
|
authorized_users,
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
[package]
|
||||||
|
name = "trifid-pki"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
description = "A rust implementation of the Nebula PKI system"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
|
@ -0,0 +1,14 @@
|
||||||
|
pub fn add(left: usize, right: usize) -> usize {
|
||||||
|
left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let result = add(2, 2);
|
||||||
|
assert_eq!(result, 4);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue