Load a list of clients
This commit is contained in:
parent
f6403afa34
commit
da6a494875
28
Cargo.lock
generated
28
Cargo.lock
generated
@ -395,6 +395,7 @@ dependencies = [
|
|||||||
"mime_guess",
|
"mime_guess",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
@ -952,6 +953,12 @@ version = "0.2.121"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
|
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linked-hash-map"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "local-channel"
|
name = "local-channel"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
@ -1356,6 +1363,18 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_yaml"
|
||||||
|
version = "0.8.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
"yaml-rust",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha-1"
|
name = "sha-1"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@ -1733,6 +1752,15 @@ version = "0.32.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
|
checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yaml-rust"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
||||||
|
dependencies = [
|
||||||
|
"linked-hash-map",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zeroize"
|
name = "zeroize"
|
||||||
version = "1.5.4"
|
version = "1.5.4"
|
||||||
|
@ -13,6 +13,7 @@ clap = { version = "3.1.6", features = ["derive", "env"] }
|
|||||||
include_dir = "0.7.2"
|
include_dir = "0.7.2"
|
||||||
log = "0.4.16"
|
log = "0.4.16"
|
||||||
serde_json = "1.0.79"
|
serde_json = "1.0.79"
|
||||||
|
serde_yaml = "0.8.23"
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.9.0"
|
||||||
serde = { version = "1.0.136", features = ["derive"] }
|
serde = { version = "1.0.136", features = ["derive"] }
|
||||||
bcrypt = "0.12.1"
|
bcrypt = "0.12.1"
|
||||||
|
@ -3,6 +3,9 @@ use std::time::Duration;
|
|||||||
/// File in storage containing users list
|
/// File in storage containing users list
|
||||||
pub const USERS_LIST_FILE: &str = "users.json";
|
pub const USERS_LIST_FILE: &str = "users.json";
|
||||||
|
|
||||||
|
/// File in storage containing clients list
|
||||||
|
pub const CLIENTS_LIST_FILE: &str = "clients.yaml";
|
||||||
|
|
||||||
/// Default built-in credentials
|
/// Default built-in credentials
|
||||||
pub const DEFAULT_ADMIN_USERNAME: &str = "admin";
|
pub const DEFAULT_ADMIN_USERNAME: &str = "admin";
|
||||||
pub const DEFAULT_ADMIN_PASSWORD: &str = "admin";
|
pub const DEFAULT_ADMIN_PASSWORD: &str = "admin";
|
||||||
|
25
src/controllers/admin_controller.rs
Normal file
25
src/controllers/admin_controller.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
use actix_web::{HttpResponse, Responder, web};
|
||||||
|
use askama::Template;
|
||||||
|
|
||||||
|
use crate::controllers::settings_controller::BaseSettingsPage;
|
||||||
|
use crate::data::client::{Client, ClientManager};
|
||||||
|
use crate::data::current_user::CurrentUser;
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "settings/clients_list.html")]
|
||||||
|
struct ClientsListTemplate {
|
||||||
|
_parent: BaseSettingsPage,
|
||||||
|
clients: Vec<Client>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn clients_route(user: CurrentUser, clients: web::Data<ClientManager>) -> impl Responder {
|
||||||
|
HttpResponse::Ok().body(ClientsListTemplate {
|
||||||
|
_parent: BaseSettingsPage::get(
|
||||||
|
"Clients list",
|
||||||
|
&user,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
clients: clients.cloned(),
|
||||||
|
}.render().unwrap())
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
pub mod assets_controller;
|
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;
|
@ -12,17 +12,17 @@ use crate::data::user::User;
|
|||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
#[template(path = "settings/base_settings_page.html")]
|
#[template(path = "settings/base_settings_page.html")]
|
||||||
struct BaseSettingsPage {
|
pub(crate) struct BaseSettingsPage {
|
||||||
danger_message: Option<String>,
|
pub danger_message: Option<String>,
|
||||||
success_message: Option<String>,
|
pub success_message: Option<String>,
|
||||||
page_title: &'static str,
|
pub page_title: &'static str,
|
||||||
app_name: &'static str,
|
pub app_name: &'static str,
|
||||||
is_admin: bool,
|
pub is_admin: bool,
|
||||||
user_name: String,
|
pub user_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BaseSettingsPage {
|
impl BaseSettingsPage {
|
||||||
async fn get(page_title: &'static str, user: &User,
|
pub fn get(page_title: &'static str, user: &User,
|
||||||
danger_message: Option<String>, success_message: Option<String>) -> BaseSettingsPage {
|
danger_message: Option<String>, success_message: Option<String>) -> BaseSettingsPage {
|
||||||
Self {
|
Self {
|
||||||
danger_message,
|
danger_message,
|
||||||
@ -58,7 +58,7 @@ pub async fn account_settings_details_route(user: CurrentUser) -> impl Responder
|
|||||||
let user = user.into();
|
let user = user.into();
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.body(AccountDetailsPage {
|
.body(AccountDetailsPage {
|
||||||
_parent: BaseSettingsPage::get("Account details", &user, None, None).await,
|
_parent: BaseSettingsPage::get("Account details", &user, None, None),
|
||||||
user_id: user.uid,
|
user_id: user.uid,
|
||||||
first_name: user.first_name,
|
first_name: user.first_name,
|
||||||
last_name: user.last_last,
|
last_name: user.last_last,
|
||||||
@ -121,7 +121,7 @@ pub async fn change_password_route(user: CurrentUser,
|
|||||||
|
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.body(ChangePasswordPage {
|
.body(ChangePasswordPage {
|
||||||
_parent: BaseSettingsPage::get("Change password", &user, danger, success).await,
|
_parent: BaseSettingsPage::get("Change password", &user, danger, success),
|
||||||
min_pwd_len: MIN_PASS_LEN,
|
min_pwd_len: MIN_PASS_LEN,
|
||||||
}.render().unwrap())
|
}.render().unwrap())
|
||||||
}
|
}
|
@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
|
||||||
use crate::constants::USERS_LIST_FILE;
|
use crate::constants::{CLIENTS_LIST_FILE, USERS_LIST_FILE};
|
||||||
|
|
||||||
/// Basic OIDC provider
|
/// Basic OIDC provider
|
||||||
#[derive(Parser, Debug, Clone)]
|
#[derive(Parser, Debug, Clone)]
|
||||||
@ -41,4 +41,8 @@ impl AppConfig {
|
|||||||
pub fn users_file(&self) -> PathBuf {
|
pub fn users_file(&self) -> PathBuf {
|
||||||
self.storage_path().join(USERS_LIST_FILE)
|
self.storage_path().join(USERS_LIST_FILE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clients_file(&self) -> PathBuf {
|
||||||
|
self.storage_path().join(CLIENTS_LIST_FILE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
32
src/data/client.rs
Normal file
32
src/data/client.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
use crate::data::entity_manager::EntityManager;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Eq, PartialEq)]
|
||||||
|
pub struct ClientID(pub String);
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||||
|
pub struct Client {
|
||||||
|
pub id: ClientID,
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Client {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id.eq(&other.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Client {}
|
||||||
|
|
||||||
|
pub type ClientManager = EntityManager<Client>;
|
||||||
|
|
||||||
|
impl EntityManager<Client> {
|
||||||
|
pub fn find_by_id(&self, u: &ClientID) -> Option<Client> {
|
||||||
|
for entry in self.iter() {
|
||||||
|
if entry.id.eq(u) {
|
||||||
|
return Some(entry.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
use std::ops::Deref;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
use actix::Addr;
|
use actix::Addr;
|
||||||
@ -19,6 +20,14 @@ impl From<CurrentUser> for User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deref for CurrentUser {
|
||||||
|
type Target = User;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl FromRequest for CurrentUser {
|
impl FromRequest for CurrentUser {
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Pin<Box<dyn Future<Output=Result<Self, Self::Error>>>>;
|
type Future = Pin<Box<dyn Future<Output=Result<Self, Self::Error>>>>;
|
||||||
|
@ -3,14 +3,16 @@ use std::slice::Iter;
|
|||||||
|
|
||||||
use crate::utils::err::Res;
|
use crate::utils::err::Res;
|
||||||
|
|
||||||
|
enum FileFormat { Json, Yaml }
|
||||||
|
|
||||||
pub struct EntityManager<E> {
|
pub struct EntityManager<E> {
|
||||||
file_path: PathBuf,
|
file_path: PathBuf,
|
||||||
list: Vec<E>,
|
list: Vec<E>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> EntityManager<E>
|
impl<E> EntityManager<E>
|
||||||
where
|
where
|
||||||
E: serde::Serialize + serde::de::DeserializeOwned + Eq + Clone,
|
E: serde::Serialize + serde::de::DeserializeOwned + Eq + Clone,
|
||||||
{
|
{
|
||||||
/// Open entity
|
/// Open entity
|
||||||
pub fn open_or_create<A: AsRef<Path>>(path: A) -> Res<Self> {
|
pub fn open_or_create<A: AsRef<Path>>(path: A) -> Res<Self> {
|
||||||
@ -23,12 +25,34 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
log::info!("Open existing entity file {:?}", path.as_ref());
|
log::info!("Open existing entity file {:?}", path.as_ref());
|
||||||
|
let file_content = std::fs::read_to_string(path.as_ref())?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
file_path: path.as_ref().to_path_buf(),
|
file_path: path.as_ref().to_path_buf(),
|
||||||
list: serde_json::from_str(&std::fs::read_to_string(path.as_ref())?)?,
|
list: match Self::file_format(path.as_ref()) {
|
||||||
|
FileFormat::Json => serde_json::from_str(&file_content)?,
|
||||||
|
FileFormat::Yaml => serde_yaml::from_str(&file_content)?
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Save the list
|
||||||
|
fn save(&self) -> Res {
|
||||||
|
Ok(std::fs::write(
|
||||||
|
&self.file_path,
|
||||||
|
match Self::file_format(self.file_path.as_ref()) {
|
||||||
|
FileFormat::Json => serde_json::to_string(&self.list)?,
|
||||||
|
FileFormat::Yaml => serde_yaml::to_string(&self.list)?,
|
||||||
|
},
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file_format(p: &Path) -> FileFormat {
|
||||||
|
match p.to_string_lossy().ends_with(".json") {
|
||||||
|
true => FileFormat::Json,
|
||||||
|
false => FileFormat::Yaml
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the number of entries in the list
|
/// Get the number of entries in the list
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.list.len()
|
self.list.len()
|
||||||
@ -38,14 +62,6 @@ where
|
|||||||
self.len() == 0
|
self.len() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Save the list
|
|
||||||
fn save(&self) -> Res {
|
|
||||||
Ok(std::fs::write(
|
|
||||||
&self.file_path,
|
|
||||||
serde_json::to_string(&self.list)?,
|
|
||||||
)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert a new element in the list
|
/// Insert a new element in the list
|
||||||
pub fn insert(&mut self, el: E) -> Res {
|
pub fn insert(&mut self, el: E) -> Res {
|
||||||
self.list.push(el);
|
self.list.push(el);
|
||||||
@ -54,8 +70,8 @@ where
|
|||||||
|
|
||||||
/// Replace entries in the list that matches a criteria
|
/// Replace entries in the list that matches a criteria
|
||||||
pub fn replace_entries<F>(&mut self, filter: F, el: &E) -> Res
|
pub fn replace_entries<F>(&mut self, filter: F, el: &E) -> Res
|
||||||
where
|
where
|
||||||
F: Fn(&E) -> bool,
|
F: Fn(&E) -> bool,
|
||||||
{
|
{
|
||||||
for i in 0..self.list.len() {
|
for i in 0..self.list.len() {
|
||||||
if filter(&self.list[i]) {
|
if filter(&self.list[i]) {
|
||||||
@ -70,4 +86,9 @@ where
|
|||||||
pub fn iter(&self) -> Iter<'_, E> {
|
pub fn iter(&self) -> Iter<'_, E> {
|
||||||
self.list.iter()
|
self.list.iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a cloned list of entries
|
||||||
|
pub fn cloned(&self) -> Vec<E> {
|
||||||
|
self.list.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,5 +3,6 @@ pub mod entity_manager;
|
|||||||
pub mod service;
|
pub mod service;
|
||||||
pub mod session_identity;
|
pub mod session_identity;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
pub mod client;
|
||||||
pub mod remote_ip;
|
pub mod remote_ip;
|
||||||
pub mod current_user;
|
pub mod current_user;
|
10
src/main.rs
10
src/main.rs
@ -12,10 +12,11 @@ use basic_oidc::constants::{
|
|||||||
DEFAULT_ADMIN_PASSWORD, DEFAULT_ADMIN_USERNAME, MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION,
|
DEFAULT_ADMIN_PASSWORD, DEFAULT_ADMIN_USERNAME, MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION,
|
||||||
SESSION_COOKIE_NAME,
|
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::controllers::settings_controller;
|
|
||||||
use basic_oidc::data::app_config::AppConfig;
|
use basic_oidc::data::app_config::AppConfig;
|
||||||
|
use basic_oidc::data::client::ClientManager;
|
||||||
use basic_oidc::data::entity_manager::EntityManager;
|
use basic_oidc::data::entity_manager::EntityManager;
|
||||||
use basic_oidc::data::user::{hash_password, User};
|
use basic_oidc::data::user::{hash_password, User};
|
||||||
use basic_oidc::middlewares::auth_middleware::AuthMiddleware;
|
use basic_oidc::middlewares::auth_middleware::AuthMiddleware;
|
||||||
@ -71,6 +72,9 @@ async fn main() -> std::io::Result<()> {
|
|||||||
let listen_address = config.listen_address.to_string();
|
let listen_address = config.listen_address.to_string();
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
|
let clients = ClientManager::open_or_create(config.clients_file())
|
||||||
|
.expect("Failed to load clients list!");
|
||||||
|
|
||||||
let policy = CookieIdentityPolicy::new(config.token_key.as_bytes())
|
let policy = CookieIdentityPolicy::new(config.token_key.as_bytes())
|
||||||
.name(SESSION_COOKIE_NAME)
|
.name(SESSION_COOKIE_NAME)
|
||||||
.secure(config.secure_cookie())
|
.secure(config.secure_cookie())
|
||||||
@ -82,6 +86,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.app_data(web::Data::new(users_actor.clone()))
|
.app_data(web::Data::new(users_actor.clone()))
|
||||||
.app_data(web::Data::new(bruteforce_actor.clone()))
|
.app_data(web::Data::new(bruteforce_actor.clone()))
|
||||||
.app_data(web::Data::new(config.clone()))
|
.app_data(web::Data::new(config.clone()))
|
||||||
|
.app_data(web::Data::new(clients))
|
||||||
|
|
||||||
.wrap(Logger::default())
|
.wrap(Logger::default())
|
||||||
.wrap(AuthMiddleware {})
|
.wrap(AuthMiddleware {})
|
||||||
@ -108,6 +113,9 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.route("/settings", web::get().to(settings_controller::account_settings_details_route))
|
.route("/settings", web::get().to(settings_controller::account_settings_details_route))
|
||||||
.route("/settings/change_password", web::get().to(settings_controller::change_password_route))
|
.route("/settings/change_password", web::get().to(settings_controller::change_password_route))
|
||||||
.route("/settings/change_password", web::post().to(settings_controller::change_password_route))
|
.route("/settings/change_password", web::post().to(settings_controller::change_password_route))
|
||||||
|
|
||||||
|
// Admin routes
|
||||||
|
.route("/admin/clients", web::get().to(admin_controller::clients_route))
|
||||||
})
|
})
|
||||||
.bind(listen_address)?
|
.bind(listen_address)?
|
||||||
.run()
|
.run()
|
||||||
|
@ -30,8 +30,8 @@
|
|||||||
<hr/>
|
<hr/>
|
||||||
{% if is_admin %}
|
{% if is_admin %}
|
||||||
<li>
|
<li>
|
||||||
<a href="/admin/apps" class="nav-link link-dark">
|
<a href="/admin/clients" class="nav-link link-dark">
|
||||||
Applications
|
Clients
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
23
templates/settings/clients_list.html
Normal file
23
templates/settings/clients_list.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{% extends "base_settings_page.html" %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<table class="table table-hover" style="max-width: 600px;" aria-describedby="Clients list">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">ID</th>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col">Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for c in clients %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ c.id.0 }}</td>
|
||||||
|
<td>{{ c.name }}</td>
|
||||||
|
<td>{{ c.description }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
{% endblock content %}
|
Loading…
Reference in New Issue
Block a user