init
This commit is contained in:
commit
00eca2801f
18 changed files with 2837 additions and 0 deletions
21
identity/Cargo.toml
Normal file
21
identity/Cargo.toml
Normal 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
5
identity/build.rs
Normal 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");
|
||||
}
|
38
identity/migrations/20240115015514_init.sql
Normal file
38
identity/migrations/20240115015514_init.sql
Normal 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
46
identity/src/db/mod.rs
Normal 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
34
identity/src/main.rs
Normal 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(())
|
||||
}
|
18
identity/src/model/account.rs
Normal file
18
identity/src/model/account.rs
Normal 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,
|
||||
}
|
16
identity/src/model/chat_instance.rs
Normal file
16
identity/src/model/chat_instance.rs
Normal 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,
|
||||
}
|
2
identity/src/model/mod.rs
Normal file
2
identity/src/model/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod account;
|
||||
pub mod chat_instance;
|
Loading…
Add table
Add a link
Reference in a new issue