WIP
This commit is contained in:
parent
b20c30048c
commit
330add1f17
11 changed files with 93 additions and 11 deletions
20
TODO.md
20
TODO.md
|
@ -28,13 +28,21 @@
|
||||||
|
|
||||||
- [ ] Support error responses by https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
|
- [ ] Support error responses by https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1
|
||||||
|
|
||||||
- [ ] UserWebGUI: Redirect to login when JWT expire
|
- [x] UserWebGUI: Redirect to login when JWT expire
|
||||||
- [ ] UserWebGUI: Show user authorizations.
|
- [x] UserWebGUI: Show user authorizations.
|
||||||
|
- [ ] UserWebGUI: Allow to revoke an authorization
|
||||||
- [ ] UserWebGUI: Show available apps
|
- [ ] UserWebGUI: Show available apps
|
||||||
- [ ] UserWebGUI: Direct user grant flow, User can login to the target app/client, event if it did
|
- [ ] UserWebGUI: Direct user grant flow, User can login to the target app/client, event if it did not started here.
|
||||||
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
|
- [ ] Add admin panel via API
|
||||||
- [ ] AdminWebGUI: Ability to create invitation links
|
- [ ] AdminWebGUI: Ability to create invitation links
|
||||||
- [ ] Add admin CLI
|
- [ ] Add admin CLI
|
||||||
|
|
||||||
- [ ] add TOTP
|
|
||||||
|
|
0
src/controllers/ui/admin/apps.rs
Normal file
0
src/controllers/ui/admin/apps.rs
Normal file
0
src/controllers/ui/admin/authorizations.rs
Normal file
0
src/controllers/ui/admin/authorizations.rs
Normal file
0
src/controllers/ui/admin/users.rs
Normal file
0
src/controllers/ui/admin/users.rs
Normal file
|
@ -4,3 +4,4 @@ pub mod login;
|
||||||
pub mod register;
|
pub mod register;
|
||||||
pub mod me;
|
pub mod me;
|
||||||
pub mod logout;
|
pub mod logout;
|
||||||
|
pub mod user_panel;
|
||||||
|
|
49
src/controllers/ui/user_panel/authorizations.rs
Normal file
49
src/controllers/ui/user_panel/authorizations.rs
Normal file
|
@ -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<AppState>,
|
||||||
|
Extension(renderer): Extension<TemplateRenderer>,
|
||||||
|
Extension(token_claims): Extension<UserTokenClaims>,
|
||||||
|
) -> 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<AppState>,
|
||||||
|
Extension(renderer): Extension<TemplateRenderer>,
|
||||||
|
Extension(token_claims): Extension<UserTokenClaims>,
|
||||||
|
cookies: CookieJar,
|
||||||
|
Form(form): Form<RevokeAuthorizationForm>
|
||||||
|
) -> 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")
|
||||||
|
)
|
||||||
|
}
|
1
src/controllers/ui/user_panel/mod.rs
Normal file
1
src/controllers/ui/user_panel/mod.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod authorizations;
|
|
@ -12,7 +12,6 @@ pub async fn renderer_middleware(
|
||||||
env: app_state.templating_env,
|
env: app_state.templating_env,
|
||||||
token_claims: token_claims_ext.map(|x| x.0)
|
token_claims: token_claims_ext.map(|x| x.0)
|
||||||
};
|
};
|
||||||
dbg!(&renderer_instance);
|
|
||||||
req.extensions_mut().insert(renderer_instance);
|
req.extensions_mut().insert(renderer_instance);
|
||||||
Ok(next.run(req).await)
|
Ok(next.run(req).await)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
.layer(middleware::from_fn_with_state(app_state.clone(), user_auth::auth_middleware));
|
||||||
|
|
||||||
let user_routes = Router::new()
|
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("/logout", get(ui::logout::perform_logout))
|
||||||
.route("/authorize", get(ui::authorize::authorize_form))
|
.route("/authorize", get(ui::authorize::authorize_form))
|
||||||
.route("/authorize", post(ui::authorize::perform_authorize))
|
.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(), 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::enforce_auth_middleware))
|
||||||
.layer(middleware::from_fn_with_state(app_state.clone(), user_auth::auth_middleware));
|
.layer(middleware::from_fn_with_state(app_state.clone(), user_auth::auth_middleware));
|
||||||
|
|
21
src/templates/pages/user_panel/authorizations.html
Normal file
21
src/templates/pages/user_panel/authorizations.html
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{% extends "layouts/base.html" %}
|
||||||
|
{% block body %}
|
||||||
|
<h1>Your authorizations</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<ul>
|
||||||
|
{% for item in user_authorizations %}
|
||||||
|
<li>
|
||||||
|
{{ item.client_id }}
|
||||||
|
Scopes: {{ item.scopes }}
|
||||||
|
Last_used_at: {{ item.last_used_at }}
|
||||||
|
<form method="post" action="/me/authorizations/revoke">
|
||||||
|
<input type="hidden" name="client_id" value="{{ item.client_id }}" />
|
||||||
|
<button class="btn btn-primary">Revoke</button>
|
||||||
|
</form>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -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.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()
|
components.get(1).ok_or(anyhow!("Expected password in encoded Authorization header value."))?.to_string()
|
||||||
))
|
))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue