backend signup

This commit is contained in:
2025-10-29 15:09:16 +01:00
parent abe3fc777c
commit 1cc0aafc1a
21 changed files with 673 additions and 211 deletions
+70 -1
View File
@@ -18,7 +18,7 @@ use uuid::Uuid;
use crate::{
auth::{password, AuthenticatedUser},
error::{AppError, AppResult},
models::{NewRefreshToken, RefreshToken, Tenant, User, UserMembership},
models::{NewRefreshToken, NewUser, RefreshToken, Tenant, TenantStatus, User, UserMembership},
schema::{
refresh_tokens, tenants::dsl as tenant_dsl, user_memberships::dsl as memberships_dsl,
users::dsl,
@@ -38,6 +38,12 @@ pub struct LoginRequest {
pub preferred_tenant_slug: Option<String>,
}
#[derive(Deserialize)]
pub struct SignupRequest {
pub username: String,
pub password: String,
}
#[derive(Serialize)]
pub struct LoginResponse {
pub access_token: String,
@@ -68,6 +74,50 @@ pub struct TenantSelectionRequest {
pub tenant_id: Uuid,
}
pub async fn signup(
State(state): State<AppState>,
Json(payload): Json<SignupRequest>,
) -> AppResult<Response> {
let username = payload.username.trim();
let password = payload.password.trim();
if username.is_empty() || password.is_empty() {
return Err(AppError::bad_request(
"username and password must not be empty",
));
}
let mut conn = state.db_unscoped()?;
let exists: bool = dsl::users
.filter(dsl::username.eq(username))
.first::<User>(&mut conn)
.optional()?
.is_some();
if exists {
return Err(AppError::conflict("username already exists"));
}
let user_id = Uuid::new_v4();
let password_hash = password::hash_password(password)?;
insert_user(&mut conn, user_id, username, &password_hash)?;
let tenant_slug = username.to_lowercase();
let tenant = state.tenants.create_tenant(
&tenant_slug,
None,
None,
TenantStatus::Creating,
&[user_id],
Some(user_id),
)?;
let user: User = dsl::users.find(user_id).first(&mut conn)?;
issue_session(&state, &mut conn, &user, tenant.id)
}
pub async fn login(
State(state): State<AppState>,
Json(payload): Json<LoginRequest>,
@@ -181,6 +231,25 @@ pub async fn refresh(
issue_session(&state, &mut conn, &user, token.tenant_id)
}
fn insert_user(
conn: &mut PgConnection,
id: Uuid,
username: &str,
password_hash: &str,
) -> AppResult<()> {
let new_user = NewUser {
id,
username: username.to_string(),
password_hash: password_hash.to_string(),
};
diesel::insert_into(dsl::users)
.values(&new_user)
.execute(conn)
.map(|_| ())
.map_err(AppError::from)
}
pub async fn select_tenant(
State(state): State<AppState>,
TypedHeader(Authorization(bearer)): TypedHeader<Authorization<Bearer>>,
+1
View File
@@ -54,6 +54,7 @@ pub fn create_router(state: AppState) -> Router<()> {
};
let auth_routes = Router::new()
.route("/signup", post(auth::signup))
.route("/login", post(auth::login))
.route("/refresh", post(auth::refresh))
.route("/logout", post(auth::logout))