feat(ui/user): add apps listing

This commit is contained in:
Matthieu Bessat 2024-11-25 09:07:30 +01:00
parent acb96dee39
commit 69af48bb62
8 changed files with 133 additions and 17 deletions

20
TODO.md
View file

@ -30,10 +30,14 @@
- [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.
- all apps must have a `/oauth2/login` URL that redirect to the right minauth /authorize URL
- [x] UserWebGUI: Allow to revoke an authorization
- [x] UserWebGUI: Show available apps (basic)
- [x] 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, `login_uri` in config.toml
- [x] UserWebGUI: activate account with token
- [ ] feat(perms): add groups and roles
- [ ] UserWebGUI: add TOTP
- [ ] send emails to users
@ -41,8 +45,6 @@
- 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
- [ ] AdminWebGUI: List users
- [ ] AdminWebGUI: Assign groups to users
- [ ] AdminWebGUI: Create invitation

View file

@ -9,10 +9,38 @@ name = "Demo app"
description = "A super application where you can do everything you want."
client_id = "a1785786-8be1-443c-9a6f-35feed703609"
client_secret = "49c6c16a-0a8a-4981-a60d-5cb96582cc1a"
login_uri = "https://localhost:9876"
allowed_redirect_uris = [
"http://localhost:9090/authorize",
"http://localhost:9090/callback",
"http://localhost:9876/callback"
]
visibility = "Internal"
authorize_flow = "Implicit"
[[applications]]
slug = "wiki"
name = "Wiki app"
description = "The knowledge base of the exemple org."
client_id = "f9de1885-448d-44bb-8c48-7e985486a8c6"
client_secret = "49c6c16a-0a8a-4981-a60d-5cb96582cc1a"
login_uri = "https://wiki.example.org/login"
allowed_redirect_uris = [
"https://wiki.example.org/oauth2/callback"
]
visibility = "Public"
authorize_flow = "Implicit"
[[applications]]
slug = "private_app"
name = "Demo app"
description = "Private app you should never discover"
client_id = "c8a08783-2342-4ce3-a3cb-9dc89b6bdf"
client_secret = "this_is_the_secret"
login_uri = "https://private-app.org"
allowed_redirect_uris = [
"http://localhost:9091/authorize",
]
visibility = "Private"
authorize_flow = "Implicit"
[[roles]]

View file

@ -0,0 +1,32 @@
use axum::{extract::State, response::IntoResponse, Extension};
use minijinja::context;
use crate::{
models::{config::AppVisibility, config::Application},
renderer::TemplateRenderer,
server::AppState
};
pub async fn list_apps(
State(app_state): State<AppState>,
Extension(renderer): Extension<TemplateRenderer>,
) -> impl IntoResponse {
// implement app discovery
// for now, we just list all apps in the organization
let apps: Vec<&Application> = app_state.config
.applications
.iter()
.filter(|a|
a.visibility == AppVisibility::Public ||
a.visibility == AppVisibility::Internal
).collect();
renderer.render(
"pages/apps",
context!(
apps => apps
)
)
}

View file

@ -5,3 +5,4 @@ pub mod register;
pub mod me;
pub mod logout;
pub mod user_panel;
pub mod apps;

View file

@ -21,16 +21,31 @@ enum AppAuthorizeFlow {
Implicit
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[fully_pub]
enum AppVisibility {
/// app is public (visible to non-signed in user), useful for app discovery
Public,
/// app is visible to all signed-in users
Internal,
/// app will be only visible when the user reach the login endpoint
Private
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[fully_pub]
struct Application {
slug: String,
name: String,
description: String,
logo_uri: Option<String>,
client_id: String,
client_secret: String,
allowed_redirect_uris: Vec<String>,
authorize_flow: AppAuthorizeFlow
authorize_flow: AppAuthorizeFlow,
visibility: AppVisibility,
login_uri: String
}
#[derive(Debug, Clone, Serialize, Deserialize)]

View file

@ -26,6 +26,7 @@ pub fn build_router(server_config: &ServerConfig, app_state: AppState) -> Router
.route("/logout", get(ui::logout::perform_logout))
.route("/authorize", get(ui::authorize::authorize_form))
.route("/authorize", post(ui::authorize::perform_authorize))
.route("/apps", get(ui::apps::list_apps))
.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))

View file

@ -9,20 +9,23 @@
</li>
</ul>
<ul class="navbar-nav">
{% if token_claims is none %}
{% if token_claims %}
<li class="nav-item">
<a class="nav-link" href="/login">Login</a>
<a class="nav-link" href="/apps">Apps</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/register">Register</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="/me">Me</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/logout">Logout</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="/login">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/register">Register</a>
</li>
{% endif %}
</ul>
</div>

View file

@ -0,0 +1,34 @@
{% extends "layouts/base.html" %}
{% block body %}
<h1>Available apps</h1>
<p>List of apps you can use with Single-Sign-On in this organization.</p>
<div class="apps-mosaic">
<div class="row">
{% for app in apps %}
<div class="col-xs-12 col-sm-6 col-lg-3 mb-3 mb-sm-0">
<div class="card">
<div class="card-body">
{% if app.logo_uri %}
<img src="{{ app.logo_uri}}" class="card-img-top" alt="{{ app.name }} logo">
{% endif %}
<h5 class="card-title">
{{ app.name }}
</h5>
<p class="card-text">
{{ app.description }}
</p>
<a
href="{{ app.login_uri }}"
class="btn btn-primary"
title="Open the app or login"
>
Open app
</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}