From bba99009b489cce80d6fdb77a37cffde9190ba71 Mon Sep 17 00:00:00 2001 From: Wes Holland Date: Sun, 3 Nov 2024 15:10:25 -0600 Subject: [PATCH] Temp authorization process. Templated responses --- Cargo.lock | 12 ++++++++++++ Cargo.toml | 1 + src/auth.rs | 39 ++++++++++++++++++++++++++++----------- src/main.rs | 22 +++++++++++++++++++--- templates/index.html | 12 ++++++++++++ templates/main.html | 16 ++++++++++++++++ 6 files changed, 88 insertions(+), 14 deletions(-) create mode 100644 templates/index.html create mode 100644 templates/main.html diff --git a/Cargo.lock b/Cargo.lock index becb9e7..5d4febe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,6 +78,17 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "askama_axum" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a41603f7cdbf5ac4af60760f17253eb6adf6ec5b6f14a7ed830cf687d375f163" +dependencies = [ + "askama", + "axum-core", + "http 1.1.0", +] + [[package]] name = "askama_derive" version = "0.12.5" @@ -1080,6 +1091,7 @@ version = "0.1.0" dependencies = [ "anyhow", "askama", + "askama_axum", "axum", "axum-htmx", "dotenvy", diff --git a/Cargo.toml b/Cargo.toml index d348413..d58fda8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ tracing = "0.1.40" tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } serde = { version = "1.0.213", features = ["derive"] } reqwest = { version = "0.12.9", features = ["json"] } +askama_axum = "0.4.0" [dev-dependencies] httpc-test = "0.1.10" diff --git a/src/auth.rs b/src/auth.rs index 31f3cc0..f5c71f0 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -1,4 +1,4 @@ -use anyhow::Context; +use anyhow::{anyhow, Context}; use axum::{async_trait, http}; use axum::extract::{FromRef, FromRequestParts, Query, State}; use axum::http::{header, StatusCode}; @@ -66,6 +66,16 @@ pub async fn auth_authorized( ) -> anyhow::Result { let user_info_endpoint = std::env::var("OAUTH_USER_INFO_URL") .context("OAUTH_USER_INFO_URL not set")?; + + // Validate that we have a stored csrf token that matches the one passed back to us + let stored_csrf_token = session.remove::(CSRF_TOKEN) + .await + .context("unable to access csrf token")? + .ok_or(anyhow!("session does not exist"))?; + + if !query_auth.state.eq(stored_csrf_token.secret()) { + return Err(anyhow!("session csrf mismatch").into()) + } let token = oauth_client .exchange_code(AuthorizationCode::new(query_auth.code.clone())) @@ -87,10 +97,24 @@ pub async fn auth_authorized( .await .context("failed to deserialize response as JSON")?; - session.insert(USER_SESSION, user_data).await?; + //TODO Check against database instead of string + let valid_users = std::env::var("AUTHORIZED_USERS") + .context("Authorized users not set")?; + + let valid_users = valid_users.split(";") + .collect::>(); + + let is_authorized = valid_users.contains(&user_data.email.as_str()); + + if is_authorized { + session.insert(USER_SESSION, user_data).await?; - //TODO Redirect somewhere sane - Ok(Redirect::to("/protected")) + //TODO Redirect somewhere sane + Ok(Redirect::to("/protected").into_response()) + } + else { + Ok((http::StatusCode::UNAUTHORIZED, "Unauthorized").into_response()) + } } pub async fn auth_logout( @@ -157,13 +181,6 @@ where .map_err(|_| UserExtractError(StatusCode::INTERNAL_SERVER_ERROR))? .ok_or(UserExtractError(StatusCode::UNAUTHORIZED))?; - let db = SqlitePool::from_ref(state); - - //TODO actual verification of users - if user.email != "whatswithwes@gmail.com" { - Err(UserExtractError(StatusCode::FORBIDDEN))?; - } - Ok(user) } } diff --git a/src/main.rs b/src/main.rs index 68a219d..e8b16ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use crate::app_state::AppState; use crate::auth::User; use crate::error::AppError; use anyhow::{anyhow, Context, Result}; +use askama::Template; use axum::extract::{FromRef, Query, State}; use axum::http::header::SET_COOKIE; use axum::http::HeaderMap; @@ -76,17 +77,23 @@ async fn main() -> Result<()>{ .route("/auth/authorized", get(auth::auth_authorized)); let static_routes = Router::new() - .nest_service("/", ServeDir::new("static/")); + .nest_service("/css/pico.min.css", ServeFile::new("static/css/pico.min.css")) + .nest_service("/js/htmx.min.js", ServeFile::new("static/js/htmx.min.js")) + .nest_service("/favicon.ico", ServeFile::new("static/favicon.ico")); let test_routes: Router = Router::new() .route("/fail", get(fail)) .route("/usertest", get(index)) .route("/protected", get(protected)); + let app_routes: Router = Router::new() + .route("/", get(index)); + let router = Router::new() .merge(auth_routes) .merge(test_routes) - .fallback_service(static_routes) + .merge(app_routes) + .merge(static_routes) .layer(session_layer) .layer(TraceLayer::new_for_http()) .with_state(app_state); @@ -117,8 +124,17 @@ fn always_fails() -> Result<()> { Err(anyhow!("I always fail")) } +#[derive(Template)] // this will generate the code... +#[template(path = "index.html")] // using the template in this path, relative +// to the `templates` dir in the crate root +struct IndexTemplate<'a> { // the name of the struct can be anything + name: &'a str, // the field name should match the variable name + // in your template +} + async fn index(user: User) -> impl IntoResponse { - format!("Hello {}", user.email) + IndexTemplate { name: user.name.as_str() }.into_response() + //format!("Hello {}", user.email) } async fn protected( diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..5d0198a --- /dev/null +++ b/templates/index.html @@ -0,0 +1,12 @@ +{% extends "main.html" %} + +{% block content %} + +

Hello {{ name }}

+

This is a test page

+
+

Card

+
+Logout + +{% endblock %} \ No newline at end of file diff --git a/templates/main.html b/templates/main.html new file mode 100644 index 0000000..db0ac51 --- /dev/null +++ b/templates/main.html @@ -0,0 +1,16 @@ + + + + + + + + + Test Page + + +
+ {% block content %}

Placeholder content

{% endblock %} +
+ + \ No newline at end of file