db&webapi, primitive session tokens

adds a temporary auth method to users which does not require a password
or similar. This is just for testing right now and assumes a self-hosted
no-threats environment.

Also adds a user_key state to keep track of authed users. These
currently *DO NOT EXPIRE* which is pretty bad haha. The entire auth
system will be redone.
This commit is contained in:
Julia Lange 2026-03-05 10:15:16 -08:00
parent 949a984d0c
commit b089f62bcd
Signed by: Julia
SSH key fingerprint: SHA256:5DJcfxa5/fKCYn57dcabJa2vN2e6eT0pBerYi5SUbto
5 changed files with 86 additions and 3 deletions

View file

@ -1,24 +1,42 @@
use std::collections::HashMap;
use koucha::{
AdapterBuilder,
Adapter,
Adapter, AdapterBuilder, db::{User, UserKey as DbUserKey}
};
use crate::types::UserKey;
mod routes;
mod types;
#[derive(Clone)]
struct AppState {
pub adapter: Adapter,
// TODO: Set up UserKey expirations
auth_map: HashMap<UserKey, DbUserKey>,
}
impl AppState {
fn create_user_key(&mut self, db_user_key: DbUserKey) -> UserKey {
let key = UserKey::new();
self.auth_map.insert(key.clone(), db_user_key);
key
}
#[allow(dead_code)]
fn user_from_key(&self, key: &UserKey) -> Option<DbUserKey> {
self.auth_map.get(key).map(DbUserKey::clone)
}
}
#[tokio::main]
async fn main() {
let db_url = std::env::var("DATABASE_URL").unwrap();
let adapter = AdapterBuilder::new()
.database_url(&db_url)
.create().await.unwrap();
let auth_map = HashMap::new();
let app = routes::router().with_state(AppState { adapter });
let app = routes::router().with_state(AppState { adapter, auth_map });
let listener = tokio::net::TcpListener::bind("0.0.0.0:4142").await.unwrap();
axum::serve(listener, app).await.unwrap();

View file

@ -0,0 +1,41 @@
use axum::{Json, extract::State};
use koucha::db::User as DbUser;
use reqwest::StatusCode;
use serde::{Serialize, Deserialize};
use crate::{
AppState, routes::{ApiError, ApiResult, ApiResponse}, types::UserKey
};
#[derive(Deserialize)]
pub struct Input {
pub user_name: String,
}
#[derive(Serialize, Deserialize)]
pub struct Output {
user_key: UserKey,
}
pub async fn handler(
State(mut state): State<AppState>,
Json(body): Json<Input>,
) -> ApiResult<Output> {
let dbuser = DbUser::temporary_auth(
state.adapter.get_pool(),
&body.user_name
).await.map_err(|_e| {
// TODO: Logging
ApiError {
status: StatusCode::INTERNAL_SERVER_ERROR,
error: "InternalError",
message: String::from(
"Error authentiating user ".to_owned() + &body.user_name
),
}
})?;
let key = state.create_user_key(dbuser.key());
Ok(ApiResponse(StatusCode::OK, Output { user_key: key }))
}

View file

@ -11,6 +11,7 @@ use axum::{
};
use reqwest::StatusCode;
mod create_session;
mod get_users;
mod new_user;
@ -28,6 +29,7 @@ pub struct ApiError {
pub fn router() -> Router<AppState> {
Router::new()
.route("/create_session", post(create_session::handler))
.route("/get_users", get(get_users::handler))
.route("/new_user", post(new_user::handler))
}

View file

@ -1,6 +1,17 @@
use chrono::Utc;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
pub struct User {
pub name: String,
}
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct UserKey(String);
impl UserKey {
pub fn new() -> Self {
UserKey(
Utc::now().to_rfc3339()
)
}
}

View file

@ -30,6 +30,17 @@ impl User {
pub fn key(&self) -> UserKey { self.key }
pub fn name(&self) -> &str { &self.name }
pub async fn temporary_auth(
pool: &AdapterPool,
name: &str,
) -> Result<Self> {
sqlx::query_as!(
UnparsedUser,
"SELECT id as `id!`, name FROM users WHERE name = ?",
name
).fetch_one(&pool.0).await?.parse()
}
pub async fn get(pool: &AdapterPool, key: UserKey) -> Result<Self> {
sqlx::query_as!(
UnparsedUser,