mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 04:42:15 +00:00
Implement Nextcloud login flow
This commit is contained in:
@@ -27,6 +27,7 @@ derive_more = { workspace = true }
|
||||
rustical_xml.workspace = true
|
||||
toml.workspace = true
|
||||
tokio.workspace = true
|
||||
rand.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
rstest = { workspace = true }
|
||||
|
||||
@@ -8,6 +8,7 @@ use async_trait::async_trait;
|
||||
pub trait AuthenticationProvider: 'static {
|
||||
async fn get_principal(&self, id: &str) -> Result<Option<User>, crate::Error>;
|
||||
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error>;
|
||||
async fn add_app_token(&self, user_id: &str, name: String, token: String) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub use middleware::AuthenticationMiddleware;
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
use super::AuthenticationProvider;
|
||||
use super::{user::AppToken, AuthenticationProvider};
|
||||
use crate::{auth::User, error::Error};
|
||||
use anyhow::anyhow;
|
||||
use async_trait::async_trait;
|
||||
use password_hash::PasswordHasher;
|
||||
use pbkdf2::{
|
||||
password_hash::{self, rand_core::OsRng, SaltString},
|
||||
Params,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::HashMap, fs, io};
|
||||
use std::{collections::HashMap, fs, io, ops::Deref};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||
@@ -18,6 +24,7 @@ pub struct TomlUserStoreConfig {
|
||||
#[derive(Debug)]
|
||||
pub struct TomlPrincipalStore {
|
||||
pub principals: RwLock<HashMap<String, User>>,
|
||||
config: TomlUserStoreConfig,
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
@@ -35,8 +42,21 @@ impl TomlPrincipalStore {
|
||||
principals: RwLock::new(HashMap::from_iter(
|
||||
principals.into_iter().map(|user| (user.id.clone(), user)),
|
||||
)),
|
||||
config,
|
||||
})
|
||||
}
|
||||
|
||||
fn save(&self, principals: &HashMap<String, User>) -> Result<(), Error> {
|
||||
let out = toml::to_string_pretty(&TomlDataModel {
|
||||
principals: principals
|
||||
.iter()
|
||||
.map(|(_, value)| value.to_owned())
|
||||
.collect(),
|
||||
})
|
||||
.map_err(|_| anyhow!("Error saving principal database"))?;
|
||||
fs::write(&self.config.path, out)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -70,4 +90,32 @@ impl AuthenticationProvider for TomlPrincipalStore {
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn add_app_token(&self, user_id: &str, name: String, token: String) -> Result<(), Error> {
|
||||
let mut principals = self.principals.write().await;
|
||||
if let Some(principal) = principals.get_mut(user_id) {
|
||||
let salt = SaltString::generate(OsRng);
|
||||
let token_hash = pbkdf2::Pbkdf2
|
||||
.hash_password_customized(
|
||||
token.as_bytes(),
|
||||
None,
|
||||
None,
|
||||
Params {
|
||||
rounds: 1000,
|
||||
..Default::default()
|
||||
},
|
||||
&salt,
|
||||
)
|
||||
.map_err(|_| Error::PasswordHash)?
|
||||
.to_string();
|
||||
principal.app_tokens.push(AppToken {
|
||||
name,
|
||||
token: token_hash,
|
||||
});
|
||||
self.save(principals.deref())?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::NotFound)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
use actix_web::{http::StatusCode, ResponseError};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
||||
pub enum Error {
|
||||
#[error("Not found")]
|
||||
NotFound,
|
||||
@@ -15,6 +14,12 @@ pub enum Error {
|
||||
#[error("Read-only")]
|
||||
ReadOnly,
|
||||
|
||||
#[error("Error generating password hash")]
|
||||
PasswordHash,
|
||||
|
||||
#[error(transparent)]
|
||||
IO(#[from] std::io::Error),
|
||||
|
||||
#[error(transparent)]
|
||||
ParserError(#[from] ical::parser::ParserError),
|
||||
|
||||
|
||||
Reference in New Issue
Block a user