Automatically create admin on first start
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,2 +1,3 @@
 | 
				
			|||||||
/target
 | 
					/target
 | 
				
			||||||
.idea
 | 
					.idea
 | 
				
			||||||
 | 
					storage
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1587
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1587
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -6,3 +6,10 @@ edition = "2021"
 | 
				
			|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
					# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
 | 
					rocket = "0.5.0-rc.1"
 | 
				
			||||||
 | 
					log = "0.4.16"
 | 
				
			||||||
 | 
					serde_json = "1.0.79"
 | 
				
			||||||
 | 
					env_logger = "0.9.0"
 | 
				
			||||||
 | 
					serde = { version = "1.0.136", features = ["derive"] }
 | 
				
			||||||
 | 
					bcrypt = "0.12.1"
 | 
				
			||||||
 | 
					uuid = { version = "0.8.2", features = ["v4"] }
 | 
				
			||||||
							
								
								
									
										6
									
								
								src/constants.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/constants.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
				
			|||||||
 | 
					/// File in storage containing users list
 | 
				
			||||||
 | 
					pub const USERS_LIST_FILE: &str = "users.json";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Default built-in credentials
 | 
				
			||||||
 | 
					pub const DEFAULT_ADMIN_USERNAME: &str = "admin";
 | 
				
			||||||
 | 
					pub const DEFAULT_ADMIN_PASSWORD: &str = "admin";
 | 
				
			||||||
							
								
								
									
										19
									
								
								src/data/app_config.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/data/app_config.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					use std::path::{Path, PathBuf};
 | 
				
			||||||
 | 
					use rocket::serde::Deserialize;
 | 
				
			||||||
 | 
					use crate::constants::USERS_LIST_FILE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Deserialize)]
 | 
				
			||||||
 | 
					#[serde(crate = "rocket::serde")]
 | 
				
			||||||
 | 
					pub struct AppConfig {
 | 
				
			||||||
 | 
					    storage_path: PathBuf,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl AppConfig {
 | 
				
			||||||
 | 
					    pub fn storage_path(&self) -> &Path {
 | 
				
			||||||
 | 
					        self.storage_path.as_ref()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn users_file(&self) -> PathBuf {
 | 
				
			||||||
 | 
					        self.storage_path.join(USERS_LIST_FILE)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										43
									
								
								src/data/entity_manager.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/data/entity_manager.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
				
			|||||||
 | 
					use std::path::{Path, PathBuf};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::utils::err::Res;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct EntityManager<E> {
 | 
				
			||||||
 | 
					    file_path: PathBuf,
 | 
				
			||||||
 | 
					    list: Vec<E>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<E> EntityManager<E> where E: rocket::serde::Serialize + rocket::serde::DeserializeOwned + Eq + Clone {
 | 
				
			||||||
 | 
					    /// Open entity
 | 
				
			||||||
 | 
					    pub fn open_or_create<A: AsRef<Path>>(path: A) -> Res<Self> {
 | 
				
			||||||
 | 
					        if !path.as_ref().is_file() {
 | 
				
			||||||
 | 
					            log::warn!("Entities at {:?} does not point to a file, creating a new empty entity container...", path.as_ref());
 | 
				
			||||||
 | 
					            return Ok(Self {
 | 
				
			||||||
 | 
					                file_path: path.as_ref().to_path_buf(),
 | 
				
			||||||
 | 
					                list: vec![],
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        log::info!("Open existing entity file {:?}", path.as_ref());
 | 
				
			||||||
 | 
					        Ok(Self {
 | 
				
			||||||
 | 
					            file_path: path.as_ref().to_path_buf(),
 | 
				
			||||||
 | 
					            list: serde_json::from_str(&std::fs::read_to_string(path.as_ref())?)?,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Get the number of entries in the list
 | 
				
			||||||
 | 
					    pub fn len(&self) -> usize {
 | 
				
			||||||
 | 
					        self.list.len()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// 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
 | 
				
			||||||
 | 
					    pub fn insert(&mut self, el: E) -> Res {
 | 
				
			||||||
 | 
					        self.list.push(el);
 | 
				
			||||||
 | 
					        self.save()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										4
									
								
								src/data/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/data/mod.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					pub mod app_config;
 | 
				
			||||||
 | 
					pub mod user;
 | 
				
			||||||
 | 
					pub mod service;
 | 
				
			||||||
 | 
					pub mod entity_manager;
 | 
				
			||||||
							
								
								
									
										2
									
								
								src/data/service.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/data/service.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)]
 | 
				
			||||||
 | 
					pub struct ServiceID(String);
 | 
				
			||||||
							
								
								
									
										48
									
								
								src/data/user.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/data/user.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					use crate::data::service::ServiceID;
 | 
				
			||||||
 | 
					use crate::utils::err::Res;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
 | 
					pub struct User {
 | 
				
			||||||
 | 
					    pub uid: String,
 | 
				
			||||||
 | 
					    pub first_name: String,
 | 
				
			||||||
 | 
					    pub last_last: String,
 | 
				
			||||||
 | 
					    pub username: String,
 | 
				
			||||||
 | 
					    pub email: String,
 | 
				
			||||||
 | 
					    pub password: String,
 | 
				
			||||||
 | 
					    pub need_reset_password: bool,
 | 
				
			||||||
 | 
					    pub enabled: bool,
 | 
				
			||||||
 | 
					    pub admin: bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// None = all services
 | 
				
			||||||
 | 
					    /// Some([]) = no service
 | 
				
			||||||
 | 
					    pub authorized_services: Option<Vec<ServiceID>>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl PartialEq for User {
 | 
				
			||||||
 | 
					    fn eq(&self, other: &Self) -> bool {
 | 
				
			||||||
 | 
					        self.uid.eq(&other.uid)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Eq for User {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for User {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            uid: uuid::Uuid::new_v4().to_string(),
 | 
				
			||||||
 | 
					            first_name: "".to_string(),
 | 
				
			||||||
 | 
					            last_last: "".to_string(),
 | 
				
			||||||
 | 
					            username: "".to_string(),
 | 
				
			||||||
 | 
					            email: "".to_string(),
 | 
				
			||||||
 | 
					            password: "".to_string(),
 | 
				
			||||||
 | 
					            need_reset_password: false,
 | 
				
			||||||
 | 
					            enabled: true,
 | 
				
			||||||
 | 
					            admin: false,
 | 
				
			||||||
 | 
					            authorized_services: None
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn hash_password<P: AsRef<[u8]>>(pwd: P) -> Res<String> {
 | 
				
			||||||
 | 
					    Ok(bcrypt::hash(pwd, bcrypt::DEFAULT_COST)?)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										3
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/lib.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					pub mod data;
 | 
				
			||||||
 | 
					pub mod utils;
 | 
				
			||||||
 | 
					pub mod constants;
 | 
				
			||||||
							
								
								
									
										52
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										52
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -1,3 +1,51 @@
 | 
				
			|||||||
fn main() {
 | 
					#[macro_use]
 | 
				
			||||||
    println!("Hello, world!");
 | 
					extern crate rocket;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use rocket::fairing::AdHoc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use basic_oidc::constants::{DEFAULT_ADMIN_PASSWORD, DEFAULT_ADMIN_USERNAME};
 | 
				
			||||||
 | 
					use basic_oidc::data::app_config::AppConfig;
 | 
				
			||||||
 | 
					use basic_oidc::data::entity_manager::EntityManager;
 | 
				
			||||||
 | 
					use basic_oidc::data::user::{hash_password, User};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[get("/health")]
 | 
				
			||||||
 | 
					fn index() -> &'static str {
 | 
				
			||||||
 | 
					    "Running"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[launch]
 | 
				
			||||||
 | 
					fn rocket() -> _ {
 | 
				
			||||||
 | 
					    //env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let rocket = rocket::build()
 | 
				
			||||||
 | 
					        .mount("/", routes![index])
 | 
				
			||||||
 | 
					        .attach(AdHoc::config::<AppConfig>());
 | 
				
			||||||
 | 
					    let figment = rocket.figment();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Initialize application
 | 
				
			||||||
 | 
					    let config: AppConfig = figment.extract().expect("config");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if !config.storage_path().exists() {
 | 
				
			||||||
 | 
					        log::error!("Specified storage path {:?} does not exists!", config.storage_path());
 | 
				
			||||||
 | 
					        panic!()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut users = EntityManager::<User>::open_or_create(config.users_file())
 | 
				
			||||||
 | 
					        .expect("Failed to load users list!");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Create initial user if required
 | 
				
			||||||
 | 
					    if users.len() == 0 {
 | 
				
			||||||
 | 
					        log::info!("Create default {} user", DEFAULT_ADMIN_USERNAME);
 | 
				
			||||||
 | 
					        let mut default_admin = User::default();
 | 
				
			||||||
 | 
					        default_admin.username = DEFAULT_ADMIN_USERNAME.to_string();
 | 
				
			||||||
 | 
					        default_admin.password = hash_password(DEFAULT_ADMIN_PASSWORD).unwrap();
 | 
				
			||||||
 | 
					        default_admin.need_reset_password = true;
 | 
				
			||||||
 | 
					        default_admin.authorized_services = None;
 | 
				
			||||||
 | 
					        default_admin.admin = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        users.insert(default_admin)
 | 
				
			||||||
 | 
					            .expect("Failed to create initial user!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rocket
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								src/utils.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/utils.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					pub mod err;
 | 
				
			||||||
							
								
								
									
										2
									
								
								src/utils/err.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/utils/err.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					use std::error::Error;
 | 
				
			||||||
 | 
					pub type Res<A = ()> = Result<A, Box<dyn Error>>;
 | 
				
			||||||
		Reference in New Issue
	
	Block a user