From 330add1f1740b3c593ea4f921f30b71847f1064c Mon Sep 17 00:00:00 2001 From: Matthieu Bessat Date: Mon, 18 Nov 2024 08:58:38 +0100 Subject: [PATCH] WIP --- TODO.md | 20 +++++--- src/controllers/ui/admin/apps.rs | 0 src/controllers/ui/admin/authorizations.rs | 0 src/controllers/ui/admin/users.rs | 0 src/controllers/ui/mod.rs | 1 + .../ui/user_panel/authorizations.rs | 49 +++++++++++++++++++ src/controllers/ui/user_panel/mod.rs | 1 + src/middlewares/renderer.rs | 1 - src/router.rs | 9 ++-- .../pages/user_panel/authorizations.html | 21 ++++++++ src/utils.rs | 2 +- 11 files changed, 93 insertions(+), 11 deletions(-) create mode 100644 src/controllers/ui/admin/apps.rs create mode 100644 src/controllers/ui/admin/authorizations.rs create mode 100644 src/controllers/ui/admin/users.rs create mode 100644 src/controllers/ui/user_panel/authorizations.rs create mode 100644 src/controllers/ui/user_panel/mod.rs create mode 100644 src/templates/pages/user_panel/authorizations.html diff --git a/TODO.md b/TODO.md index ec8bc9b..26607cf 100644 --- a/TODO.md +++ b/TODO.md @@ -28,13 +28,21 @@ - [ ] Support error responses by https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1 -- [ ] UserWebGUI: Redirect to login when JWT expire -- [ ] UserWebGUI: Show user authorizations. +- [x] UserWebGUI: Redirect to login when JWT expire +- [x] UserWebGUI: Show user authorizations. +- [ ] UserWebGUI: Allow to revoke an authorization - [ ] UserWebGUI: Show available apps -- [ ] UserWebGUI: Direct user grant flow, User can login to the target app/client, event if it did -not started here. +- [ ] UserWebGUI: Direct user grant flow, User can login to the target app/client, event if it did not started here. + - all apps must have a `/oauth2/login` URL that redirect to the right minauth /authorize URL + +- [ ] UserWebGUI: add TOTP +- [ ] send emails to users + +- Architecture: do we have an admin API? + +- [ ] AdminCLI: init +- [ ] AdminCLI: create invitation links + - [ ] Add admin panel via API - [ ] AdminWebGUI: Ability to create invitation links - [ ] Add admin CLI - -- [ ] add TOTP diff --git a/src/controllers/ui/admin/apps.rs b/src/controllers/ui/admin/apps.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/controllers/ui/admin/authorizations.rs b/src/controllers/ui/admin/authorizations.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/controllers/ui/admin/users.rs b/src/controllers/ui/admin/users.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/controllers/ui/mod.rs b/src/controllers/ui/mod.rs index 5910f89..37188bb 100644 --- a/src/controllers/ui/mod.rs +++ b/src/controllers/ui/mod.rs @@ -4,3 +4,4 @@ pub mod login; pub mod register; pub mod me; pub mod logout; +pub mod user_panel; diff --git a/src/controllers/ui/user_panel/authorizations.rs b/src/controllers/ui/user_panel/authorizations.rs new file mode 100644 index 0000000..36bb14f --- /dev/null +++ b/src/controllers/ui/user_panel/authorizations.rs @@ -0,0 +1,49 @@ +use axum::{extract::State, response::{IntoResponse, Redirect}, Extension, Form}; +use axum_extra::extract::{cookie::Cookie, CookieJar}; +use fully_pub::fully_pub; +use minijinja::context; +use serde::Deserialize; + +use crate::{models::{authorization::Authorization, token_claims::UserTokenClaims}, renderer::TemplateRenderer, server::AppState}; + +pub async fn get_authorizations( + State(app_state): State, + Extension(renderer): Extension, + Extension(token_claims): Extension, +) -> impl IntoResponse { + let user_authorizations = sqlx::query_as::<_, Authorization>("SELECT * FROM authorizations WHERE user_id = $1") + .bind(&token_claims.sub) + .fetch_all(&app_state.db) + .await + .expect("To get user authorization with user_id from claim"); + renderer.render( + "pages/user_panel/authorizations", + context!( + user_authorizations => user_authorizations + ) + ) +} + + +#[derive(Debug, Deserialize)] +#[fully_pub] +struct RevokeAuthorizationForm { + authorization_id: String +} + +pub async fn revoke_authorization( + State(app_state): State, + Extension(renderer): Extension, + Extension(token_claims): Extension, + cookies: CookieJar, + Form(form): Form +) -> impl IntoResponse { + let delete_res = sqlx::query("DELETE FROM authorizations WHERE id = $1") + .bind(&form.authorization_id) + .execute(&app_state.db) + .await; + + ( + Redirect::temporary("/me/authorizations") + ) +} diff --git a/src/controllers/ui/user_panel/mod.rs b/src/controllers/ui/user_panel/mod.rs new file mode 100644 index 0000000..11834ae --- /dev/null +++ b/src/controllers/ui/user_panel/mod.rs @@ -0,0 +1 @@ +pub mod authorizations; diff --git a/src/middlewares/renderer.rs b/src/middlewares/renderer.rs index 21f00f7..0e01238 100644 --- a/src/middlewares/renderer.rs +++ b/src/middlewares/renderer.rs @@ -12,7 +12,6 @@ pub async fn renderer_middleware( env: app_state.templating_env, token_claims: token_claims_ext.map(|x| x.0) }; - dbg!(&renderer_instance); req.extensions_mut().insert(renderer_instance); Ok(next.run(req).await) } diff --git a/src/router.rs b/src/router.rs index 9082f6d..8578027 100644 --- a/src/router.rs +++ b/src/router.rs @@ -23,12 +23,15 @@ pub fn build_router(server_config: &ServerConfig, app_state: AppState) -> Router .layer(middleware::from_fn_with_state(app_state.clone(), user_auth::auth_middleware)); let user_routes = Router::new() - .route("/me", get(ui::me::me_page)) - .route("/me/details-form", get(ui::me::me_update_details_form)) - .route("/me/details-form", post(ui::me::me_perform_update_details)) .route("/logout", get(ui::logout::perform_logout)) .route("/authorize", get(ui::authorize::authorize_form)) .route("/authorize", post(ui::authorize::perform_authorize)) + .route("/me", get(ui::me::me_page)) + .route("/me/details-form", get(ui::me::me_update_details_form)) + .route("/me/details-form", post(ui::me::me_perform_update_details)) + .route("/me/authorizations", get(ui::user_panel::authorizations::get_authorizations)) + .route("/me/authorizations/revoke", post(ui::user_panel::authorizations::revoke_authorization)) + .route("/me/authorizations/reset-all", post(ui::me::me_perform_update_details)) .layer(middleware::from_fn_with_state(app_state.clone(), renderer_middleware)) .layer(middleware::from_fn_with_state(app_state.clone(), user_auth::enforce_auth_middleware)) .layer(middleware::from_fn_with_state(app_state.clone(), user_auth::auth_middleware)); diff --git a/src/templates/pages/user_panel/authorizations.html b/src/templates/pages/user_panel/authorizations.html new file mode 100644 index 0000000..fc7fc56 --- /dev/null +++ b/src/templates/pages/user_panel/authorizations.html @@ -0,0 +1,21 @@ +{% extends "layouts/base.html" %} +{% block body %} +

Your authorizations

+ +

+

    + {% for item in user_authorizations %} +
  • + {{ item.client_id }} + Scopes: {{ item.scopes }} + Last_used_at: {{ item.last_used_at }} +
    + + +
    +
  • + {% endfor %} +
+

+{% endblock %} + diff --git a/src/utils.rs b/src/utils.rs index 7486772..4afce48 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -59,5 +59,5 @@ pub fn parse_basic_auth(header_value: &str) -> Result<(String, String)> { components.first().ok_or(anyhow!("Expected username in encoded Authorization header value."))?.to_string(), components.get(1).ok_or(anyhow!("Expected password in encoded Authorization header value."))?.to_string() )) - } +