initial commit
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I4a6b498153eccd5407510dd541b7f4816a6a6964
This commit is contained in:
commit
6a73d11c4b
124 changed files with 34856 additions and 0 deletions
119
crates/pinakes-server/src/routes/auth.rs
Normal file
119
crates/pinakes-server/src/routes/auth.rs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
use axum::Json;
|
||||
use axum::extract::State;
|
||||
use axum::http::{HeaderMap, StatusCode};
|
||||
|
||||
use crate::dto::{LoginRequest, LoginResponse, UserInfoResponse};
|
||||
use crate::state::AppState;
|
||||
|
||||
pub async fn login(
|
||||
State(state): State<AppState>,
|
||||
Json(req): Json<LoginRequest>,
|
||||
) -> Result<Json<LoginResponse>, StatusCode> {
|
||||
// Limit input sizes to prevent DoS
|
||||
if req.username.len() > 255 || req.password.len() > 1024 {
|
||||
return Err(StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
let config = state.config.read().await;
|
||||
if !config.accounts.enabled {
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
let user = config
|
||||
.accounts
|
||||
.users
|
||||
.iter()
|
||||
.find(|u| u.username == req.username);
|
||||
|
||||
let user = match user {
|
||||
Some(u) => u,
|
||||
None => {
|
||||
tracing::warn!(username = %req.username, "login failed: unknown user");
|
||||
return Err(StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
};
|
||||
|
||||
// Verify password using argon2
|
||||
use argon2::password_hash::PasswordVerifier;
|
||||
let hash = &user.password_hash;
|
||||
let parsed_hash = argon2::password_hash::PasswordHash::new(hash)
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
let valid = argon2::Argon2::default()
|
||||
.verify_password(req.password.as_bytes(), &parsed_hash)
|
||||
.is_ok();
|
||||
if !valid {
|
||||
tracing::warn!(username = %req.username, "login failed: invalid password");
|
||||
return Err(StatusCode::UNAUTHORIZED);
|
||||
}
|
||||
|
||||
// Generate session token
|
||||
use rand::Rng;
|
||||
let token: String = rand::rng()
|
||||
.sample_iter(&rand::distr::Alphanumeric)
|
||||
.take(48)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
|
||||
let role = user.role;
|
||||
let username = user.username.clone();
|
||||
|
||||
// Store session
|
||||
{
|
||||
let mut sessions = state.sessions.write().await;
|
||||
sessions.insert(
|
||||
token.clone(),
|
||||
crate::state::SessionInfo {
|
||||
username: username.clone(),
|
||||
role,
|
||||
created_at: chrono::Utc::now(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
tracing::info!(username = %username, role = %role, "login successful");
|
||||
|
||||
Ok(Json(LoginResponse {
|
||||
token,
|
||||
username,
|
||||
role: role.to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub async fn logout(State(state): State<AppState>, headers: HeaderMap) -> StatusCode {
|
||||
if let Some(token) = extract_bearer_token(&headers) {
|
||||
let mut sessions = state.sessions.write().await;
|
||||
sessions.remove(token);
|
||||
}
|
||||
StatusCode::OK
|
||||
}
|
||||
|
||||
pub async fn me(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
) -> Result<Json<UserInfoResponse>, StatusCode> {
|
||||
let config = state.config.read().await;
|
||||
if !config.accounts.enabled {
|
||||
// When accounts are not enabled, return a default admin user
|
||||
return Ok(Json(UserInfoResponse {
|
||||
username: "admin".to_string(),
|
||||
role: "admin".to_string(),
|
||||
}));
|
||||
}
|
||||
drop(config);
|
||||
|
||||
let token = extract_bearer_token(&headers).ok_or(StatusCode::UNAUTHORIZED)?;
|
||||
let sessions = state.sessions.read().await;
|
||||
let session = sessions.get(token).ok_or(StatusCode::UNAUTHORIZED)?;
|
||||
|
||||
Ok(Json(UserInfoResponse {
|
||||
username: session.username.clone(),
|
||||
role: session.role.to_string(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn extract_bearer_token(headers: &HeaderMap) -> Option<&str> {
|
||||
headers
|
||||
.get("authorization")
|
||||
.and_then(|v| v.to_str().ok())
|
||||
.and_then(|s| s.strip_prefix("Bearer "))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue