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..079cd1c --- /dev/null +++ b/src/controllers/ui/user_panel/authorizations.rs @@ -0,0 +1,70 @@ +use axum::{extract::State, response::IntoResponse, Extension, Form}; +use minijinja::context; + +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 { + client_id: String +} + +pub async fn revoke_authorization( + State(app_state): State, + Extension(renderer): Extension, + Extension(token_claims): Extension, + Form(form): Form +) -> impl IntoResponse { + + let update_res = sqlx::query("DELETE FROM authorizations WHERE id = $1") + .bind(&form.client_id) + .execute(&app_state.db) + .await; + + let user_res = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = $1") + .bind(&token_claims.sub) + .fetch_one(&app_state.db) + .await + .expect("To get user from claim"); + + match update_res { + Ok(_) => { + renderer.render( + template_path, + context!( + success => true, + user => user_res + ) + ) + }, + Err(err) => { + error!("Cannot update user details. {}", err); + renderer.render( + template_path, + context!( + error => Some("Cannot update user details.".to_string()), + user => user_res + ) + ) + } + } +} 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 %} +