authenticate an endpoint for getting the user profile
This commit is contained in:
parent
a0f1a0b81c
commit
822dfe2a13
@ -10,7 +10,6 @@ async-std = { version = "1.13.0" }
|
||||
async-trait = { version = "0.1.83" }
|
||||
authdb = { path = "../../authdb/" }
|
||||
axum = { version = "0.7.9" }
|
||||
axum-test = "16.4.1"
|
||||
futures = { version = "0.3.31" }
|
||||
include_dir = { version = "0.7.4" }
|
||||
lazy_static = { version = "1.5.0" }
|
||||
@ -31,3 +30,4 @@ uuid = { version = "1.11.0", features = ["v4"] }
|
||||
|
||||
[dev-dependencies]
|
||||
cool_asserts = "2.0.3"
|
||||
axum-test = "16.4.1"
|
||||
|
@ -192,7 +192,7 @@ impl Core {
|
||||
id: CharacterId,
|
||||
) -> ResultExt<Option<serde_json::Value>, AppError, FatalError> {
|
||||
let mut state = self.0.write().await;
|
||||
let cr = state.db.character(id).await;
|
||||
let cr = state.db.character(&id).await;
|
||||
match cr {
|
||||
Ok(Some(row)) => ok(Some(row.data)),
|
||||
Ok(None) => ok(None),
|
||||
@ -258,13 +258,22 @@ impl Core {
|
||||
let state = self.0.write().await;
|
||||
match state.db.user_by_username(username).await {
|
||||
Ok(Some(row)) if (row.password == password) => {
|
||||
let session_id = state.db.create_session(row.id).await.unwrap();
|
||||
let session_id = state.db.create_session(&row.id).await.unwrap();
|
||||
ok(session_id)
|
||||
}
|
||||
Ok(_) => error(AppError::AuthFailed),
|
||||
Err(err) => fatal(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn session(&self, session_id: &SessionId) -> ResultExt<Option<User>, AppError, FatalError> {
|
||||
let state = self.0.read().await;
|
||||
match state.db.session(session_id).await {
|
||||
Ok(Some(user_row)) => ok(Some(User::from(user_row))),
|
||||
Ok(None) => ok(None),
|
||||
Err(fatal_error) => fatal(fatal_error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -382,7 +391,7 @@ mod test {
|
||||
match core.auth("admin", "aoeu").await {
|
||||
ResultExt::Ok(session_id) => {
|
||||
let st = core.0.read().await;
|
||||
match st.db.session(session_id).await {
|
||||
match st.db.session(&session_id).await {
|
||||
Ok(Some(user_row)) => assert_eq!(user_row.name, "admin"),
|
||||
Ok(None) => panic!("no matching user row for the session id"),
|
||||
Err(err) => panic!("{}", err),
|
||||
|
@ -58,11 +58,11 @@ pub trait Database: Send + Sync {
|
||||
|
||||
async fn games(&mut self) -> Result<Vec<GameRow>, FatalError>;
|
||||
|
||||
async fn character(&mut self, id: CharacterId) -> Result<Option<CharsheetRow>, FatalError>;
|
||||
async fn character(&mut self, id: &CharacterId) -> Result<Option<CharsheetRow>, FatalError>;
|
||||
|
||||
async fn session(&self, id: SessionId) -> Result<Option<UserRow>, FatalError>;
|
||||
async fn session(&self, id: &SessionId) -> Result<Option<UserRow>, FatalError>;
|
||||
|
||||
async fn create_session(&self, id: UserId) -> Result<SessionId, FatalError>;
|
||||
async fn create_session(&self, id: &UserId) -> Result<SessionId, FatalError>;
|
||||
}
|
||||
|
||||
pub struct DbConn {
|
||||
@ -144,16 +144,16 @@ impl Database for DbConn {
|
||||
send_request!(self, Request::Games, DatabaseResponse::Games(lst) => Ok(lst))
|
||||
}
|
||||
|
||||
async fn character(&mut self, id: CharacterId) -> Result<Option<CharsheetRow>, FatalError> {
|
||||
send_request!(self, Request::Charsheet(id), DatabaseResponse::Charsheet(row) => Ok(row))
|
||||
async fn character(&mut self, id: &CharacterId) -> Result<Option<CharsheetRow>, FatalError> {
|
||||
send_request!(self, Request::Charsheet(id.to_owned()), DatabaseResponse::Charsheet(row) => Ok(row))
|
||||
}
|
||||
|
||||
async fn session(&self, id: SessionId) -> Result<Option<UserRow>, FatalError> {
|
||||
send_request!(self, Request::Session(id), DatabaseResponse::Session(row) => Ok(row))
|
||||
async fn session(&self, id: &SessionId) -> Result<Option<UserRow>, FatalError> {
|
||||
send_request!(self, Request::Session(id.to_owned()), DatabaseResponse::Session(row) => Ok(row))
|
||||
}
|
||||
|
||||
async fn create_session(&self, id: UserId) -> Result<SessionId, FatalError> {
|
||||
send_request!(self, Request::CreateSession(id), DatabaseResponse::CreateSession(session_id) => Ok(session_id))
|
||||
async fn create_session(&self, id: &UserId) -> Result<SessionId, FatalError> {
|
||||
send_request!(self, Request::CreateSession(id.to_owned()), DatabaseResponse::CreateSession(session_id) => Ok(session_id))
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,6 +194,6 @@ mod test {
|
||||
let memory_db: Option<PathBuf> = None;
|
||||
let mut conn = DbConn::new(memory_db);
|
||||
|
||||
assert_matches!(conn.character(CharacterId::from("1")).await, Ok(None));
|
||||
assert_matches!(conn.character(&CharacterId::from("1")).await, Ok(None));
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::fmt;
|
||||
|
||||
use rusqlite::types::{FromSql, FromSqlResult, ValueRef};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
@ -72,6 +74,12 @@ impl FromSql for SessionId {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for SessionId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
f.write_str(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct GameId(String);
|
||||
|
||||
|
@ -1,18 +1,44 @@
|
||||
use std::future::Future;
|
||||
|
||||
use axum::{
|
||||
http::{HeaderMap, StatusCode},
|
||||
Json,
|
||||
};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use result_extended::{error, ok, return_error, ResultExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
use axum::{http::StatusCode, Json};
|
||||
|
||||
use crate::{
|
||||
asset_db::AssetId,
|
||||
core::Core,
|
||||
database::{CharacterId, UserId, SessionId},
|
||||
types::{AppError, FatalError},
|
||||
database::{CharacterId, SessionId, UserId},
|
||||
types::{AppError, FatalError, User},
|
||||
};
|
||||
|
||||
async fn check_session(
|
||||
core: &Core,
|
||||
headers: HeaderMap,
|
||||
) -> ResultExt<Option<User>, AppError, FatalError> {
|
||||
println!("headers: {:?}", headers);
|
||||
println!("auth_header: {:?}", headers.get("Authorization"));
|
||||
match headers.get("Authorization") {
|
||||
Some(token) => {
|
||||
match token
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.split(" ")
|
||||
.collect::<Vec<&str>>()
|
||||
.as_slice()
|
||||
{
|
||||
[_schema, token] => core.session(&SessionId::from(token.to_owned())).await,
|
||||
_ => error(AppError::BadRequest),
|
||||
}
|
||||
}
|
||||
None => ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||
pub struct HealthCheck {
|
||||
pub ok: bool,
|
||||
@ -36,7 +62,10 @@ pub struct AuthRequest {
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
pub async fn check_password(core: Core, req: Json<AuthRequest>) -> (StatusCode, Json<Option<SessionId>>) {
|
||||
pub async fn check_password(
|
||||
core: Core,
|
||||
req: Json<AuthRequest>,
|
||||
) -> (StatusCode, Json<Option<SessionId>>) {
|
||||
let Json(AuthRequest { username, password }) = req;
|
||||
match core.auth(&username, &password).await {
|
||||
ResultExt::Ok(session_id) => (StatusCode::OK, Json(Some(session_id))),
|
||||
@ -45,6 +74,24 @@ pub async fn check_password(core: Core, req: Json<AuthRequest>) -> (StatusCode,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[typeshare]
|
||||
pub struct UserProfile {
|
||||
pub userid: UserId,
|
||||
pub username: String,
|
||||
}
|
||||
|
||||
pub async fn get_user(core: Core, headers: HeaderMap) -> (StatusCode, Json<Option<UserProfile>>) {
|
||||
match check_session(&core, headers).await {
|
||||
ResultExt::Ok(Some(user)) => {
|
||||
(StatusCode::OK, Json(Some(UserProfile{userid: UserId::from(user.id), username: user.name} )))
|
||||
}
|
||||
ResultExt::Ok(None) => (StatusCode::UNAUTHORIZED, Json(None)),
|
||||
ResultExt::Err(_err) => (StatusCode::BAD_REQUEST, Json(None)),
|
||||
ResultExt::Fatal(err) => panic!("{}", err),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub async fn handle_auth(
|
||||
auth_ctx: &AuthDB,
|
||||
|
@ -1,11 +1,14 @@
|
||||
use std::fmt;
|
||||
|
||||
use axum::{
|
||||
http::{HeaderMap, StatusCode},
|
||||
routing::{get, post},
|
||||
Json, Router,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
core::Core,
|
||||
handlers::{check_password, healthcheck, AuthRequest},
|
||||
handlers::{check_password, get_user, healthcheck, AuthRequest},
|
||||
};
|
||||
|
||||
pub fn routes(core: Core) -> Router {
|
||||
@ -24,6 +27,14 @@ pub fn routes(core: Core) -> Router {
|
||||
move |req: Json<AuthRequest>| check_password(core, req)
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
// By default, just get the self user.
|
||||
"/api/v1/user",
|
||||
get({
|
||||
let core = core.clone();
|
||||
move |headers: HeaderMap| get_user(core, headers)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -40,6 +51,7 @@ mod test {
|
||||
asset_db::FsAssets,
|
||||
core::Core,
|
||||
database::{Database, DbConn, SessionId, UserId},
|
||||
handlers::UserProfile,
|
||||
};
|
||||
|
||||
fn setup_without_admin() -> (Core, TestServer) {
|
||||
@ -120,6 +132,35 @@ mod test {
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_returns_user_profile() {
|
||||
let (_core, server) = setup_admin_enabled().await;
|
||||
|
||||
let response = server.get("/api/v1/user").await;
|
||||
response.assert_status(StatusCode::UNAUTHORIZED);
|
||||
|
||||
let response = server
|
||||
.post("/api/v1/auth")
|
||||
.json(&AuthRequest {
|
||||
username: "admin".to_owned(),
|
||||
password: "aoeu".to_owned(),
|
||||
})
|
||||
.await;
|
||||
response.assert_status_ok();
|
||||
let session_id: Option<SessionId> = response.json();
|
||||
let session_id = session_id.unwrap();
|
||||
|
||||
let response = server
|
||||
.get("/api/v1/user")
|
||||
.add_header("Authorization", format!("Bearer {}", session_id))
|
||||
.await;
|
||||
response.assert_status_ok();
|
||||
let profile: Option<UserProfile> = response.json();
|
||||
let profile = profile.unwrap();
|
||||
assert_eq!(profile.userid, UserId::from("admin"));
|
||||
assert_eq!(profile.username, "admin");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn an_admin_can_create_a_user() {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use typeshare::typeshare;
|
||||
|
||||
use crate::{asset_db::AssetId, database::UserRow};
|
||||
use crate::{asset_db::AssetId, database::{UserId, UserRow}};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FatalError {
|
||||
@ -30,6 +30,9 @@ impl result_extended::FatalError for FatalError {}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum AppError {
|
||||
#[error("invalid request")]
|
||||
BadRequest,
|
||||
|
||||
#[error("something wasn't found {0}")]
|
||||
NotFound(String),
|
||||
|
||||
@ -62,7 +65,7 @@ pub struct Rgb {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[typeshare]
|
||||
pub struct User {
|
||||
pub id: String,
|
||||
pub id: UserId,
|
||||
pub name: String,
|
||||
pub password: String,
|
||||
pub admin: bool,
|
||||
@ -72,7 +75,7 @@ pub struct User {
|
||||
impl From<UserRow> for User {
|
||||
fn from(row: UserRow) -> Self {
|
||||
Self {
|
||||
id: row.id.as_str().to_owned(),
|
||||
id: row.id,
|
||||
name: row.name.to_owned(),
|
||||
password: row.password.to_owned(),
|
||||
admin: row.admin,
|
||||
|
Loading…
Reference in New Issue
Block a user