feat: Basic user creation form

This commit is contained in:
selfhoster selfhoster 2026-09-19 11:13:21 +02:00
commit e28c584b26
5 changed files with 54 additions and 5 deletions

View file

@ -67,12 +67,11 @@ pub trait DatabaseInterface: Clone + Send + Sync + 'static {
&mut self, &mut self,
user: User, user: User,
) -> Result<Result<(), UserCreationError>, BoxedError>; ) -> Result<Result<(), UserCreationError>, BoxedError>;
#[expect(unused)] fn try_create_user(
async fn try_create_user(
&mut self, &mut self,
new_user: User, new_user: User,
current_user: &User, current_user: &User,
) -> Result<Result<(), UserCreationError>, BoxedError>; ) -> impl Future<Output = Result<Result<(), UserCreationError>, BoxedError>> + Send;
fn list_all_domains(&self) -> impl Future<Output = Result<Vec<Domain>, BoxedError>> + Send; fn list_all_domains(&self) -> impl Future<Output = Result<Vec<Domain>, BoxedError>> + Send;
#[expect(unused)] #[expect(unused)]

View file

@ -91,7 +91,6 @@ impl DatabaseInterface for MemoryDatabase {
// //
// TODO: for now we don't allow creating service users manually // TODO: for now we don't allow creating service users manually
// so we assume there's a domain provided // so we assume there's a domain provided
// TODO: restrict user creation on non-declared domains
let Some(new_user_domain) = &new_user.domain else { let Some(new_user_domain) = &new_user.domain else {
return Ok(Err(UserCreationError::Permissions)); return Ok(Err(UserCreationError::Permissions));
}; };

View file

@ -19,6 +19,7 @@ mod login;
mod logout; mod logout;
mod session; mod session;
use session::HttpSessionManager; use session::HttpSessionManager;
mod user;
impl AxumListener for Listener { impl AxumListener for Listener {
type Io = AbstractStreamKind; type Io = AbstractStreamKind;
@ -102,6 +103,7 @@ pub async fn http_listen<D: DatabaseInterface>(listener: Listener, db: Database<
.route("/logout", get(logout::logout)) .route("/logout", get(logout::logout))
.route("/domain/{domain}", get(domain::get_domain)) .route("/domain/{domain}", get(domain::get_domain))
.route("/domain", post(domain::create_domain)) .route("/domain", post(domain::create_domain))
.route("/user", post(user::create_user))
.with_state(HttpState::new(db)); .with_state(HttpState::new(db));
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();

49
src/http/user.rs Normal file
View file

@ -0,0 +1,49 @@
use axum::extract::{Form, State};
use axum::response::{IntoResponse, Redirect, Response};
use axum_extra::extract::cookie::CookieJar;
use serde::Deserialize;
use crate::db::{DatabaseInterface, Role, User};
use crate::http::HttpState;
use crate::http::login::login_page;
#[derive(Clone, Debug, Deserialize)]
pub struct UserCreationForm {
pub username: String,
pub domain: String,
pub password: String,
}
pub async fn create_user<D: DatabaseInterface>(
State(mut state): State<HttpState<D>>,
cookies: CookieJar,
Form(form): Form<UserCreationForm>,
) -> Response {
let Some(session) = state.sessions.get_session(&cookies) else {
return login_page(State(state), None).await.into_response();
};
// let domain = form.domain;
// let op = Operation::CreateUser(domain.clone());
// if !session.user.can_perform(&op) {
// return format!("Not authorized to create a new user on domain {domain}").into_response()
// }
let new_user = User {
mail: format!("{}@{}", form.username, form.domain),
username: form.username.clone(),
domain: Some(form.domain.clone()),
password: form.password,
role: Role::User,
};
match state.db.try_create_user(new_user, &session.user).await {
Ok(Ok(())) => Redirect::to(&format!("/domain/{}", form.domain)).into_response(),
Ok(Err(e)) => format!(
"Failed to create user {} on domain {}: {}",
form.username, form.domain, e
)
.into_response(),
Err(e) => format!("Database error: {e}").into_response(),
}
}

View file

@ -10,7 +10,7 @@
<div> <div>
<h2>Create user</h2> <h2>Create user</h2>
<form action="/user" method="POST"> <form action="/user" method="POST">
<input type="hidden" name="domainname" value="{{ domain.name }}"> <input type="hidden" name="domain" value="{{ domain.name }}">
<input type="text" name="username" placeholder="username"> <input type="text" name="username" placeholder="username">
<input type="password" name="password" placeholder="password"> <input type="password" name="password" placeholder="password">
<button type="submit" class="button is-info">Create</button> <button type="submit" class="button is-info">Create</button>