feat: add admin user initialization endpoint with request handling
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
pub mod init_admin;
|
||||
pub mod login;
|
||||
|
||||
use std::sync::Arc;
|
||||
@@ -11,6 +12,7 @@ use crate::routes::AppState;
|
||||
|
||||
pub fn get_basic_auth_router(state: Arc<AppState>) -> Router {
|
||||
Router::new()
|
||||
.route("/login", post(login::login))
|
||||
.route("/auth/login", post(login::login))
|
||||
.route("/auth/init_admin", post(init_admin::init_admin))
|
||||
.with_state(state)
|
||||
}
|
||||
|
||||
143
apps/api/src/routes/api/auth/init_admin.rs
Normal file
143
apps/api/src/routes/api/auth/init_admin.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
Json,
|
||||
extract::State,
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use database::generated::entities::user;
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, TransactionTrait};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, from_value};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use crate::{
|
||||
helpers::constants::ADMIN_INIT_SECRET_KEY,
|
||||
routes::{AppState, api::openapi::tag::AUTH_TAG},
|
||||
services::auth::user::NewUser,
|
||||
};
|
||||
|
||||
/// Login request payload
|
||||
#[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct AdminInitRequest {
|
||||
username: String,
|
||||
password: String,
|
||||
// The secret key required to initialize the admin user
|
||||
setup_secret: String,
|
||||
}
|
||||
|
||||
/// Initializes the admin user
|
||||
///
|
||||
/// Initializes the admin user if no admin user exists and the correct setup secret is provided.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/auth/init_admin",
|
||||
request_body = AdminInitRequest,
|
||||
responses(
|
||||
(status = 200, description = "Admin user initialized successfully"),
|
||||
(status = 400, description = "Invalid request payload"),
|
||||
(status = 401, description = "Unauthorized: Admin user already exists or invalid setup secret"),
|
||||
(status = 500, description = "Internal server error"),
|
||||
),
|
||||
tag = AUTH_TAG,
|
||||
)]
|
||||
pub async fn init_admin(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(payload): Json<Value>,
|
||||
) -> Response {
|
||||
if user::Entity::find()
|
||||
.filter(user::Column::IsAdmin.eq(true))
|
||||
.filter(user::Column::IsActive.eq(true))
|
||||
.one(state.database_connection.as_ref())
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failed to query for existing admin user: {}", err);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})
|
||||
.unwrap_or(None)
|
||||
.is_some()
|
||||
{
|
||||
warn!("Admin user already exists. Skipping admin initialization.");
|
||||
return (StatusCode::UNAUTHORIZED).into_response();
|
||||
}
|
||||
|
||||
let init_request: AdminInitRequest = match from_value(payload) {
|
||||
Ok(req) => req,
|
||||
Err(e) => {
|
||||
warn!("Invalid login request: {}", e);
|
||||
return (StatusCode::BAD_REQUEST).into_response();
|
||||
}
|
||||
};
|
||||
|
||||
let admin_secret = match state
|
||||
.service
|
||||
.settings
|
||||
.get_setting(ADMIN_INIT_SECRET_KEY)
|
||||
.await
|
||||
{
|
||||
Ok(secret) => secret,
|
||||
Err(e) => {
|
||||
error!(
|
||||
"Failed to retrieve admin initialization secret. Invalid internal state?: {}",
|
||||
e
|
||||
);
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR).into_response();
|
||||
}
|
||||
};
|
||||
|
||||
if init_request.setup_secret != admin_secret {
|
||||
info!("{},{}", init_request.setup_secret, admin_secret);
|
||||
warn!("Invalid admin initialization secret provided.");
|
||||
return (StatusCode::UNAUTHORIZED).into_response();
|
||||
}
|
||||
|
||||
let mut tx = match state.database_connection.begin().await {
|
||||
Ok(tx) => tx,
|
||||
Err(e) => {
|
||||
error!("Failed to start transaction: {}", e);
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR).into_response();
|
||||
}
|
||||
};
|
||||
|
||||
let user = match state
|
||||
.service
|
||||
.user
|
||||
.create_user(
|
||||
NewUser {
|
||||
username: init_request.username,
|
||||
is_admin: true,
|
||||
},
|
||||
Some(&mut tx),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(user) => user,
|
||||
Err(e) => {
|
||||
error!("Failed to initialize admin user: {}", e);
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR).into_response();
|
||||
}
|
||||
};
|
||||
|
||||
debug!("Created admin user with ID: {}", user.id);
|
||||
match state
|
||||
.service
|
||||
.auth_state
|
||||
.strategy
|
||||
.password
|
||||
.create_identity(user.id, &init_request.password, Some(&mut tx))
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
error!("Failed to create admin user identity: {}", e);
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR).into_response();
|
||||
}
|
||||
};
|
||||
|
||||
tx.commit().await.unwrap_or_else(|e| {
|
||||
error!("Failed to commit transaction: {}", e);
|
||||
});
|
||||
|
||||
(StatusCode::OK).into_response()
|
||||
}
|
||||
@@ -8,11 +8,15 @@ pub mod tag {
|
||||
#[openapi(
|
||||
paths(
|
||||
crate::routes::api::health::info::get_health_info,
|
||||
crate::routes::api::auth::login::login
|
||||
// Authentication paths
|
||||
crate::routes::api::auth::login::login,
|
||||
crate::routes::api::auth::init_admin::init_admin,
|
||||
),
|
||||
components(
|
||||
schemas(crate::routes::api::health::info::HealthInfo), // Register any schemas used in your paths
|
||||
schemas(crate::routes::api::auth::login::LoginRequest)
|
||||
schemas(crate::routes::api::health::info::HealthInfo),
|
||||
// Authentication schemas
|
||||
schemas(crate::routes::api::auth::login::LoginRequest),
|
||||
schemas(crate::routes::api::auth::init_admin::AdminInitRequest),
|
||||
),
|
||||
tags(
|
||||
(name = tag::HEALTH_TAG, description = "Health information API"),
|
||||
|
||||
Reference in New Issue
Block a user