add initial endpoints

This commit is contained in:
sam 2024-01-16 17:16:39 +01:00
parent 97d089c284
commit 3025f1380c
Signed by: sam
GPG key ID: B4EF20DDE721CAA1
19 changed files with 300 additions and 9 deletions

View file

@ -17,9 +17,11 @@ 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"] }
rsa = { version = "0.9.6", features = ["serde", "sha2"] }
rand = "0.8.5"
toml = "0.8.8"
tokio = { version = "1.35.1", features = ["macros", "rt-multi-thread"] }
tracing-subscriber = "0.3.18"
tracing = "0.1.40"
tower-http = { version = "0.5.1", features = ["trace"] }
bcrypt = "0.15.0"

View file

@ -17,6 +17,7 @@ create type instance_status as enum ('active', 'suspended');
create table chat_instances (
id text primary key,
domain text not null unique,
base_url text not null,
public_key text not null,
status instance_status not null default 'active',
reason text

View file

@ -0,0 +1,8 @@
use sqlx::{Pool, Postgres};
use crate::config::Config;
pub struct AppState {
pub pool: Pool<Postgres>,
pub config: Config,
}

View file

@ -1,6 +1,5 @@
use eyre::{OptionExt, Result};
use rsa::pkcs1::LineEnding;
use rsa::pkcs8::{EncodePrivateKey, EncodePublicKey};
use rsa::pkcs1::{LineEnding, EncodeRsaPublicKey, EncodeRsaPrivateKey};
use rsa::{RsaPrivateKey, RsaPublicKey};
use sqlx::postgres::PgPoolOptions;
use sqlx::{Pool, Postgres};
@ -34,13 +33,13 @@ pub async fn init_instance(pool: &Pool<Postgres>) -> Result<()> {
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::LF)?;
let pub_key_string = pub_key.to_public_key_pem(LineEnding::LF)?;
let priv_key_string = priv_key.to_pkcs1_pem(LineEnding::LF)?;
let pub_key_string = pub_key.to_pkcs1_pem(LineEnding::LF)?;
sqlx::query!(
"insert into instance (public_key, private_key) values ($1, $2)",
pub_key_string,
priv_key_string.to_string(),
pub_key_string
)
.execute(&mut *tx)
.await?;

View file

@ -0,0 +1,43 @@
use std::sync::Arc;
use axum::{Extension, Json};
use eyre::{Context, Result};
use foxchat::http::ApiError;
use serde::{Deserialize, Serialize};
use ulid::Ulid;
use crate::app_state::AppState;
const PASSWORD_COST: u32 = 12;
pub async fn create_user(
Extension(state): Extension<Arc<AppState>>,
Json(data): Json<CreateUserRequest>,
) -> Result<Json<CreateUserResponse>, ApiError> {
let password_hash =
bcrypt::hash(data.password, PASSWORD_COST).wrap_err("failed to hash password")?;
let account = sqlx::query!(
"insert into accounts (id, username, email, password) values ($1, $2, $3, $4) returning id, username",
Ulid::new().to_string(), data.username, data.email, password_hash)
.fetch_one(&state.pool)
.await?;
Ok(Json(CreateUserResponse {
id: account.id,
username: account.username,
}))
}
#[derive(Deserialize)]
pub struct CreateUserRequest {
pub username: String,
pub email: String,
pub password: String,
}
#[derive(Serialize)]
pub struct CreateUserResponse {
pub id: String,
pub username: String,
}

View file

@ -0,0 +1,7 @@
mod create_user;
use axum::{routing::post, Router};
pub fn router() -> Router {
Router::new().route("/_fox/ident/accounts", post(create_user::create_user))
}

22
identity/src/http/mod.rs Normal file
View file

@ -0,0 +1,22 @@
mod node;
mod account;
use std::sync::Arc;
use axum::{routing::get, Extension, Router};
use sqlx::{Pool, Postgres};
use tower_http::trace::TraceLayer;
use crate::{app_state::AppState, config::Config};
pub fn new(pool: Pool<Postgres>, config: Config) -> Router {
let app_state = Arc::new(AppState { pool, config });
let app = Router::new()
.merge(account::router())
.route("/_fox/ident/node", get(node::get_node))
.layer(TraceLayer::new_for_http())
.layer(Extension(app_state));
return app;
}

31
identity/src/http/node.rs Normal file
View file

@ -0,0 +1,31 @@
use std::sync::Arc;
use axum::{Extension, Json};
use eyre::{Result, Context};
use foxchat::http::ApiError;
use rsa::pkcs1::EncodeRsaPublicKey;
use serde::Serialize;
use crate::{app_state::AppState, model::instance::Instance};
pub async fn get_node(Extension(state): Extension<Arc<AppState>>) -> Result<Json<NodeResponse>, ApiError> {
let instance = Instance::get(&state.pool).await?;
let public_key = instance
.public_key
.to_pkcs1_pem(rsa::pkcs8::LineEnding::LF)
.wrap_err("serializing public key")?;
Ok(Json(NodeResponse {
software: NODE_SOFTWARE,
public_key,
}))
}
const NODE_SOFTWARE: &str = "foxchat_ident";
#[derive(Serialize)]
pub struct NodeResponse {
pub software: &'static str,
pub public_key: String,
}

View file

@ -1,10 +1,15 @@
mod app_state;
mod config;
mod db;
mod http;
mod model;
use std::net::{Ipv4Addr, SocketAddrV4};
use crate::config::Config;
use clap::{Parser, Subcommand};
use color_eyre::eyre::Result;
use tokio::net::TcpListener;
use tracing::info;
#[derive(Debug, Parser)]
@ -59,5 +64,12 @@ async fn main_web(config: Config) -> Result<()> {
db::init_instance(&pool).await?;
info!("Initialized instance data!");
let port = config.port;
let app = http::new(pool, config);
let listener = TcpListener::bind(SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), port)).await?;
axum::serve(listener, app).await?;
Ok(())
}

View file

@ -4,6 +4,8 @@ use ulid::Ulid;
pub struct ChatInstance {
pub id: Ulid,
pub domain: String,
pub base_url: String,
pub public_key: String,
pub status: InstanceStatus,
pub reason: Option<String>,
}

View file

@ -0,0 +1,25 @@
use eyre::Result;
use sqlx::{Pool, Postgres};
use rsa::{RsaPrivateKey, RsaPublicKey, pkcs1::{DecodeRsaPublicKey, DecodeRsaPrivateKey}};
#[derive(Debug, Clone)]
pub struct Instance {
pub public_key: RsaPublicKey,
pub private_key: RsaPrivateKey,
}
impl Instance {
pub async fn get(pool: &Pool<Postgres>) -> Result<Self> {
let instance = sqlx::query!("SELECT * FROM instance WHERE id = 1")
.fetch_one(pool)
.await?;
let public_key = RsaPublicKey::from_pkcs1_pem(&instance.public_key)?;
let private_key = RsaPrivateKey::from_pkcs1_pem(&instance.private_key)?;
Ok(Self {
public_key,
private_key,
})
}
}

View file

@ -1,2 +1,3 @@
pub mod account;
pub mod chat_instance;
pub mod instance;