Skip to content
61 changes: 61 additions & 0 deletions src/auth/component.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
use crate::auth::core::basic_checker;
use askama::Template;
use poem::{
error::InternalServerError,
handler,
http::StatusCode,
session::Session,
web::{Form, Html},
IntoResponse, Request, Response,
};
use poem_openapi::auth::Basic;
use serde::Deserialize;

/// Template for Signin form
#[derive(Template)]
#[template(path = "auth/component/signin_form.html")]
pub struct SigninForm {
pub error: Option<String>,
}

#[derive(Deserialize)]
struct SigninParams {
username: String,
password: String,
}

/// Signin form for the UI
#[handler]
pub async fn signin_form(
Form(params): Form<SigninParams>,
session: &Session,
req: &Request,
) -> Result<Response, poem::Error> {
let basic = Basic {
username: params.username,
password: params.password,
};
// Do the creds match what we are expecting?
match basic_checker(req, basic).await {
Some(user) => {
// Save the username if auth is good
session.set("username", user.username);

// Redirect back to home page
Ok(Response::builder()
.status(StatusCode::FOUND)
.header("HX-Redirect", "/")
.finish())
}
None => {
// Well, looks like user auth failed
let signin_form: String = SigninForm {
error: Some("User authentiation failed".to_string()),
}
.render()
.map_err(InternalServerError)?;

Ok(Html(signin_form).into_response())
}
}
}
31 changes: 26 additions & 5 deletions src/auth/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,39 @@ async fn token_checker(req: &Request, api_key: ApiKey) -> Option<User> {
// Pull jwt data
let token_data = decode::<Claim>(&api_key.key, decoding_key, &Validation::default()).ok()?;

// Params to valid the user
let allowed_users: &Vec<UserCred> = req.data::<Vec<UserCred>>()?;
let username: &str = &token_data.claims.sub;

// Make sure the user in the token is still valid.
let user_creds = req.data::<Vec<UserCred>>()?;
user_creds
validate_user(allowed_users, username)
}

/// Is the username still valid?
fn validate_user(allowed_users: &[UserCred], username: &str) -> Option<User> {
// Make sure the user in the token is still valid.
let found_user: &UserCred = allowed_users
.iter()
.find(|user_cred| user_cred.username == token_data.claims.sub)?;
.find(|allowed_user| allowed_user.username == username)?;

// Return User from inside the token
// Return found User
Some(User {
username: token_data.claims.sub,
username: found_user.username.to_string(),
})
}

/// Is the user allowed access?
pub fn has_ui_access(username: &str, req: &Request) -> bool {
// Params to valid the user
let allowed_users: Option<&Vec<UserCred>> = req.data::<Vec<UserCred>>();

// Make sure the user in the token is still valid.
match allowed_users {
Some(allowed_users) => validate_user(allowed_users, username).is_some(),
None => false,
}
}

/// Key or Basic Auth
#[derive(SecurityScheme)]
pub enum TokenOrBasicAuth {
Expand Down
6 changes: 4 additions & 2 deletions src/auth/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
mod api;
mod component;
mod core;
mod page;
mod route;

pub use crate::auth::{
api::AuthApi,
core::{Auth, TokenAuth, UserCred},
page::route,
core::{has_ui_access, Auth, TokenAuth, UserCred},
route::route,
};
104 changes: 26 additions & 78 deletions src/auth/page.rs
Original file line number Diff line number Diff line change
@@ -1,98 +1,53 @@
use crate::{auth::core::basic_checker, index::Navbar};
use crate::{auth::component::SigninForm, index::Navbar};
use askama::Template;
use poem::{
error::InternalServerError,
get, handler,
handler,
http::{header, StatusCode},
session::Session,
web::{Form, Html},
IntoResponse, Request, Response, Route,
web::Html,
IntoResponse, Response,
};
use poem_openapi::auth::Basic;
use serde::Deserialize;

/// Tempate for Signin page
/// Template for Sign In page
#[derive(Template)]
#[template(path = "auth/page/signin.html")]
struct Signin<'a> {
struct Signin {
navbar: Navbar,
signin_form: SigninForm<'a>,
signin_form: SigninForm,
}

/// Sign in page
#[handler]
fn signin(session: &Session) -> Result<Response, poem::Error> {
pub fn signin(session: &Session) -> Result<Response, poem::Error> {
let username: Option<String> = session.get("username");

// Are we already signed in?
if username.is_some() {
// Redirect back to home page
Ok(Response::builder()
.status(StatusCode::FOUND)
.header(header::LOCATION, "/")
.finish())
} else {
// Ok, we are not signed in
let signin_html: String = Signin {
navbar: Navbar { username: None },
signin_form: SigninForm { error: None },
match username {
Some(_) => {
// Redirect back to home page
Ok(Response::builder()
.status(StatusCode::FOUND)
.header(header::LOCATION, "/")
.finish())
}
.render()
.map_err(InternalServerError)?;

Ok(Html(signin_html).into_response())
}
}

/// Tempate for Signin form
#[derive(Template)]
#[template(path = "auth/component/signin_form.html")]
struct SigninForm<'a> {
error: Option<&'a str>,
}

#[derive(Deserialize)]
struct SigninParams {
username: String,
password: String,
}

/// Signin form for the UI
#[handler]
async fn signin_form(
Form(params): Form<SigninParams>,
session: &Session,
req: &Request,
) -> Result<Response, poem::Error> {
let basic = Basic {
username: params.username,
password: params.password,
};
// Do the creds match what we are expecting?
if let Some(user) = basic_checker(req, basic).await {
// Save the username if auth is good
session.set("username", user.username);

// Redirect back to home page
Ok(Response::builder()
.status(StatusCode::FOUND)
.header("HX-Redirect", "/")
.finish())
} else {
// Well, looks like user auth failed
let signin_form_html: String = SigninForm {
error: Some("User authentiation failed"),
None => {
// Ok, we are not signed in
let signin: String = Signin {
navbar: Navbar { username: None },
signin_form: SigninForm { error: None },
}
.render()
.map_err(InternalServerError)?;

Ok(Html(signin).into_response())
}
.render()
.map_err(InternalServerError)?;

Ok(Html(signin_form_html).into_response())
}
}

/// Logout and purge some cookies
#[handler]
fn logout(session: &Session) -> Response {
pub fn logout(session: &Session) -> Response {
session.clear();

// Redirect back to home page
Expand All @@ -101,10 +56,3 @@ fn logout(session: &Session) -> Response {
.header(header::LOCATION, "/")
.finish()
}

/// Provide routs for the API endpoints
pub fn route() -> Route {
Route::new()
.at("/signin", get(signin).post(signin_form))
.at("/logout", get(logout))
}
12 changes: 12 additions & 0 deletions src/auth/route.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use crate::auth::{
component::signin_form,
page::{logout, signin},
};
use poem::{get, Route};

/// Provide routs for the API endpoints
pub fn route() -> Route {
Route::new()
.at("/signin", get(signin).post(signin_form))
.at("/logout", get(logout))
}
27 changes: 19 additions & 8 deletions src/domain/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,23 +126,27 @@ impl DomainApi {
Query(domain_name): Query<Option<String>>,
Query(owner): Query<Option<String>>,
Query(extra): Query<Option<String>>,
Query(ascending): Query<Option<bool>>,
Query(page): Query<Option<u64>>,
) -> Result<Json<SearchDomain>, poem::Error> {
// Default no page to 0
let page = page.unwrap_or(0);
// Defaults
let page: u64 = page.unwrap_or(0);
let ascending: bool = ascending.unwrap_or(true);

// Search Params
let search_param = SearchDomainParam {
domain_name,
owner,
extra,
ascending,
page,
};

// Start Transaction
let mut tx = pool.begin().await.map_err(InternalServerError)?;

// Pull domain
let search_domain = search_domain_read(&mut tx, &search_param, &page).await?;
let search_domain = search_domain_read(&mut tx, &search_param).await?;

Ok(Json(search_domain))
}
Expand All @@ -151,10 +155,13 @@ impl DomainApi {
#[cfg(test)]
mod tests {
use super::*;
use crate::util::test_utils::{
gen_jwt_encode_decode_token, gen_test_domain_json, gen_test_model_json, gen_test_pack_json,
gen_test_schema_json, gen_test_user_creds, post_test_domain, post_test_model,
post_test_pack, post_test_schema,
use crate::util::{
test_utils::{
gen_jwt_encode_decode_token, gen_test_domain_json, gen_test_model_json,
gen_test_pack_json, gen_test_schema_json, gen_test_user_creds, post_test_domain,
post_test_model, post_test_pack, post_test_schema,
},
PAGE_SIZE,
};
use poem::{
http::StatusCode,
Expand Down Expand Up @@ -866,7 +873,11 @@ mod tests {
let test_json = response.json().await;
let json_value = test_json.value();

json_value.object().get("domains").array().assert_len(50);
json_value
.object()
.get("domains")
.array()
.assert_len(PAGE_SIZE as usize);
json_value.object().get("page").assert_i64(0);
json_value.object().get("more").assert_bool(true);
}
Expand Down
Loading