Segregate features surrounding multiple certificates & passwords to a seperate feature
This commit is contained in:
parent
9916770bd2
commit
28a4d64c5f
|
@ -9,7 +9,8 @@ repository = "https://github.com/panicbit/northstar"
|
||||||
documentation = "https://docs.rs/northstar"
|
documentation = "https://docs.rs/northstar"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
user_management = ["sled", "bincode", "serde/derive", "rust-argon2", "crc32fast", "ring"]
|
user_management = ["sled", "bincode", "serde/derive", "crc32fast"]
|
||||||
|
user_management_advanced = ["rust-argon2", "ring", "user_management"]
|
||||||
default = ["serve_dir"]
|
default = ["serve_dir"]
|
||||||
serve_dir = ["mime_guess", "tokio/fs"]
|
serve_dir = ["mime_guess", "tokio/fs"]
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ pub enum UserManagerError {
|
||||||
DatabaseError(sled::Error),
|
DatabaseError(sled::Error),
|
||||||
DatabaseTransactionError(sled::transaction::TransactionError),
|
DatabaseTransactionError(sled::transaction::TransactionError),
|
||||||
DeserializeError(bincode::Error),
|
DeserializeError(bincode::Error),
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
Argon2Error(argon2::Error),
|
Argon2Error(argon2::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +60,7 @@ impl From<bincode::Error> for UserManagerError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
impl From<argon2::Error> for UserManagerError {
|
impl From<argon2::Error> for UserManagerError {
|
||||||
fn from(error: argon2::Error) -> Self {
|
fn from(error: argon2::Error) -> Self {
|
||||||
Self::Argon2Error(error)
|
Self::Argon2Error(error)
|
||||||
|
@ -71,6 +73,7 @@ impl std::error::Error for UserManagerError {
|
||||||
Self::DatabaseError(e) => Some(e),
|
Self::DatabaseError(e) => Some(e),
|
||||||
Self::DatabaseTransactionError(e) => Some(e),
|
Self::DatabaseTransactionError(e) => Some(e),
|
||||||
Self::DeserializeError(e) => Some(e),
|
Self::DeserializeError(e) => Some(e),
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
Self::Argon2Error(e) => Some(e),
|
Self::Argon2Error(e) => Some(e),
|
||||||
_ => None
|
_ => None
|
||||||
}
|
}
|
||||||
|
@ -90,6 +93,7 @@ impl std::fmt::Display for UserManagerError {
|
||||||
write!(f, "Error accessing the user database: {}", e),
|
write!(f, "Error accessing the user database: {}", e),
|
||||||
Self::DeserializeError(e) =>
|
Self::DeserializeError(e) =>
|
||||||
write!(f, "Recieved messy data from database, possible corruption: {}", e),
|
write!(f, "Recieved messy data from database, possible corruption: {}", e),
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
Self::Argon2Error(e) =>
|
Self::Argon2Error(e) =>
|
||||||
write!(f, "Argon2 Error, likely malformed password hash, possible database corruption: {}", e),
|
write!(f, "Argon2 Error, likely malformed password hash, possible database corruption: {}", e),
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,10 @@ use crate::types::document::HeadingLevel;
|
||||||
use crate::user_management::{User, RegisteredUser, UserManagerError};
|
use crate::user_management::{User, RegisteredUser, UserManagerError};
|
||||||
|
|
||||||
const UNAUTH: &str = include_str!("pages/unauth.gmi");
|
const UNAUTH: &str = include_str!("pages/unauth.gmi");
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
const NSI: &str = include_str!("pages/nsi.gmi");
|
const NSI: &str = include_str!("pages/nsi.gmi");
|
||||||
|
#[cfg(not(feature = "user_management_advanced"))]
|
||||||
|
const NSI: &str = include_str!("pages/nopass/nsi.gmi");
|
||||||
|
|
||||||
/// Import this trait to use [`add_um_routes()`](Self::add_um_routes())
|
/// Import this trait to use [`add_um_routes()`](Self::add_um_routes())
|
||||||
pub trait UserManagementRoutes: private::Sealed {
|
pub trait UserManagementRoutes: private::Sealed {
|
||||||
|
@ -86,11 +89,18 @@ impl<A: ToSocketAddrs> UserManagementRoutes for crate::Builder<A> {
|
||||||
///
|
///
|
||||||
/// See [`UserManagementRoutes::add_um_routes()`]
|
/// See [`UserManagementRoutes::add_um_routes()`]
|
||||||
fn add_um_routes<UserData: Serialize + DeserializeOwned + Default + 'static>(self, redir: &'static str) -> Self {
|
fn add_um_routes<UserData: Serialize + DeserializeOwned + Default + 'static>(self, redir: &'static str) -> Self {
|
||||||
self.add_route("/account", move|r|handle_base::<UserData>(r, redir))
|
#[allow(unused_mut)]
|
||||||
|
let mut modified_self = self.add_route("/account", move|r|handle_base::<UserData>(r, redir))
|
||||||
.add_route("/account/askcert", move|r|handle_ask_cert::<UserData>(r, redir))
|
.add_route("/account/askcert", move|r|handle_ask_cert::<UserData>(r, redir))
|
||||||
.add_route("/account/register", move|r|handle_register::<UserData>(r, redir))
|
.add_route("/account/register", move|r|handle_register::<UserData>(r, redir));
|
||||||
.add_route("/account/login", move|r|handle_login::<UserData>(r, redir))
|
|
||||||
.add_route("/account/password", handle_password::<UserData>)
|
#[cfg(feature = "user_management_advanced")] {
|
||||||
|
modified_self = modified_self
|
||||||
|
.add_route("/account/login", move|r|handle_login::<UserData>(r, redir))
|
||||||
|
.add_route("/account/password", handle_password::<UserData>);
|
||||||
|
}
|
||||||
|
|
||||||
|
modified_self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a special route that requires users to be logged in
|
/// Add a special route that requires users to be logged in
|
||||||
|
@ -171,7 +181,12 @@ async fn handle_ask_cert<UserData: Serialize + DeserializeOwned>(request: Reques
|
||||||
Response::client_certificate_required()
|
Response::client_certificate_required()
|
||||||
},
|
},
|
||||||
User::NotSignedIn(_) => {
|
User::NotSignedIn(_) => {
|
||||||
Response::success_gemini(include_str!("pages/askcert/success.gmi"))
|
#[cfg(feature = "user_management_advanced")] {
|
||||||
|
Response::success_gemini(include_str!("pages/askcert/success.gmi"))
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "user_management_advanced"))] {
|
||||||
|
Response::success_gemini(include_str!("pages/nopass/askcert/success.gmi"))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
User::SignedIn(user) => {
|
User::SignedIn(user) => {
|
||||||
Response::success_gemini(format!(
|
Response::success_gemini(format!(
|
||||||
|
@ -192,17 +207,34 @@ async fn handle_register<UserData: Serialize + DeserializeOwned + Default>(reque
|
||||||
if let Some(username) = request.input() {
|
if let Some(username) = request.input() {
|
||||||
match nsi.register::<UserData>(username.to_owned()) {
|
match nsi.register::<UserData>(username.to_owned()) {
|
||||||
Err(UserManagerError::UsernameNotUnique) => {
|
Err(UserManagerError::UsernameNotUnique) => {
|
||||||
Response::success_gemini(format!(
|
#[cfg(feature = "user_management_advanced")] {
|
||||||
include_str!("pages/register/exists.gmi"),
|
Response::success_gemini(format!(
|
||||||
username = username,
|
include_str!("pages/register/exists.gmi"),
|
||||||
))
|
username = username,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "user_management_advanced"))] {
|
||||||
|
Response::success_gemini(format!(
|
||||||
|
include_str!("pages/register/exists.gmi"),
|
||||||
|
username = username,
|
||||||
|
))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
Response::success_gemini(format!(
|
#[cfg(feature = "user_management_advanced")] {
|
||||||
include_str!("pages/register/success.gmi"),
|
Response::success_gemini(format!(
|
||||||
username = username,
|
include_str!("pages/register/success.gmi"),
|
||||||
redirect = redirect,
|
username = username,
|
||||||
))
|
redirect = redirect,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "user_management_advanced"))] {
|
||||||
|
Response::success_gemini(format!(
|
||||||
|
include_str!("pages/nopass/register/success.gmi"),
|
||||||
|
username = username,
|
||||||
|
redirect = redirect,
|
||||||
|
))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(e) => return Err(e.into())
|
Err(e) => return Err(e.into())
|
||||||
}
|
}
|
||||||
|
@ -216,6 +248,7 @@ async fn handle_register<UserData: Serialize + DeserializeOwned + Default>(reque
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
async fn handle_login<UserData: Serialize + DeserializeOwned + Default>(request: Request, redirect: &'static str) -> Result<Response> {
|
async fn handle_login<UserData: Serialize + DeserializeOwned + Default>(request: Request, redirect: &'static str) -> Result<Response> {
|
||||||
Ok(match request.user::<UserData>()? {
|
Ok(match request.user::<UserData>()? {
|
||||||
User::Unauthenticated => {
|
User::Unauthenticated => {
|
||||||
|
@ -257,6 +290,7 @@ async fn handle_login<UserData: Serialize + DeserializeOwned + Default>(request:
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
async fn handle_password<UserData: Serialize + DeserializeOwned + Default>(request: Request) -> Result<Response> {
|
async fn handle_password<UserData: Serialize + DeserializeOwned + Default>(request: Request) -> Result<Response> {
|
||||||
Ok(match request.user::<UserData>()? {
|
Ok(match request.user::<UserData>()? {
|
||||||
User::Unauthenticated => {
|
User::Unauthenticated => {
|
||||||
|
@ -289,13 +323,17 @@ fn render_settings_menu<UserData: Serialize + DeserializeOwned>(
|
||||||
user: RegisteredUser<UserData>,
|
user: RegisteredUser<UserData>,
|
||||||
redirect: &str
|
redirect: &str
|
||||||
) -> Response {
|
) -> Response {
|
||||||
Document::new()
|
let mut document = Document::new();
|
||||||
|
document
|
||||||
.add_heading(HeadingLevel::H1, "User Settings")
|
.add_heading(HeadingLevel::H1, "User Settings")
|
||||||
.add_blank_line()
|
.add_blank_line()
|
||||||
.add_text(&format!("Welcome {}!", user.username()))
|
.add_text(&format!("Welcome {}!", user.username()))
|
||||||
.add_blank_line()
|
.add_blank_line()
|
||||||
.add_link(redirect, "Back to the app")
|
.add_link(redirect, "Back to the app")
|
||||||
.add_blank_line()
|
.add_blank_line();
|
||||||
|
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
|
document
|
||||||
.add_text(
|
.add_text(
|
||||||
if user.has_password() {
|
if user.has_password() {
|
||||||
concat!(
|
concat!(
|
||||||
|
@ -312,8 +350,9 @@ fn render_settings_menu<UserData: Serialize + DeserializeOwned>(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.add_blank_line()
|
.add_blank_line()
|
||||||
.add_link("/account/password", if user.has_password() { "Change password" } else { "Set password" })
|
.add_link("/account/password", if user.has_password() { "Change password" } else { "Set password" });
|
||||||
.into()
|
|
||||||
|
document.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
mod private {
|
mod private {
|
||||||
|
|
|
@ -21,6 +21,7 @@ use crate::user_management::UserManager;
|
||||||
use crate::user_management::Result;
|
use crate::user_management::Result;
|
||||||
use crate::user_management::manager::CertificateData;
|
use crate::user_management::manager::CertificateData;
|
||||||
|
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
const ARGON2_CONFIG: argon2::Config = argon2::Config {
|
const ARGON2_CONFIG: argon2::Config = argon2::Config {
|
||||||
ad: &[],
|
ad: &[],
|
||||||
hash_length: 32,
|
hash_length: 32,
|
||||||
|
@ -33,6 +34,7 @@ const ARGON2_CONFIG: argon2::Config = argon2::Config {
|
||||||
version: argon2::Version::Version13,
|
version: argon2::Version::Version13,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
static ref RANDOM: ring::rand::SystemRandom = ring::rand::SystemRandom::new();
|
static ref RANDOM: ring::rand::SystemRandom = ring::rand::SystemRandom::new();
|
||||||
}
|
}
|
||||||
|
@ -45,6 +47,7 @@ lazy_static::lazy_static! {
|
||||||
pub (crate) struct PartialUser<UserData> {
|
pub (crate) struct PartialUser<UserData> {
|
||||||
pub data: UserData,
|
pub data: UserData,
|
||||||
pub certificates: Vec<u32>,
|
pub certificates: Vec<u32>,
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
pub pass_hash: Option<(Vec<u8>, [u8; 32])>,
|
pub pass_hash: Option<(Vec<u8>, [u8; 32])>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,6 +123,7 @@ impl NotSignedInUser {
|
||||||
PartialUser {
|
PartialUser {
|
||||||
data: UserData::default(),
|
data: UserData::default(),
|
||||||
certificates: Vec::with_capacity(1),
|
certificates: Vec::with_capacity(1),
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
pass_hash: None,
|
pass_hash: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -133,6 +137,7 @@ impl NotSignedInUser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
/// Attach this certificate to an existing user
|
/// Attach this certificate to an existing user
|
||||||
///
|
///
|
||||||
/// Try to add this certificate to another user using a username and password. If
|
/// Try to add this certificate to another user using a username and password. If
|
||||||
|
@ -249,6 +254,7 @@ impl<UserData: Serialize + DeserializeOwned> RegisteredUser<UserData> {
|
||||||
&self.username
|
&self.username
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
/// Check a password against the user's password hash
|
/// Check a password against the user's password hash
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
|
@ -270,6 +276,7 @@ impl<UserData: Serialize + DeserializeOwned> RegisteredUser<UserData> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
/// Set's the password for this user
|
/// Set's the password for this user
|
||||||
///
|
///
|
||||||
/// By default, users have no password, meaning the cannot add any certificates beyond
|
/// By default, users have no password, meaning the cannot add any certificates beyond
|
||||||
|
@ -352,6 +359,7 @@ impl<UserData: Serialize + DeserializeOwned> RegisteredUser<UserData> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "user_management_advanced")]
|
||||||
/// Check if the user has a password set
|
/// Check if the user has a password set
|
||||||
///
|
///
|
||||||
/// Since authentication is done using client certificates, users aren't required to
|
/// Since authentication is done using client certificates, users aren't required to
|
||||||
|
|
Loading…
Reference in a new issue