This commit is contained in:
sam 2024-01-15 16:39:31 +01:00
commit 00eca2801f
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
18 changed files with 2837 additions and 0 deletions

21
identity/Cargo.toml Normal file
View file

@ -0,0 +1,21 @@
[package]
name = "identity"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
foxchat = { path = "../foxchat" }
axum = { version = "0.7.4", features = ["macros", "query", "tracing", "ws"] }
clap = { version = "4.4.16", features = ["env", "derive"] }
dotenvy = "0.15.7"
sqlx = { version = "0.7.3", features = ["runtime-tokio", "tls-rustls", "postgres", "migrate", "uuid", "chrono", "json"] }
uuid = { version = "1.6.1", features = ["v7"] }
serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.111"
ulid = { version = "1.1.0", features = ["serde"] }
eyre = "0.6.11"
color-eyre = "0.6.2"
rsa = { version = "0.9.6", features = ["serde"] }
rand = "0.8.5"

5
identity/build.rs Normal file
View file

@ -0,0 +1,5 @@
// generated by `sqlx migrate build-script`
fn main() {
// trigger recompilation when a new migration is added
println!("cargo:rerun-if-changed=migrations");
}

View file

@ -0,0 +1,38 @@
create type account_role as enum ('user', 'admin');
create table accounts (
id text primary key,
username text not null,
email text not null,
password text not null, -- Hashed + salted password
role account_role not null default 'user',
avatar text null -- Avatar hash
);
create unique index users_username_idx on accounts (lower(username));
create type instance_status as enum ('active', 'suspended');
create table chat_instances (
id text primary key,
domain text not null unique,
public_key text not null,
status instance_status not null default 'active',
reason text
);
create table chat_instance_accounts (
account_id text not null references accounts (id) on delete cascade,
chat_instance_id text not null references chat_instances (id) on delete cascade,
primary key (account_id, chat_instance_id)
);
create table instance (
id integer not null primary key default 1,
public_key text not null,
private_key text not null,
constraint singleton check (id = 1)
);

46
identity/src/db/mod.rs Normal file
View file

@ -0,0 +1,46 @@
use std::time::Duration;
use eyre::{OptionExt, Result};
use rsa::{RsaPrivateKey, RsaPublicKey};
use rsa::pkcs1::LineEnding;
use rsa::pkcs8::{EncodePrivateKey, EncodePublicKey};
use sqlx::{Pool, Postgres};
use sqlx::postgres::PgPoolOptions;
pub async fn init_db(dsn: &str) -> Result<Pool<Postgres>> {
let pool = PgPoolOptions::new()
.acquire_timeout(Duration::from_secs(2)) // Fail fast and don't hang
.max_connections(100)
.connect(dsn)
.await?;
init_instance(&pool)?;
Ok(pool)
}
const PRIVATE_KEY_BITS: usize = 2048;
async fn init_instance(pool: &Pool<Postgres>) -> Result<()> {
let mut tx = pool.begin().await?;
// Check if we already have an instance configuration
let row = sqlx::query!("select exists(select * from instance)").fetch_one(&mut *tx).await?;
if row.exists.ok_or_eyre("exists was null")? {
return Ok(());
}
// Generate public/private key
let mut rng = rand::thread_rng();
let priv_key = RsaPrivateKey::new(&mut rng, PRIVATE_KEY_BITS)?;
let pub_key = RsaPublicKey::from(&priv_key);
let priv_key_string = priv_key.to_pkcs8_pem(LineEnding::default())?;
let pub_key_string = pub_key.to_public_key_pem(LineEnding::default())?;
sqlx::query!("insert into instance (public_key, private_key) values ($1, $2)",
priv_key_string.to_string(), pub_key_string).execute(&mut *tx).await?;
tx.commit().await?;
Ok(())
}

34
identity/src/main.rs Normal file
View file

@ -0,0 +1,34 @@
mod model;
mod db;
use color_eyre::eyre::Result;
use clap::{Parser, Subcommand};
use ulid::Ulid;
#[derive(Debug, Parser)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
Serve,
Migrate,
}
fn main() -> Result<()> {
color_eyre::install()?;
let args = Cli::parse();
println!("{args:#?}");
println!("{}", match args.command {
Command::Serve => "serving!",
Command::Migrate => "migrating!"
});
println!("{}", Ulid::new());
Ok(())
}

View file

@ -0,0 +1,18 @@
use serde::{Deserialize, Serialize};
use ulid::Ulid;
pub struct Account {
pub id: Ulid,
pub username: String,
pub email: String,
pub password: String,
pub role: Role,
pub avatar: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, sqlx::Type)]
#[sqlx(type_name = "account_role", rename_all = "lowercase")]
pub enum Role {
User,
Admin,
}

View file

@ -0,0 +1,16 @@
use serde::{Deserialize, Serialize};
use ulid::Ulid;
pub struct ChatInstance {
pub id: Ulid,
pub domain: String,
pub status: InstanceStatus,
pub reason: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, sqlx::Type)]
#[sqlx(type_name = "instance_status", rename_all = "lowercase")]
pub enum InstanceStatus {
Active,
Suspended,
}

View file

@ -0,0 +1,2 @@
pub mod account;
pub mod chat_instance;