Skip to content

Commit

Permalink
Introduce Session Deauthorization & Re-org routes
Browse files Browse the repository at this point in the history
  • Loading branch information
lucemans committed Jul 30, 2024
1 parent f7818bc commit 16c8e99
Show file tree
Hide file tree
Showing 10 changed files with 235 additions and 161 deletions.
8 changes: 8 additions & 0 deletions engine/src/auth/hash.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use hmac::{digest::InvalidLength, Hmac, Mac};
use sha2::Sha256;

pub fn hash_session(session_id: &str) -> Result<String, InvalidLength> {
let mut hash = Hmac::<Sha256>::new_from_slice(b"")?;
hash.update(session_id.as_bytes());
Ok(hex::encode(hash.finalize().into_bytes()))
}
8 changes: 2 additions & 6 deletions engine/src/auth/middleware.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use std::sync::Arc;

use hmac::{Hmac, Mac};
use poem::{web::Data, Error, FromRequest, Request, RequestBody, Result};
use reqwest::StatusCode;
use sha2::Sha256;

use crate::state::AppState;

use super::session::SessionState;
use super::{hash::hash_session, session::SessionState};

pub struct ActiveUser {
pub session: SessionState,
Expand All @@ -32,9 +30,7 @@ impl<'a> FromRequest<'a> for AuthToken {
match token {
Some(token) => {
// Hash the token
let mut hash = Hmac::<Sha256>::new_from_slice(b"").unwrap();
hash.update(token.as_bytes());
let hash = hex::encode(hash.finalize().into_bytes());
let hash = hash_session(&token).unwrap();

// Check if active session exists with token
let session = SessionState::try_access(&hash, &state.database)
Expand Down
3 changes: 2 additions & 1 deletion engine/src/auth/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod hash;
pub mod middleware;
pub mod oauth;
pub mod session;
pub mod middleware;
19 changes: 18 additions & 1 deletion engine/src/auth/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl SessionState {
Ok(session)
}

pub async fn get_by_id(id: &str, database: &Database) -> Result<Option<Self>, sqlx::Error> {
pub async fn _get_by_id(id: &str, database: &Database) -> Result<Option<Self>, sqlx::Error> {
let session = sqlx::query_as::<_, SessionState>(
"SELECT * FROM sessions WHERE id = $1 AND valid = TRUE",
)
Expand Down Expand Up @@ -88,6 +88,23 @@ impl SessionState {
Ok(sessions)
}

/// Set session to invalid by id
pub async fn invalidate_by_id(
id: &str,
user_id: i32,
database: &Database,
) -> Result<Vec<Self>, sqlx::Error> {
let sessions = sqlx::query_as::<_, SessionState>(
"UPDATE sessions SET valid = FALSE WHERE user_id = $1 AND id = $2",
)
.bind(user_id)
.bind(id)
.fetch_all(&database.pool)
.await?;

Ok(sessions)
}

/// Invalidate all sessions for a user that are older than the given time
pub async fn _invalidate_by_user_id_by_time(
user_id: i32,
Expand Down
3 changes: 1 addition & 2 deletions engine/src/routes/me.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use std::sync::Arc;

use poem::{handler, web::Data, Error, IntoResponse};
use poem::web::Data;
use poem_openapi::{payload::Json, OpenApi};
use reqwest::StatusCode;

use crate::{auth::middleware::AuthToken, models::user_data::{User, UserEntry}, state::AppState};

Expand Down
104 changes: 14 additions & 90 deletions engine/src/routes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,114 +2,43 @@ use std::sync::Arc;

use me::ApiMe;
use poem::{
delete, get, handler,
listener::TcpListener,
middleware::Cors,
web::{Data, Html, Path},
EndpointExt, Route, Server,
get, handler, listener::TcpListener, middleware::Cors, web::Html, EndpointExt, Route, Server,
};
use poem_openapi::{param::Query, payload::PlainText, OpenApi, OpenApiService};
use poem_openapi::{OpenApi, OpenApiService};
use root::RootApi;
use sessions::ApiSessions;

use crate::{
auth::{middleware::AuthToken, session::SessionState},
models::{media::Media, product::Product, property::Property},
state::AppState,
};
use crate::state::AppState;

pub mod me;
pub mod oauth;
pub mod root;
pub mod sessions;

struct Api;

#[OpenApi]
impl Api {
/// Testing one two three
#[oai(path = "/hello", method = "get")]
async fn index(&self, name: Query<Option<String>>) -> PlainText<String> {
match name.0 {
Some(name) => PlainText(format!("Hello, {}!", name)),
None => PlainText("Hello, World!".to_string()),
}
}

#[oai(path = "/properties", method = "get")]
async fn get_properties(
&self,
state: Data<&Arc<AppState>>,
owner_id: Query<Option<i32>>,
) -> poem_openapi::payload::Json<Vec<Property>> {
let owner_id = owner_id.0.unwrap_or(0);

let properties = Property::get_by_owner_id(owner_id, &state.database)
.await
.unwrap();

poem_openapi::payload::Json(properties)
}

#[oai(path = "/media/:media_id", method = "get")]
async fn get_media(
&self,
state: Data<&Arc<AppState>>,
media_id: Path<i32>,
) -> poem_openapi::payload::Json<Media> {
let media = Media::get_by_id(media_id.0, &state.database).await.unwrap();

poem_openapi::payload::Json(media)
}

#[oai(path = "/product/:product_id", method = "get")]
async fn get_product(
&self,
state: Data<&Arc<AppState>>,
product_id: Path<i32>,
) -> poem_openapi::payload::Json<Product> {
let product = Product::get_by_id(product_id.0, &state.database)
.await
.unwrap();

poem_openapi::payload::Json(product)
}

#[oai(path = "/property/:property_id", method = "get")]
async fn get_property(
&self,
state: Data<&Arc<AppState>>,
property_id: Path<i32>,
) -> poem_openapi::payload::Json<Property> {
let property = Property::get_by_id(property_id.0, &state.database)
.await
.unwrap();

poem_openapi::payload::Json(property)
}
fn get_api() -> impl OpenApi {
(RootApi, ApiMe, ApiSessions)
}

// returns the html from the index.html file
#[handler]
async fn ui() -> Html<&'static str> {
async fn get_openapi_docs() -> Html<&'static str> {
Html(include_str!("./index.html"))
}

pub async fn serve(state: AppState) -> Result<(), poem::Error> {
let api_service = OpenApiService::new((Api, ApiMe, ApiSessions), "Hello World", "1.0")
.server("http://localhost:3000/api");
let api_service =
OpenApiService::new(get_api(), "Hello World", "1.0").server("http://localhost:3000/api");

let spec = api_service.spec_endpoint();

let state = Arc::new(state);

let app = Route::new()
// .at("/login", get(auth::login))
// .at("/me", get(auth::me))
// .at("/sessions", delete(auth::delete_sessions))
// .at("/callback", get(auth::callback))
.at("/login", get(oauth::login::login))
.at("/callback", get(oauth::callback::callback))
.nest("/api", api_service)
.nest("/openapi.json", spec)
.at("/", get(ui))
// .with(CookieJarManager::new())
.at("/docs", get(get_openapi_docs))
.at("/", get(get_openapi_docs))
.with(Cors::new())
.data(state);

Expand All @@ -119,8 +48,3 @@ pub async fn serve(state: AppState) -> Result<(), poem::Error> {

Ok(())
}

#[handler]
async fn root() -> &'static str {
"Hello, World!"
}
10 changes: 3 additions & 7 deletions engine/src/routes/oauth/callback.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use std::sync::Arc;

use hmac::{Hmac, Mac};
use openid::Token;
use poem::{handler, http::HeaderMap, web::{Data, Query, RealIp, Redirect}, IntoResponse};
use serde::Deserialize;
use sha2::Sha256;
use url::Url;
use uuid::Uuid;

use crate::{auth::session::SessionState, models::user_data::UserEntry, state::AppState};
use crate::{auth::{hash::hash_session, session::SessionState}, models::user_data::UserEntry, state::AppState};


#[derive(Deserialize, Debug)]
Expand Down Expand Up @@ -50,11 +48,9 @@ pub async fn callback(
let user_ip = ip.0.unwrap();

let token = Uuid::new_v4().to_string();
let mut hash = Hmac::<Sha256>::new_from_slice(b"").unwrap();
hash.update(token.as_bytes());
let hash = hex::encode(hash.finalize().into_bytes());
let hash = hash_session(&token).unwrap();

let session = SessionState::new(&hash, user.id, user_agent, &user_ip, &state.database)
let _session = SessionState::new(&hash, user.id, user_agent, &user_ip, &state.database)
.await
.unwrap();

Expand Down
77 changes: 77 additions & 0 deletions engine/src/routes/root.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use std::sync::Arc;

use poem::
web::{Data, Path}
;
use poem_openapi::{param::Query, payload::PlainText, OpenApi};

use crate::{
models::{media::Media, product::Product, property::Property},
state::AppState,
};

pub struct RootApi;

#[OpenApi]
impl RootApi {
/// Testing one two three
#[oai(path = "/hello", method = "get")]
async fn index(&self, name: Query<Option<String>>) -> PlainText<String> {
match name.0 {
Some(name) => PlainText(format!("Hello, {}!", name)),
None => PlainText("Hello, World!".to_string()),
}
}

#[oai(path = "/properties", method = "get")]
async fn get_properties(
&self,
state: Data<&Arc<AppState>>,
owner_id: Query<Option<i32>>,
) -> poem_openapi::payload::Json<Vec<Property>> {
let owner_id = owner_id.0.unwrap_or(0);

let properties = Property::get_by_owner_id(owner_id, &state.database)
.await
.unwrap();

poem_openapi::payload::Json(properties)
}

#[oai(path = "/media/:media_id", method = "get")]
async fn get_media(
&self,
state: Data<&Arc<AppState>>,
media_id: Path<i32>,
) -> poem_openapi::payload::Json<Media> {
let media = Media::get_by_id(media_id.0, &state.database).await.unwrap();

poem_openapi::payload::Json(media)
}

#[oai(path = "/product/:product_id", method = "get")]
async fn get_product(
&self,
state: Data<&Arc<AppState>>,
product_id: Path<i32>,
) -> poem_openapi::payload::Json<Product> {
let product = Product::get_by_id(product_id.0, &state.database)
.await
.unwrap();

poem_openapi::payload::Json(product)
}

#[oai(path = "/property/:property_id", method = "get")]
async fn get_property(
&self,
state: Data<&Arc<AppState>>,
property_id: Path<i32>,
) -> poem_openapi::payload::Json<Property> {
let property = Property::get_by_id(property_id.0, &state.database)
.await
.unwrap();

poem_openapi::payload::Json(property)
}
}
32 changes: 30 additions & 2 deletions engine/src/routes/sessions/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use std::sync::Arc;

use poem::web::Data;
use poem_openapi::OpenApi;
use poem::web::{Data, Path};
use poem_openapi::{payload::Json, OpenApi};
use reqwest::{Error, StatusCode};
use tracing::info;

use crate::{
auth::{middleware::AuthToken, session::SessionState},
Expand Down Expand Up @@ -29,4 +31,30 @@ impl ApiSessions {
AuthToken::None => poem_openapi::payload::Json(vec![]),
}
}

#[oai(path = "/sessions/:session_id", method = "delete")]
async fn delete_session(
&self,
auth: AuthToken,
state: Data<&Arc<AppState>>,
session_id: Path<String>,
) -> poem_openapi::payload::Json<Vec<SessionState>> {
match auth {
AuthToken::Active(active_user) => {
info!("Deleting session {:?}", session_id.0);

let sessions = SessionState::invalidate_by_id(
&session_id.0,
active_user.session.user_id,
&state.database,
)
.await
.unwrap();

Json(sessions)
}
_ => Json(vec![]),
// _ => Error::from_string("Not Authenticated", StatusCode::UNAUTHORIZED).into_response(),
}
}
}
Loading

0 comments on commit 16c8e99

Please sign in to comment.