add initial endpoints
This commit is contained in:
parent
97d089c284
commit
3025f1380c
19 changed files with 300 additions and 9 deletions
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
8
identity/src/app_state.rs
Normal file
8
identity/src/app_state.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use sqlx::{Pool, Postgres};
|
||||
|
||||
use crate::config::Config;
|
||||
|
||||
pub struct AppState {
|
||||
pub pool: Pool<Postgres>,
|
||||
pub config: Config,
|
||||
}
|
|
@ -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?;
|
||||
|
|
43
identity/src/http/account/create_user.rs
Normal file
43
identity/src/http/account/create_user.rs
Normal 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,
|
||||
}
|
7
identity/src/http/account/mod.rs
Normal file
7
identity/src/http/account/mod.rs
Normal 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
22
identity/src/http/mod.rs
Normal 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
31
identity/src/http/node.rs
Normal 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,
|
||||
}
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
25
identity/src/model/instance.rs
Normal file
25
identity/src/model/instance.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,2 +1,3 @@
|
|||
pub mod account;
|
||||
pub mod chat_instance;
|
||||
pub mod instance;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue