Compare commits
3 Commits
587758f4ed
...
a6acbde093
Author | SHA1 | Date | |
---|---|---|---|
a6acbde093 | |||
91d71c7006 | |||
af903de7c2 |
@ -28,6 +28,13 @@ pub struct GetUserRequest(pub UserID);
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct GetUserResult(pub Option<User>);
|
pub struct GetUserResult(pub Option<User>);
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(FindUserByUsernameResult)]
|
||||||
|
pub struct FindUserByUsername(pub String);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct FindUserByUsernameResult(pub Option<User>);
|
||||||
|
|
||||||
#[derive(Message)]
|
#[derive(Message)]
|
||||||
#[rtype(GetAllUsersResult)]
|
#[rtype(GetAllUsersResult)]
|
||||||
pub struct GetAllUsersRequest;
|
pub struct GetAllUsersRequest;
|
||||||
@ -98,6 +105,14 @@ impl Handler<GetUserRequest> for UsersActor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Handler<FindUserByUsername> for UsersActor {
|
||||||
|
type Result = MessageResult<FindUserByUsername>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: FindUserByUsername, _ctx: &mut Self::Context) -> Self::Result {
|
||||||
|
MessageResult(FindUserByUsernameResult(self.manager.find_by_username_or_email(&msg.0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Handler<GetAllUsersRequest> for UsersActor {
|
impl Handler<GetAllUsersRequest> for UsersActor {
|
||||||
type Result = MessageResult<GetAllUsersRequest>;
|
type Result = MessageResult<GetAllUsersRequest>;
|
||||||
|
|
||||||
|
21
src/controllers/admin_api.rs
Normal file
21
src/controllers/admin_api.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use actix::Addr;
|
||||||
|
use actix_web::{HttpResponse, Responder, web};
|
||||||
|
|
||||||
|
use crate::actors::users_actor::{FindUserByUsername, UsersActor};
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize)]
|
||||||
|
pub struct FindUserNameReq {
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
struct FindUserResult {
|
||||||
|
user_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn find_username(req: web::Form<FindUserNameReq>, users: web::Data<Addr<UsersActor>>) -> impl Responder {
|
||||||
|
let res = users.send(FindUserByUsername(req.0.username)).await.unwrap();
|
||||||
|
HttpResponse::Ok().json(FindUserResult {
|
||||||
|
user_id: res.0.map(|r| r.uid)
|
||||||
|
})
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
use actix_web::{HttpResponse, Responder, web};
|
use actix_web::{HttpResponse, Responder, web};
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
@ -23,6 +25,14 @@ struct UsersListTemplate {
|
|||||||
users: Vec<User>,
|
users: Vec<User>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "settings/edit_user.html")]
|
||||||
|
struct EditUserTemplate {
|
||||||
|
_parent: BaseSettingsPage,
|
||||||
|
u: User,
|
||||||
|
clients: Vec<Client>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
pub async fn clients_route(user: CurrentUser, clients: web::Data<ClientManager>) -> impl Responder {
|
pub async fn clients_route(user: CurrentUser, clients: web::Data<ClientManager>) -> impl Responder {
|
||||||
HttpResponse::Ok().body(ClientsListTemplate {
|
HttpResponse::Ok().body(ClientsListTemplate {
|
||||||
@ -48,4 +58,12 @@ pub async fn users_route(user: CurrentUser, users: web::Data<Addr<UsersActor>>)
|
|||||||
),
|
),
|
||||||
users,
|
users,
|
||||||
}.render().unwrap())
|
}.render().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_user(user: CurrentUser, clients: web::Data<ClientManager>) -> impl Responder {
|
||||||
|
HttpResponse::Ok().body(EditUserTemplate {
|
||||||
|
_parent: BaseSettingsPage::get("Create a new user", user.deref(), None, None),
|
||||||
|
u: Default::default(),
|
||||||
|
clients: clients.cloned(),
|
||||||
|
}.render().unwrap())
|
||||||
}
|
}
|
@ -2,4 +2,5 @@ pub mod assets_controller;
|
|||||||
pub mod base_controller;
|
pub mod base_controller;
|
||||||
pub mod login_controller;
|
pub mod login_controller;
|
||||||
pub mod settings_controller;
|
pub mod settings_controller;
|
||||||
pub mod admin_controller;
|
pub mod admin_controller;
|
||||||
|
pub mod admin_api;
|
@ -1,6 +1,5 @@
|
|||||||
pub mod app_config;
|
pub mod app_config;
|
||||||
pub mod entity_manager;
|
pub mod entity_manager;
|
||||||
pub mod service;
|
|
||||||
pub mod session_identity;
|
pub mod session_identity;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
|
|
||||||
pub struct ServiceID(String);
|
|
@ -1,5 +1,5 @@
|
|||||||
|
use crate::data::client::ClientID;
|
||||||
use crate::data::entity_manager::EntityManager;
|
use crate::data::entity_manager::EntityManager;
|
||||||
use crate::data::service::ServiceID;
|
|
||||||
use crate::utils::err::Res;
|
use crate::utils::err::Res;
|
||||||
|
|
||||||
pub type UserID = String;
|
pub type UserID = String;
|
||||||
@ -18,10 +18,17 @@ pub struct User {
|
|||||||
|
|
||||||
/// None = all services
|
/// None = all services
|
||||||
/// Some([]) = no service
|
/// Some([]) = no service
|
||||||
pub authorized_services: Option<Vec<ServiceID>>,
|
pub authorized_clients: Option<Vec<ClientID>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl User {
|
impl User {
|
||||||
|
pub fn can_access_app(&self, id: &ClientID) -> bool {
|
||||||
|
match &self.authorized_clients {
|
||||||
|
None => true,
|
||||||
|
Some(c) => c.contains(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn verify_password<P: AsRef<[u8]>>(&self, pass: P) -> bool {
|
pub fn verify_password<P: AsRef<[u8]>>(&self, pass: P) -> bool {
|
||||||
verify_password(pass, &self.password)
|
verify_password(pass, &self.password)
|
||||||
}
|
}
|
||||||
@ -47,7 +54,7 @@ impl Default for User {
|
|||||||
need_reset_password: false,
|
need_reset_password: false,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
admin: false,
|
admin: false,
|
||||||
authorized_services: None,
|
authorized_clients: Some(Vec::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
src/main.rs
13
src/main.rs
@ -8,11 +8,8 @@ use clap::Parser;
|
|||||||
|
|
||||||
use basic_oidc::actors::bruteforce_actor::BruteForceActor;
|
use basic_oidc::actors::bruteforce_actor::BruteForceActor;
|
||||||
use basic_oidc::actors::users_actor::UsersActor;
|
use basic_oidc::actors::users_actor::UsersActor;
|
||||||
use basic_oidc::constants::{
|
use basic_oidc::constants::*;
|
||||||
DEFAULT_ADMIN_PASSWORD, DEFAULT_ADMIN_USERNAME, MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION,
|
use basic_oidc::controllers::*;
|
||||||
SESSION_COOKIE_NAME,
|
|
||||||
};
|
|
||||||
use basic_oidc::controllers::{admin_controller, settings_controller};
|
|
||||||
use basic_oidc::controllers::assets_controller::assets_route;
|
use basic_oidc::controllers::assets_controller::assets_route;
|
||||||
use basic_oidc::controllers::login_controller::{login_route, logout_route};
|
use basic_oidc::controllers::login_controller::{login_route, logout_route};
|
||||||
use basic_oidc::data::app_config::AppConfig;
|
use basic_oidc::data::app_config::AppConfig;
|
||||||
@ -55,7 +52,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
username: DEFAULT_ADMIN_USERNAME.to_string(),
|
username: DEFAULT_ADMIN_USERNAME.to_string(),
|
||||||
password: hash_password(DEFAULT_ADMIN_PASSWORD).unwrap(),
|
password: hash_password(DEFAULT_ADMIN_PASSWORD).unwrap(),
|
||||||
need_reset_password: true,
|
need_reset_password: true,
|
||||||
authorized_services: None,
|
authorized_clients: None,
|
||||||
admin: true,
|
admin: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
@ -119,6 +116,10 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.to(|| async { HttpResponse::Found().append_header(("Location", "/settings")).finish() }))
|
.to(|| async { HttpResponse::Found().append_header(("Location", "/settings")).finish() }))
|
||||||
.route("/admin/clients", web::get().to(admin_controller::clients_route))
|
.route("/admin/clients", web::get().to(admin_controller::clients_route))
|
||||||
.route("/admin/users", web::get().to(admin_controller::users_route))
|
.route("/admin/users", web::get().to(admin_controller::users_route))
|
||||||
|
.route("/admin/create_user", web::get().to(admin_controller::create_user))
|
||||||
|
|
||||||
|
// Admin API
|
||||||
|
.route("/admin/api/find_username", web::post().to(admin_api::find_username))
|
||||||
})
|
})
|
||||||
.bind(listen_address)?
|
.bind(listen_address)?
|
||||||
.run()
|
.run()
|
||||||
|
152
templates/settings/edit_user.html
Normal file
152
templates/settings/edit_user.html
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
{% extends "base_settings_page.html" %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<form method="post" target="/admin/users">
|
||||||
|
<!-- User ID -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label mt-4" for="userID">User ID</label>
|
||||||
|
<input class="form-control" id="userID" type="text" readonly=""
|
||||||
|
name="uid" value="{{ u.uid }}"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- User name -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label mt-4" for="username">User name</label>
|
||||||
|
<input class="form-control" id="username" type="text"
|
||||||
|
name="username" value="{{ u.username }}" required/>
|
||||||
|
<div class="valid-feedback">This username is valid</div>
|
||||||
|
<div class="invalid-feedback">This username is already taken.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- First name -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label mt-4" for="first_name">First name</label>
|
||||||
|
<input class="form-control" id="first_name" type="text"
|
||||||
|
name="first_name" value="{{ u.first_name }}"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Last name -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label mt-4" for="last_name">Last name</label>
|
||||||
|
<input class="form-control" id="last_name" type="text"
|
||||||
|
name="last_name" value="{{ u.last_name }}"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Email -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label mt-4" for="email">Email address</label>
|
||||||
|
<input class="form-control" id="email" type="email"
|
||||||
|
name="email" value="{{ u.email }}"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mt-4">
|
||||||
|
<!-- Generate new password -->
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="gen_new_password" id="gen_new_password" {% if
|
||||||
|
u.password.is_empty() %} checked="" {% endif %}>
|
||||||
|
<label class="form-check-label" for="gen_new_password">
|
||||||
|
Generate a new temporary password
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Enabled -->
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="enabled" id="enabled" {% if u.enabled %} checked="" {%
|
||||||
|
endif %}>
|
||||||
|
<label class="form-check-label" for="enabled">
|
||||||
|
Enabled
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Admin -->
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" name="admin" id="admin" {% if u.admin %} checked="" {% endif
|
||||||
|
%}>
|
||||||
|
<label class="form-check-label" for="admin">
|
||||||
|
Grant admin privileges
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Granted clients -->
|
||||||
|
<fieldset class="form-group">
|
||||||
|
<legend class="mt-4">Granted clients</legend>
|
||||||
|
<div class="form-check">
|
||||||
|
<label class="form-check-label">
|
||||||
|
<input type="radio" class="form-check-input" name="granted_clients"
|
||||||
|
value="all_clients" {% if u.authorized_clients== None %} checked="" {% endif %}>
|
||||||
|
Grant all clients
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<label class="form-check-label">
|
||||||
|
<input type="radio" class="form-check-input" name="granted_clients"
|
||||||
|
value="custom_clients" {% if u.authorized_clients !=None %} checked="checked" {% endif %}>
|
||||||
|
Manually specify allowed clients
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="clients_target">
|
||||||
|
{% for c in clients %}
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" class="authorize_client" data-id="{{ c.id.0 }}"
|
||||||
|
{% if u.can_access_app(c.id) %} checked="" {% endif %}>
|
||||||
|
<label class="form-check-label" for="admin">
|
||||||
|
{{ c.name }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Check Username
|
||||||
|
async function find_username(username) {
|
||||||
|
let data = new URLSearchParams();
|
||||||
|
data.append("username", username);
|
||||||
|
|
||||||
|
return (await(await fetch("/admin/api/find_username", {
|
||||||
|
body: data,
|
||||||
|
method: "POST",
|
||||||
|
})).json()).user_id
|
||||||
|
}
|
||||||
|
|
||||||
|
const usernameEl = document.getElementById("username")
|
||||||
|
async function check_username() {
|
||||||
|
try {
|
||||||
|
usernameEl.classList.remove("is-invalid");
|
||||||
|
usernameEl.classList.remove("is-valid");
|
||||||
|
|
||||||
|
if (usernameEl.value === "")
|
||||||
|
return;
|
||||||
|
|
||||||
|
const userID = await find_username(usernameEl.value);
|
||||||
|
usernameEl.classList.add((userID === null || userID === "{{ u.uid }}") ? "is-valid" : "is-invalid");
|
||||||
|
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check_username();
|
||||||
|
usernameEl.addEventListener("change", check_username);
|
||||||
|
usernameEl.addEventListener("keyup", check_username);
|
||||||
|
|
||||||
|
|
||||||
|
// Clients granted
|
||||||
|
function refreshDisplayAuthorizedClients() {
|
||||||
|
const clientsSelectorEl = document.getElementById("clients_target");
|
||||||
|
const radioBtn = document.querySelector("input[name=granted_clients][value=custom_clients]");
|
||||||
|
clientsSelectorEl.style.display = radioBtn.checked ? "block" : "none";
|
||||||
|
}
|
||||||
|
refreshDisplayAuthorizedClients();
|
||||||
|
document.querySelectorAll("input[name=granted_clients]").forEach(el=> {
|
||||||
|
el.addEventListener("change", refreshDisplayAuthorizedClients)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock content %}
|
Loading…
x
Reference in New Issue
Block a user