backend signup
This commit is contained in:
@@ -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>>,
|
||||
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user