feat(ui/user): add apps listing
This commit is contained in:
parent
acb96dee39
commit
69af48bb62
8 changed files with 133 additions and 17 deletions
20
TODO.md
20
TODO.md
|
|
@ -30,10 +30,14 @@
|
||||||
|
|
||||||
- [x] UserWebGUI: Redirect to login when JWT expire
|
- [x] UserWebGUI: Redirect to login when JWT expire
|
||||||
- [x] UserWebGUI: Show user authorizations.
|
- [x] UserWebGUI: Show user authorizations.
|
||||||
- [ ] UserWebGUI: Allow to revoke an authorization
|
- [x] UserWebGUI: Allow to revoke an authorization
|
||||||
- [ ] UserWebGUI: Show available apps
|
- [x] UserWebGUI: Show available apps (basic)
|
||||||
- [ ] UserWebGUI: Direct user grant flow, User can login to the target app/client, event if it did not started here.
|
- [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
|
- 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
|
- [ ] UserWebGUI: add TOTP
|
||||||
- [ ] send emails to users
|
- [ ] send emails to users
|
||||||
|
|
@ -41,8 +45,6 @@
|
||||||
- Architecture: do we have an admin API?
|
- Architecture: do we have an admin API?
|
||||||
|
|
||||||
- [ ] AdminCLI: init
|
- [ ] AdminCLI: init
|
||||||
- [ ] AdminCLI: create invitation links
|
- [ ] AdminWebGUI: List users
|
||||||
|
- [ ] AdminWebGUI: Assign groups to users
|
||||||
- [ ] Add admin panel via API
|
- [ ] AdminWebGUI: Create invitation
|
||||||
- [ ] AdminWebGUI: Ability to create invitation links
|
|
||||||
- [ ] Add admin CLI
|
|
||||||
|
|
|
||||||
30
config.toml
30
config.toml
|
|
@ -9,10 +9,38 @@ name = "Demo app"
|
||||||
description = "A super application where you can do everything you want."
|
description = "A super application where you can do everything you want."
|
||||||
client_id = "a1785786-8be1-443c-9a6f-35feed703609"
|
client_id = "a1785786-8be1-443c-9a6f-35feed703609"
|
||||||
client_secret = "49c6c16a-0a8a-4981-a60d-5cb96582cc1a"
|
client_secret = "49c6c16a-0a8a-4981-a60d-5cb96582cc1a"
|
||||||
|
login_uri = "https://localhost:9876"
|
||||||
allowed_redirect_uris = [
|
allowed_redirect_uris = [
|
||||||
"http://localhost:9090/authorize",
|
"http://localhost:9090/callback",
|
||||||
"http://localhost:9876/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"
|
authorize_flow = "Implicit"
|
||||||
|
|
||||||
[[roles]]
|
[[roles]]
|
||||||
|
|
|
||||||
32
src/controllers/ui/apps.rs
Normal file
32
src/controllers/ui/apps.rs
Normal 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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -5,3 +5,4 @@ pub mod register;
|
||||||
pub mod me;
|
pub mod me;
|
||||||
pub mod logout;
|
pub mod logout;
|
||||||
pub mod user_panel;
|
pub mod user_panel;
|
||||||
|
pub mod apps;
|
||||||
|
|
|
||||||
|
|
@ -21,16 +21,31 @@ enum AppAuthorizeFlow {
|
||||||
Implicit
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[fully_pub]
|
#[fully_pub]
|
||||||
struct Application {
|
struct Application {
|
||||||
slug: String,
|
slug: String,
|
||||||
name: String,
|
name: String,
|
||||||
description: String,
|
description: String,
|
||||||
|
logo_uri: Option<String>,
|
||||||
client_id: String,
|
client_id: String,
|
||||||
client_secret: String,
|
client_secret: String,
|
||||||
allowed_redirect_uris: Vec<String>,
|
allowed_redirect_uris: Vec<String>,
|
||||||
authorize_flow: AppAuthorizeFlow
|
authorize_flow: AppAuthorizeFlow,
|
||||||
|
visibility: AppVisibility,
|
||||||
|
login_uri: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ pub fn build_router(server_config: &ServerConfig, app_state: AppState) -> Router
|
||||||
.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("/apps", get(ui::apps::list_apps))
|
||||||
.route("/me", get(ui::me::me_page))
|
.route("/me", get(ui::me::me_page))
|
||||||
.route("/me/details-form", get(ui::me::me_update_details_form))
|
.route("/me/details-form", get(ui::me::me_update_details_form))
|
||||||
.route("/me/details-form", post(ui::me::me_perform_update_details))
|
.route("/me/details-form", post(ui::me::me_perform_update_details))
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,23 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
{% if token_claims is none %}
|
{% if token_claims %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/login">Login</a>
|
<a class="nav-link" href="/apps">Apps</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/register">Register</a>
|
|
||||||
</li>
|
|
||||||
{% else %}
|
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/me">Me</a>
|
<a class="nav-link" href="/me">Me</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/logout">Logout</a>
|
<a class="nav-link" href="/logout">Logout</a>
|
||||||
</li>
|
</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 %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
34
src/templates/pages/apps.html
Normal file
34
src/templates/pages/apps.html
Normal 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 %}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue