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" }
|
async-trait = { version = "0.1.83" }
|
||||||
authdb = { path = "../../authdb/" }
|
authdb = { path = "../../authdb/" }
|
||||||
axum = { version = "0.7.9" }
|
axum = { version = "0.7.9" }
|
||||||
axum-test = "16.4.1"
|
|
||||||
futures = { version = "0.3.31" }
|
futures = { version = "0.3.31" }
|
||||||
include_dir = { version = "0.7.4" }
|
include_dir = { version = "0.7.4" }
|
||||||
lazy_static = { version = "1.5.0" }
|
lazy_static = { version = "1.5.0" }
|
||||||
@ -31,3 +30,4 @@ uuid = { version = "1.11.0", features = ["v4"] }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
cool_asserts = "2.0.3"
|
cool_asserts = "2.0.3"
|
||||||
|
axum-test = "16.4.1"
|
||||||
|
@ -192,7 +192,7 @@ impl Core {
|
|||||||
id: CharacterId,
|
id: CharacterId,
|
||||||
) -> ResultExt<Option<serde_json::Value>, AppError, FatalError> {
|
) -> ResultExt<Option<serde_json::Value>, AppError, FatalError> {
|
||||||
let mut state = self.0.write().await;
|
let mut state = self.0.write().await;
|
||||||
let cr = state.db.character(id).await;
|
let cr = state.db.character(&id).await;
|
||||||
match cr {
|
match cr {
|
||||||
Ok(Some(row)) => ok(Some(row.data)),
|
Ok(Some(row)) => ok(Some(row.data)),
|
||||||
Ok(None) => ok(None),
|
Ok(None) => ok(None),
|
||||||
@ -258,13 +258,22 @@ impl Core {
|
|||||||
let state = self.0.write().await;
|
let state = self.0.write().await;
|
||||||
match state.db.user_by_username(username).await {
|
match state.db.user_by_username(username).await {
|
||||||
Ok(Some(row)) if (row.password == password) => {
|
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(session_id)
|
||||||
}
|
}
|
||||||
Ok(_) => error(AppError::AuthFailed),
|
Ok(_) => error(AppError::AuthFailed),
|
||||||
Err(err) => fatal(err),
|
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)]
|
#[cfg(test)]
|
||||||
@ -382,7 +391,7 @@ mod test {
|
|||||||
match core.auth("admin", "aoeu").await {
|
match core.auth("admin", "aoeu").await {
|
||||||
ResultExt::Ok(session_id) => {
|
ResultExt::Ok(session_id) => {
|
||||||
let st = core.0.read().await;
|
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(Some(user_row)) => assert_eq!(user_row.name, "admin"),
|
||||||
Ok(None) => panic!("no matching user row for the session id"),
|
Ok(None) => panic!("no matching user row for the session id"),
|
||||||
Err(err) => panic!("{}", err),
|
Err(err) => panic!("{}", err),
|
||||||
|
@ -58,11 +58,11 @@ pub trait Database: Send + Sync {
|
|||||||
|
|
||||||
async fn games(&mut self) -> Result<Vec<GameRow>, FatalError>;
|
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 {
|
pub struct DbConn {
|
||||||
@ -144,16 +144,16 @@ impl Database for DbConn {
|
|||||||
send_request!(self, Request::Games, DatabaseResponse::Games(lst) => Ok(lst))
|
send_request!(self, Request::Games, DatabaseResponse::Games(lst) => Ok(lst))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn character(&mut self, id: CharacterId) -> Result<Option<CharsheetRow>, FatalError> {
|
async fn character(&mut self, id: &CharacterId) -> Result<Option<CharsheetRow>, FatalError> {
|
||||||
send_request!(self, Request::Charsheet(id), DatabaseResponse::Charsheet(row) => Ok(row))
|
send_request!(self, Request::Charsheet(id.to_owned()), DatabaseResponse::Charsheet(row) => Ok(row))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn session(&self, id: SessionId) -> Result<Option<UserRow>, FatalError> {
|
async fn session(&self, id: &SessionId) -> Result<Option<UserRow>, FatalError> {
|
||||||
send_request!(self, Request::Session(id), DatabaseResponse::Session(row) => Ok(row))
|
send_request!(self, Request::Session(id.to_owned()), DatabaseResponse::Session(row) => Ok(row))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_session(&self, id: UserId) -> Result<SessionId, FatalError> {
|
async fn create_session(&self, id: &UserId) -> Result<SessionId, FatalError> {
|
||||||
send_request!(self, Request::CreateSession(id), DatabaseResponse::CreateSession(session_id) => Ok(session_id))
|
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 memory_db: Option<PathBuf> = None;
|
||||||
let mut conn = DbConn::new(memory_db);
|
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 rusqlite::types::{FromSql, FromSqlResult, ValueRef};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
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)]
|
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||||
pub struct GameId(String);
|
pub struct GameId(String);
|
||||||
|
|
||||||
|
@ -1,18 +1,44 @@
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
|
use axum::{
|
||||||
|
http::{HeaderMap, StatusCode},
|
||||||
|
Json,
|
||||||
|
};
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use result_extended::{error, ok, return_error, ResultExt};
|
use result_extended::{error, ok, return_error, ResultExt};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use typeshare::typeshare;
|
use typeshare::typeshare;
|
||||||
use axum::{http::StatusCode, Json};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
asset_db::AssetId,
|
asset_db::AssetId,
|
||||||
core::Core,
|
core::Core,
|
||||||
database::{CharacterId, UserId, SessionId},
|
database::{CharacterId, SessionId, UserId},
|
||||||
types::{AppError, FatalError},
|
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)]
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct HealthCheck {
|
pub struct HealthCheck {
|
||||||
pub ok: bool,
|
pub ok: bool,
|
||||||
@ -36,7 +62,10 @@ pub struct AuthRequest {
|
|||||||
pub password: String,
|
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;
|
let Json(AuthRequest { username, password }) = req;
|
||||||
match core.auth(&username, &password).await {
|
match core.auth(&username, &password).await {
|
||||||
ResultExt::Ok(session_id) => (StatusCode::OK, Json(Some(session_id))),
|
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(
|
pub async fn handle_auth(
|
||||||
auth_ctx: &AuthDB,
|
auth_ctx: &AuthDB,
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
|
use std::fmt;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
|
http::{HeaderMap, StatusCode},
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
core::Core,
|
core::Core,
|
||||||
handlers::{check_password, healthcheck, AuthRequest},
|
handlers::{check_password, get_user, healthcheck, AuthRequest},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn routes(core: Core) -> Router {
|
pub fn routes(core: Core) -> Router {
|
||||||
@ -24,6 +27,14 @@ pub fn routes(core: Core) -> Router {
|
|||||||
move |req: Json<AuthRequest>| check_password(core, req)
|
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)]
|
#[cfg(test)]
|
||||||
@ -40,6 +51,7 @@ mod test {
|
|||||||
asset_db::FsAssets,
|
asset_db::FsAssets,
|
||||||
core::Core,
|
core::Core,
|
||||||
database::{Database, DbConn, SessionId, UserId},
|
database::{Database, DbConn, SessionId, UserId},
|
||||||
|
handlers::UserProfile,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn setup_without_admin() -> (Core, TestServer) {
|
fn setup_without_admin() -> (Core, TestServer) {
|
||||||
@ -120,6 +132,35 @@ mod test {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn it_returns_user_profile() {
|
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!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use typeshare::typeshare;
|
use typeshare::typeshare;
|
||||||
|
|
||||||
use crate::{asset_db::AssetId, database::UserRow};
|
use crate::{asset_db::AssetId, database::{UserId, UserRow}};
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum FatalError {
|
pub enum FatalError {
|
||||||
@ -30,6 +30,9 @@ impl result_extended::FatalError for FatalError {}
|
|||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum AppError {
|
pub enum AppError {
|
||||||
|
#[error("invalid request")]
|
||||||
|
BadRequest,
|
||||||
|
|
||||||
#[error("something wasn't found {0}")]
|
#[error("something wasn't found {0}")]
|
||||||
NotFound(String),
|
NotFound(String),
|
||||||
|
|
||||||
@ -62,7 +65,7 @@ pub struct Rgb {
|
|||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[typeshare]
|
#[typeshare]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id: String,
|
pub id: UserId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
pub admin: bool,
|
pub admin: bool,
|
||||||
@ -72,7 +75,7 @@ pub struct User {
|
|||||||
impl From<UserRow> for User {
|
impl From<UserRow> for User {
|
||||||
fn from(row: UserRow) -> Self {
|
fn from(row: UserRow) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id: row.id.as_str().to_owned(),
|
id: row.id,
|
||||||
name: row.name.to_owned(),
|
name: row.name.to_owned(),
|
||||||
password: row.password.to_owned(),
|
password: row.password.to_owned(),
|
||||||
admin: row.admin,
|
admin: row.admin,
|
||||||
|
Loading…
Reference in New Issue
Block a user