Added user management API [WIP]
This commit is contained in:
parent
070ed2a222
commit
0e2f8d5f62
13
Cargo.toml
13
Cargo.toml
|
@ -8,6 +8,10 @@ description = "Gemini server implementation"
|
|||
repository = "https://github.com/panicbit/northstar"
|
||||
documentation = "https://docs.rs/northstar"
|
||||
|
||||
[features]
|
||||
# default = ["user_management"]
|
||||
user_management = ["sled", "bincode", "serde/derive", "bcrypt", "crc32fast"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.33"
|
||||
rustls = { version = "0.18.1", features = ["dangerous_configuration"] }
|
||||
|
@ -21,6 +25,15 @@ itertools = "0.9.0"
|
|||
log = "0.4.11"
|
||||
webpki = "0.21.0"
|
||||
lazy_static = "1.4.0"
|
||||
sled = { version = "0.34.6", optional = true }
|
||||
bincode = { version = "1.3.1", optional = true }
|
||||
serde = { version = "1.0", optional = true }
|
||||
bcrypt = { version = "0.9", optional = true }
|
||||
crc32fast = { version = "1.2.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.8.1"
|
||||
|
||||
[[example]]
|
||||
name = "user_management"
|
||||
required-features = ["user_management"]
|
||||
|
|
65
examples/user_management.rs
Normal file
65
examples/user_management.rs
Normal file
|
@ -0,0 +1,65 @@
|
|||
use anyhow::*;
|
||||
use futures::{future::BoxFuture, FutureExt};
|
||||
use log::LevelFilter;
|
||||
use northstar::{
|
||||
GEMINI_MIME,
|
||||
GEMINI_PORT,
|
||||
Request,
|
||||
Response,
|
||||
Server,
|
||||
user_management::{User, UserManagerError},
|
||||
};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
env_logger::builder()
|
||||
.filter_module("northstar", LevelFilter::Debug)
|
||||
.init();
|
||||
|
||||
Server::bind(("0.0.0.0", GEMINI_PORT))
|
||||
.serve(handle_request)
|
||||
.await
|
||||
}
|
||||
|
||||
/// An ultra-simple demonstration of simple authentication.
|
||||
///
|
||||
/// If the user attempts to connect, they will be prompted to create a client certificate.
|
||||
/// Once they've made one, they'll be given the opportunity to create an account by
|
||||
/// selecting a username. They'll then get a message confirming their account creation.
|
||||
/// Any time this user visits the site in the future, they'll get a personalized welcome
|
||||
/// message.
|
||||
fn handle_request(request: Request) -> BoxFuture<'static, Result<Response>> {
|
||||
async move {
|
||||
Ok(match request.user::<String>()? {
|
||||
User::Unauthenticated => {
|
||||
Response::client_certificate_required()
|
||||
},
|
||||
User::NotSignedIn(user) => {
|
||||
if let Some(username) = request.input() {
|
||||
match user.register::<String>(username.to_owned()) {
|
||||
Ok(_user) => Response::success(&GEMINI_MIME)
|
||||
.with_body("Your account has been created!\n=>/ Begin"),
|
||||
Err(UserManagerError::UsernameNotUnique) =>
|
||||
Response::input_lossy("That username is taken. Try again"),
|
||||
Err(e) => panic!("Unexpected error: {}", e),
|
||||
}
|
||||
} else {
|
||||
Response::input_lossy("Please pick a username")
|
||||
}
|
||||
},
|
||||
User::SignedIn(mut user) => {
|
||||
if request.path_segments()[0].eq("push") { // User connecting to /push
|
||||
if let Some(push) = request.input() {
|
||||
user.as_mut().push_str(push);
|
||||
user.save();
|
||||
} else {
|
||||
return Ok(Response::input_lossy("Enter a string to push"));
|
||||
}
|
||||
}
|
||||
|
||||
Response::success(&GEMINI_MIME)
|
||||
.with_body(format!("Your current string: {}\n=> /push Push", user.as_ref()))
|
||||
}
|
||||
})
|
||||
}.boxed()
|
||||
}
|
106
src/lib.rs
106
src/lib.rs
|
@ -1,6 +1,6 @@
|
|||
#[macro_use] extern crate log;
|
||||
|
||||
use std::{panic::AssertUnwindSafe, convert::TryFrom, io::BufReader, sync::Arc};
|
||||
use std::{panic::AssertUnwindSafe, convert::TryFrom, io::BufReader, sync::Arc, path::PathBuf};
|
||||
use futures::{future::BoxFuture, FutureExt};
|
||||
use tokio::{
|
||||
prelude::*,
|
||||
|
@ -17,6 +17,11 @@ use lazy_static::lazy_static;
|
|||
|
||||
pub mod types;
|
||||
pub mod util;
|
||||
#[cfg(feature = "user_management")]
|
||||
pub mod user_management;
|
||||
|
||||
#[cfg(feature="user_management")]
|
||||
use user_management::UserManager;
|
||||
|
||||
pub use mime;
|
||||
pub use uriparse as uri;
|
||||
|
@ -33,6 +38,8 @@ pub struct Server {
|
|||
tls_acceptor: TlsAcceptor,
|
||||
listener: Arc<TcpListener>,
|
||||
handler: Handler,
|
||||
#[cfg(feature="user_management")]
|
||||
manager: UserManager,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
|
@ -59,8 +66,13 @@ impl Server {
|
|||
.context("Failed to establish TLS session")?;
|
||||
let mut stream = BufStream::new(stream);
|
||||
|
||||
let mut request = receive_request(&mut stream).await
|
||||
#[cfg(feature="user_management")]
|
||||
let mut request = self.receive_request(&mut stream).await
|
||||
.context("Failed to receive request")?;
|
||||
#[cfg(not(feature="user_management"))]
|
||||
let mut request = Self::receive_request(&mut stream).await
|
||||
.context("Failed to receive request")?;
|
||||
|
||||
debug!("Client requested: {}", request.uri());
|
||||
|
||||
// Identify the client certificate from the tls stream. This is the first
|
||||
|
@ -92,38 +104,12 @@ impl Server {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Builder<A> {
|
||||
addr: A,
|
||||
}
|
||||
|
||||
impl<A: ToSocketAddrs> Builder<A> {
|
||||
fn bind(addr: A) -> Self {
|
||||
Self { addr }
|
||||
}
|
||||
|
||||
pub async fn serve<F>(self, handler: F) -> Result<()>
|
||||
where
|
||||
F: Fn(Request) -> HandlerResponse + Send + Sync + 'static,
|
||||
{
|
||||
let config = tls_config()
|
||||
.context("Failed to create TLS config")?;
|
||||
|
||||
let listener = TcpListener::bind(self.addr).await
|
||||
.context("Failed to create socket")?;
|
||||
|
||||
let server = Server {
|
||||
tls_acceptor: TlsAcceptor::from(config),
|
||||
listener: Arc::new(listener),
|
||||
handler: Arc::new(handler),
|
||||
};
|
||||
|
||||
server.serve().await
|
||||
}
|
||||
}
|
||||
|
||||
async fn receive_request(stream: &mut (impl AsyncBufRead + Unpin)) -> Result<Request> {
|
||||
async fn receive_request(
|
||||
#[cfg(feature="user_management")]
|
||||
&self,
|
||||
stream: &mut (impl AsyncBufRead + Unpin)
|
||||
) -> Result<Request> {
|
||||
let limit = REQUEST_URI_MAX_LEN + "\r\n".len();
|
||||
let mut stream = stream.take(limit as u64);
|
||||
let mut uri = Vec::new();
|
||||
|
@ -145,10 +131,60 @@ async fn receive_request(stream: &mut (impl AsyncBufRead + Unpin)) -> Result<Req
|
|||
let uri = URIReference::try_from(&*uri)
|
||||
.context("Request URI is invalid")?
|
||||
.into_owned();
|
||||
let request = Request::from_uri(uri)
|
||||
.context("Failed to create request from URI")?;
|
||||
let request = Request::from_uri(
|
||||
uri,
|
||||
#[cfg(feature="user_management")]
|
||||
self.manager.clone(),
|
||||
) .context("Failed to create request from URI")?;
|
||||
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Builder<A> {
|
||||
addr: A,
|
||||
#[cfg(feature="user_management")]
|
||||
data_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl<A: ToSocketAddrs> Builder<A> {
|
||||
fn bind(addr: A) -> Self {
|
||||
Self {
|
||||
addr,
|
||||
#[cfg(feature="user_management")]
|
||||
data_dir: "data".into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature="user_management")]
|
||||
/// Sets the directory to store user data in
|
||||
///
|
||||
/// Defaults to `./data` if not specified
|
||||
pub fn set_database_dir(mut self, path: impl Into<PathBuf>) -> Self {
|
||||
self.data_dir = path.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub async fn serve<F>(self, handler: F) -> Result<()>
|
||||
where
|
||||
F: Fn(Request) -> HandlerResponse + Send + Sync + 'static,
|
||||
{
|
||||
let config = tls_config()
|
||||
.context("Failed to create TLS config")?;
|
||||
|
||||
let listener = TcpListener::bind(self.addr).await
|
||||
.context("Failed to create socket")?;
|
||||
|
||||
let server = Server {
|
||||
tls_acceptor: TlsAcceptor::from(config),
|
||||
listener: Arc::new(listener),
|
||||
handler: Arc::new(handler),
|
||||
#[cfg(feature="user_management")]
|
||||
manager: UserManager::new(self.data_dir)?,
|
||||
};
|
||||
|
||||
server.serve().await
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_response(mut response: Response, stream: &mut (impl AsyncWrite + Unpin)) -> Result<()> {
|
||||
|
|
|
@ -3,21 +3,39 @@ use anyhow::*;
|
|||
use percent_encoding::percent_decode_str;
|
||||
use uriparse::URIReference;
|
||||
use rustls::Certificate;
|
||||
#[cfg(feature="user_management")]
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
|
||||
#[cfg(feature="user_management")]
|
||||
use crate::user_management::{UserManager, User};
|
||||
|
||||
pub struct Request {
|
||||
uri: URIReference<'static>,
|
||||
input: Option<String>,
|
||||
certificate: Option<Certificate>,
|
||||
#[cfg(feature="user_management")]
|
||||
manager: UserManager,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn from_uri(uri: URIReference<'static>) -> Result<Self> {
|
||||
Self::with_certificate(uri, None)
|
||||
pub fn from_uri(
|
||||
uri: URIReference<'static>,
|
||||
#[cfg(feature="user_management")]
|
||||
manager: UserManager,
|
||||
) -> Result<Self> {
|
||||
Self::with_certificate(
|
||||
uri,
|
||||
None,
|
||||
#[cfg(feature="user_management")]
|
||||
manager
|
||||
)
|
||||
}
|
||||
|
||||
pub fn with_certificate(
|
||||
mut uri: URIReference<'static>,
|
||||
certificate: Option<Certificate>
|
||||
certificate: Option<Certificate>,
|
||||
#[cfg(feature="user_management")]
|
||||
manager: UserManager,
|
||||
) -> Result<Self> {
|
||||
uri.normalize();
|
||||
|
||||
|
@ -36,6 +54,8 @@ impl Request {
|
|||
uri,
|
||||
input,
|
||||
certificate,
|
||||
#[cfg(feature="user_management")]
|
||||
manager,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -63,6 +83,18 @@ impl Request {
|
|||
pub fn certificate(&self) -> Option<&Certificate> {
|
||||
self.certificate.as_ref()
|
||||
}
|
||||
|
||||
#[cfg(feature="user_management")]
|
||||
/// Attempt to determine the user who sent this request
|
||||
///
|
||||
/// May return a variant depending on if the client used a client certificate, and if
|
||||
/// they've registered as a user yet.
|
||||
pub fn user<UserData>(&self) -> Result<User<UserData>>
|
||||
where
|
||||
UserData: Serialize + DeserializeOwned
|
||||
{
|
||||
Ok(self.manager.get_user(self.certificate())?)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Deref for Request {
|
||||
|
|
121
src/user_management/manager.rs
Normal file
121
src/user_management/manager.rs
Normal file
|
@ -0,0 +1,121 @@
|
|||
use rustls::Certificate;
|
||||
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
||||
|
||||
use std::path::Path;
|
||||
|
||||
use crate::user_management::{User, Result};
|
||||
use crate::user_management::user::{SignedInUser, NotSignedInUser, UserInner};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
/// Data stored in the certificate tree about a certain certificate
|
||||
pub struct CertificateData {
|
||||
#[serde(with = "CertificateDef")]
|
||||
pub certificate: Certificate,
|
||||
pub owner_username: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(remote = "Certificate")]
|
||||
struct CertificateDef(Vec<u8>);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
/// A struct containing information for managing users.
|
||||
///
|
||||
/// Wraps a [`sled::Db`]
|
||||
pub struct UserManager {
|
||||
db: sled::Db,
|
||||
pub (crate) users: sled::Tree, // user_id:String maps to data:UserData
|
||||
pub (crate) certificates: sled::Tree, // certificate:u64 maps to data:CertificateData
|
||||
}
|
||||
|
||||
impl UserManager {
|
||||
|
||||
/// Create or open a new UserManager
|
||||
///
|
||||
/// The `dir` argument is the path to a data directory, to be populated using sled.
|
||||
/// This will be created if it does not exist.
|
||||
pub fn new(dir: impl AsRef<Path>) -> Result<Self> {
|
||||
let db = sled::open(dir)?;
|
||||
Ok(Self {
|
||||
users: db.open_tree("users")?,
|
||||
certificates: db.open_tree("certificates")?,
|
||||
db,
|
||||
})
|
||||
}
|
||||
|
||||
/// Produce a u32 hash from a certificate, used for [`lookup_certficate()`]
|
||||
pub fn hash_certificate(cert: &Certificate) -> u32 {
|
||||
let mut hasher = crc32fast::Hasher::new();
|
||||
hasher.update(cert.0.as_ref());
|
||||
hasher.finalize()
|
||||
}
|
||||
|
||||
/// Lookup information about a certificate based on it's u32 hash
|
||||
///
|
||||
/// # Errors
|
||||
/// An error is thrown if there is an error reading from the database or if data
|
||||
/// recieved from the database is corrupt
|
||||
pub fn lookup_certificate(&self, cert: u32) -> Result<Option<CertificateData>> {
|
||||
if let Some(bytes) = self.certificates.get(cert.to_le_bytes())? {
|
||||
Ok(Some(bincode::deserialize(&bytes)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Lookup information about a user by username
|
||||
///
|
||||
/// # Errors
|
||||
/// An error is thrown if there is an error reading from the database or if data
|
||||
/// recieved from the database is corrupt
|
||||
pub fn lookup_user<'de, UserData>(
|
||||
&self,
|
||||
username: impl AsRef<[u8]>
|
||||
) -> Result<Option<UserInner<UserData>>>
|
||||
where
|
||||
UserData: Serialize + DeserializeOwned
|
||||
{
|
||||
if let Some(bytes) = self.users.get(username)? {
|
||||
Ok(Some(bincode::deserialize_from(bytes.as_ref())?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to determine the user who sent a request based on the certificate.
|
||||
///
|
||||
/// # Errors
|
||||
/// An error is thrown if there is an error reading from the database or if data
|
||||
/// recieved from the database is corrupt
|
||||
///
|
||||
/// # Panics
|
||||
/// Pancis if the database is corrupt
|
||||
pub fn get_user<'de, UserData>(
|
||||
&self,
|
||||
cert: Option<&Certificate>
|
||||
) -> Result<User<UserData>>
|
||||
where
|
||||
UserData: Serialize + DeserializeOwned
|
||||
{
|
||||
if let Some(certificate) = cert {
|
||||
let cert_hash = Self::hash_certificate(certificate);
|
||||
if let Some(certificate_data) = self.lookup_certificate(cert_hash)? {
|
||||
let user_inner = self.lookup_user(&certificate_data.owner_username)?
|
||||
.expect("Database corruption: Certificate data refers to non-existant user");
|
||||
Ok(User::SignedIn(SignedInUser {
|
||||
username: certificate_data.owner_username,
|
||||
active_certificate: certificate_data.certificate,
|
||||
manager: self.clone(),
|
||||
inner: user_inner,
|
||||
}))
|
||||
} else {
|
||||
Ok(User::NotSignedIn(NotSignedInUser {
|
||||
certificate: certificate.clone(),
|
||||
manager: self.clone(),
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
Ok(User::Unauthenticated)
|
||||
}
|
||||
}
|
||||
}
|
71
src/user_management/mod.rs
Normal file
71
src/user_management/mod.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
pub mod user;
|
||||
mod manager;
|
||||
pub use manager::UserManager;
|
||||
pub use user::User;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UserManagerError {
|
||||
UsernameNotUnique,
|
||||
PasswordNotSet,
|
||||
DatabaseError(sled::Error),
|
||||
DatabaseTransactionError(sled::transaction::TransactionError),
|
||||
DeserializeError(bincode::Error),
|
||||
BcryptError(bcrypt::BcryptError),
|
||||
}
|
||||
|
||||
impl From<sled::Error> for UserManagerError {
|
||||
fn from(error: sled::Error) -> Self {
|
||||
Self::DatabaseError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<sled::transaction::TransactionError> for UserManagerError {
|
||||
fn from(error: sled::transaction::TransactionError) -> Self {
|
||||
Self::DatabaseTransactionError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bincode::Error> for UserManagerError {
|
||||
fn from(error: bincode::Error) -> Self {
|
||||
Self::DeserializeError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bcrypt::BcryptError> for UserManagerError {
|
||||
fn from(error: bcrypt::BcryptError) -> Self {
|
||||
Self::BcryptError(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for UserManagerError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
Self::DatabaseError(e) => Some(e),
|
||||
Self::DatabaseTransactionError(e) => Some(e),
|
||||
Self::DeserializeError(e) => Some(e),
|
||||
Self::BcryptError(e) => Some(e),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UserManagerError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
|
||||
match self {
|
||||
Self::UsernameNotUnique =>
|
||||
write!(f, "Attempted to create a user using a username that's already been taken"),
|
||||
Self::PasswordNotSet =>
|
||||
write!(f, "Attempted to check the password of a user who has not set one yet"),
|
||||
Self::DatabaseError(e) =>
|
||||
write!(f, "Error accessing the user database: {}", e),
|
||||
Self::DatabaseTransactionError(e) =>
|
||||
write!(f, "Error accessing the user database: {}", e),
|
||||
Self::DeserializeError(e) =>
|
||||
write!(f, "Recieved messy data from database, possible corruption: {}", e),
|
||||
Self::BcryptError(e) =>
|
||||
write!(f, "Bcrypt Error, likely malformed password hash, possible database corruption: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, UserManagerError>;
|
203
src/user_management/user.rs
Normal file
203
src/user_management/user.rs
Normal file
|
@ -0,0 +1,203 @@
|
|||
use rustls::Certificate;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sled::Transactional;
|
||||
|
||||
use crate::user_management::UserManager;
|
||||
use crate::user_management::Result;
|
||||
use crate::user_management::manager::CertificateData;
|
||||
|
||||
/// An struct corresponding to the data stored in the user tree
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct UserInner<UserData> {
|
||||
data: UserData,
|
||||
certificates: Vec<u32>,
|
||||
pass_hash: Option<String>,
|
||||
}
|
||||
|
||||
/// Any information about the connecting user
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum User<UserData> {
|
||||
/// A user who is connected without using a client certificate.
|
||||
///
|
||||
/// This could be a user who has an account but just isn't presenting a certificate at
|
||||
/// the minute, a user whose client does not support client certificates, or a user
|
||||
/// who has not yet created a certificate for the site
|
||||
Unauthenticated,
|
||||
|
||||
/// A user who is connecting with a certificate that isn't connected to an account
|
||||
///
|
||||
/// This is typically a new user who hasn't set up an account yet, or a user
|
||||
/// connecting with a new certificate that needs to be added to an existing account.
|
||||
NotSignedIn(NotSignedInUser),
|
||||
|
||||
/// A user connecting with an identified account
|
||||
SignedIn(SignedInUser<UserData>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// Data about a user with a certificate not associated with an account
|
||||
///
|
||||
/// For more information about the user lifecycle and sign-in stages, see [`User`]
|
||||
pub struct NotSignedInUser {
|
||||
pub (crate) certificate: Certificate,
|
||||
pub (crate) manager: UserManager,
|
||||
}
|
||||
|
||||
impl NotSignedInUser {
|
||||
/// Register a new user with this certificate
|
||||
///
|
||||
/// # Errors
|
||||
/// The provided username must be unique, or else an error will be raised.
|
||||
///
|
||||
/// Additional errors might occur if there is a problem writing to the database
|
||||
pub fn register<UserData>(
|
||||
self,
|
||||
username: String,
|
||||
) -> Result<SignedInUser<UserData>>
|
||||
where
|
||||
UserData: Serialize + Default
|
||||
{
|
||||
if self.manager.users.contains_key(username.as_str())? {
|
||||
Err(super::UserManagerError::UsernameNotUnique)
|
||||
} else {
|
||||
let cert_hash = UserManager::hash_certificate(&self.certificate);
|
||||
|
||||
let newser = SignedInUser {
|
||||
inner: UserInner {
|
||||
data: UserData::default(),
|
||||
certificates: vec![cert_hash],
|
||||
pass_hash: None,
|
||||
},
|
||||
username: username.clone(),
|
||||
active_certificate: self.certificate.clone(),
|
||||
manager: self.manager,
|
||||
};
|
||||
|
||||
let cert_info = CertificateData {
|
||||
certificate: self.certificate,
|
||||
owner_username: username,
|
||||
};
|
||||
|
||||
let newser_serialized = bincode::serialize(&newser.inner)?;
|
||||
let cert_info_serialized = bincode::serialize(&cert_info)?;
|
||||
|
||||
(&newser.manager.users, &newser.manager.certificates)
|
||||
.transaction(|(tx_usr, tx_crt)| {
|
||||
tx_usr.insert(
|
||||
newser.username.as_str(),
|
||||
newser_serialized.clone(),
|
||||
)?;
|
||||
tx_crt.insert(
|
||||
cert_hash.to_le_bytes().as_ref(),
|
||||
cert_info_serialized.clone(),
|
||||
)?;
|
||||
Ok(())
|
||||
})?; //TODO
|
||||
|
||||
Ok(newser)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach this certificate to an existing user
|
||||
///
|
||||
/// Try to add this certificate to another user using a username and password
|
||||
///
|
||||
/// # Errors
|
||||
/// This will error if the username and password are incorrect, or if the user has yet
|
||||
/// to set a password.
|
||||
///
|
||||
/// Additional errors might occur if an error occurs during database lookup and
|
||||
/// deserialization
|
||||
pub fn attach<'de, UserData>(
|
||||
username: impl AsRef<[u8]>,
|
||||
password: impl AsRef<[u8]>,
|
||||
) -> Result<SignedInUser<UserData>>
|
||||
where
|
||||
UserData: Serialize + Deserialize<'de>
|
||||
{
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// Data about a logged in user
|
||||
///
|
||||
/// For more information about the user lifecycle and sign-in stages, see [`User`]
|
||||
pub struct SignedInUser<UserData> {
|
||||
pub (crate) username: String,
|
||||
pub (crate) active_certificate: Certificate,
|
||||
pub (crate) manager: UserManager,
|
||||
pub (crate) inner: UserInner<UserData>,
|
||||
}
|
||||
|
||||
impl<UserData> SignedInUser<UserData> {
|
||||
/// Get the [`Certificate`] that the user is currently using to connect.
|
||||
pub const fn active_certificate(&self) -> &Certificate {
|
||||
&self.active_certificate
|
||||
}
|
||||
|
||||
/// Produce a list of all [`Certificate`]s registered to this account
|
||||
pub fn all_certificates(&self) -> Vec<Certificate> {
|
||||
self.inner.certificates
|
||||
.iter()
|
||||
.map(
|
||||
|cid| self.manager.lookup_certificate(*cid)
|
||||
.expect("Database corruption: User refers to non-existant certificate")
|
||||
.expect("Error accessing database")
|
||||
.certificate
|
||||
)
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get the user's current username.
|
||||
///
|
||||
/// NOTE: This is not guaranteed not to change.
|
||||
pub const fn username(&self) -> &String {
|
||||
&self.username
|
||||
}
|
||||
|
||||
/// Check a password against the user's password hash
|
||||
///
|
||||
/// # Errors
|
||||
/// An error is raised if the user has yet to set a password, or if the user's
|
||||
/// password hash is somehow malformed.
|
||||
pub fn check_password(
|
||||
&self,
|
||||
try_password: impl AsRef<[u8]>
|
||||
) -> Result<bool> {
|
||||
if let Some(hash) = &self.inner.pass_hash {
|
||||
Ok(bcrypt::verify(try_password, hash.as_str())?)
|
||||
} else {
|
||||
Err(super::UserManagerError::PasswordNotSet)
|
||||
}
|
||||
}
|
||||
|
||||
/// Write any updates to the user to the database.
|
||||
///
|
||||
/// Updates caused by calling methods directly on the user do not need to be saved.
|
||||
/// This is only for changes made to the UserData.
|
||||
pub fn save(&self) -> Result<()>
|
||||
where
|
||||
UserData: Serialize
|
||||
{
|
||||
self.manager.users.insert(
|
||||
&self.username,
|
||||
bincode::serialize(&self.inner)?,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<UserData> AsRef<UserData> for SignedInUser<UserData> {
|
||||
fn as_ref(&self) -> &UserData {
|
||||
&self.inner.data
|
||||
}
|
||||
}
|
||||
|
||||
impl<UserData> AsMut<UserData> for SignedInUser<UserData> {
|
||||
/// NOTE: Changes made to the user data won't be persisted until SignedInUser::save
|
||||
/// is called
|
||||
fn as_mut(&mut self) -> &mut UserData {
|
||||
&mut self.inner.data
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue