mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-14 04:42:15 +00:00
Breaking changes to auth provider, principal store outsourced to new config file
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@ crates/*/Cargo.lock
|
|||||||
|
|
||||||
db.sqlite3*
|
db.sqlite3*
|
||||||
config.toml
|
config.toml
|
||||||
|
principals.toml
|
||||||
|
|
||||||
/.idea
|
/.idea
|
||||||
|
|
||||||
|
|||||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2885,6 +2885,7 @@ dependencies = [
|
|||||||
"sha2",
|
"sha2",
|
||||||
"thiserror 2.0.11",
|
"thiserror 2.0.11",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -42,11 +42,29 @@ rustical gen-config
|
|||||||
```
|
```
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> `rustical gen-config` generates a random `frontend.secret_key`.
|
> The `rustical gen-config` command generates a random `frontend.secret_key`.
|
||||||
> This secret is used to generate session cookies so if it is leaked an attacker could use it to authenticate to against any endpoint (also when the frontend is disabled).
|
> This secret is used to generate session cookies so if it is leaked an attacker could use it to authenticate to against any endpoint (also when the frontend is disabled).
|
||||||
|
|
||||||
You'll have to set your database path to something like `/var/lib/rustical/db.sqlite3`.
|
You'll have to set your database path to something like `/var/lib/rustical/db.sqlite3`.
|
||||||
There you also set your username, password, and app tokens.
|
|
||||||
|
Next, configure the principals by creating a file specified in `auth.path` (by default `/etc/rustical/principals.toml`) and inserting your principals:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[principals]]
|
||||||
|
id = "user"
|
||||||
|
displayname = "User"
|
||||||
|
password = "$argon2id$......."
|
||||||
|
app_tokens = [
|
||||||
|
"$pbkdf2-sha256$........"
|
||||||
|
]
|
||||||
|
memberships = ["group:amazing_group"]
|
||||||
|
|
||||||
|
[[principals]]
|
||||||
|
id = "group:amazing_group"
|
||||||
|
user_type = "group"
|
||||||
|
displayname = "Amazing group"
|
||||||
|
```
|
||||||
|
|
||||||
Password hashes can be generated with
|
Password hashes can be generated with
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use calendar_set::CalendarSetResourceService;
|
|||||||
use principal::{PrincipalResource, PrincipalResourceService};
|
use principal::{PrincipalResource, PrincipalResourceService};
|
||||||
use rustical_dav::resource::{NamedRoute, ResourceService, ResourceServiceRoute};
|
use rustical_dav::resource::{NamedRoute, ResourceService, ResourceServiceRoute};
|
||||||
use rustical_dav::resources::RootResourceService;
|
use rustical_dav::resources::RootResourceService;
|
||||||
use rustical_store::auth::{AuthenticationMiddleware, AuthenticationProvider, UserStore};
|
use rustical_store::auth::{AuthenticationMiddleware, AuthenticationProvider};
|
||||||
use rustical_store::{AddressbookStore, CalendarStore, ContactBirthdayStore, SubscriptionStore};
|
use rustical_store::{AddressbookStore, CalendarStore, ContactBirthdayStore, SubscriptionStore};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use subscription::subscription_resource;
|
use subscription::subscription_resource;
|
||||||
@@ -25,13 +25,11 @@ mod subscription;
|
|||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
|
|
||||||
pub fn caldav_service<
|
pub fn caldav_service<
|
||||||
US: UserStore,
|
|
||||||
AP: AuthenticationProvider,
|
AP: AuthenticationProvider,
|
||||||
AS: AddressbookStore,
|
AS: AddressbookStore,
|
||||||
C: CalendarStore,
|
C: CalendarStore,
|
||||||
S: SubscriptionStore,
|
S: SubscriptionStore,
|
||||||
>(
|
>(
|
||||||
user_store: Arc<US>,
|
|
||||||
auth_provider: Arc<AP>,
|
auth_provider: Arc<AP>,
|
||||||
store: Arc<C>,
|
store: Arc<C>,
|
||||||
addr_store: Arc<AS>,
|
addr_store: Arc<AS>,
|
||||||
@@ -40,7 +38,7 @@ pub fn caldav_service<
|
|||||||
let birthday_store = Arc::new(ContactBirthdayStore::new(addr_store));
|
let birthday_store = Arc::new(ContactBirthdayStore::new(addr_store));
|
||||||
|
|
||||||
web::scope("")
|
web::scope("")
|
||||||
.wrap(AuthenticationMiddleware::new(auth_provider))
|
.wrap(AuthenticationMiddleware::new(auth_provider.clone()))
|
||||||
.wrap(
|
.wrap(
|
||||||
ErrorHandlers::new().handler(StatusCode::METHOD_NOT_ALLOWED, |res| {
|
ErrorHandlers::new().handler(StatusCode::METHOD_NOT_ALLOWED, |res| {
|
||||||
Ok(ErrorHandlerResponse::Response(
|
Ok(ErrorHandlerResponse::Response(
|
||||||
@@ -68,7 +66,7 @@ pub fn caldav_service<
|
|||||||
.service(
|
.service(
|
||||||
web::scope("/principal").service(
|
web::scope("/principal").service(
|
||||||
web::scope("/{principal}")
|
web::scope("/{principal}")
|
||||||
.service(PrincipalResourceService{store: user_store, home_set: &[
|
.service(PrincipalResourceService{auth_provider, home_set: &[
|
||||||
("calendar", false), ("birthdays", true)
|
("calendar", false), ("birthdays", true)
|
||||||
]}.actix_resource().name(PrincipalResource::route_name()))
|
]}.actix_resource().name(PrincipalResource::route_name()))
|
||||||
.service(web::scope("/calendar")
|
.service(web::scope("/calendar")
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use rustical_dav::privileges::UserPrivilegeSet;
|
|||||||
use rustical_dav::resource::{NamedRoute, Resource, ResourceService};
|
use rustical_dav::resource::{NamedRoute, Resource, ResourceService};
|
||||||
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
|
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
|
||||||
use rustical_store::auth::user::PrincipalType;
|
use rustical_store::auth::user::PrincipalType;
|
||||||
use rustical_store::auth::{User, UserStore};
|
use rustical_store::auth::{AuthenticationProvider, User};
|
||||||
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
|
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -131,13 +131,13 @@ impl Resource for PrincipalResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PrincipalResourceService<US: UserStore> {
|
pub struct PrincipalResourceService<AP: AuthenticationProvider> {
|
||||||
pub store: Arc<US>,
|
pub auth_provider: Arc<AP>,
|
||||||
pub home_set: &'static [(&'static str, bool)],
|
pub home_set: &'static [(&'static str, bool)],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl<US: UserStore> ResourceService for PrincipalResourceService<US> {
|
impl<AP: AuthenticationProvider> ResourceService for PrincipalResourceService<AP> {
|
||||||
type PathComponents = (String,);
|
type PathComponents = (String,);
|
||||||
type MemberType = CalendarSetResource;
|
type MemberType = CalendarSetResource;
|
||||||
type Resource = PrincipalResource;
|
type Resource = PrincipalResource;
|
||||||
@@ -148,8 +148,8 @@ impl<US: UserStore> ResourceService for PrincipalResourceService<US> {
|
|||||||
(principal,): &Self::PathComponents,
|
(principal,): &Self::PathComponents,
|
||||||
) -> Result<Self::Resource, Self::Error> {
|
) -> Result<Self::Resource, Self::Error> {
|
||||||
let user = self
|
let user = self
|
||||||
.store
|
.auth_provider
|
||||||
.get_user(principal)
|
.get_principal(principal)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(crate::Error::NotFound)?;
|
.ok_or(crate::Error::NotFound)?;
|
||||||
Ok(PrincipalResource {
|
Ok(PrincipalResource {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use principal::{PrincipalResource, PrincipalResourceService};
|
|||||||
use rustical_dav::resource::{NamedRoute, ResourceService};
|
use rustical_dav::resource::{NamedRoute, ResourceService};
|
||||||
use rustical_dav::resources::RootResourceService;
|
use rustical_dav::resources::RootResourceService;
|
||||||
use rustical_store::{
|
use rustical_store::{
|
||||||
auth::{AuthenticationMiddleware, AuthenticationProvider, UserStore},
|
auth::{AuthenticationMiddleware, AuthenticationProvider},
|
||||||
AddressbookStore, SubscriptionStore,
|
AddressbookStore, SubscriptionStore,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -27,12 +27,11 @@ pub mod principal;
|
|||||||
|
|
||||||
pub fn carddav_service<AP: AuthenticationProvider, A: AddressbookStore, S: SubscriptionStore>(
|
pub fn carddav_service<AP: AuthenticationProvider, A: AddressbookStore, S: SubscriptionStore>(
|
||||||
auth_provider: Arc<AP>,
|
auth_provider: Arc<AP>,
|
||||||
user_store: Arc<impl UserStore>,
|
|
||||||
store: Arc<A>,
|
store: Arc<A>,
|
||||||
subscription_store: Arc<S>,
|
subscription_store: Arc<S>,
|
||||||
) -> impl HttpServiceFactory {
|
) -> impl HttpServiceFactory {
|
||||||
web::scope("")
|
web::scope("")
|
||||||
.wrap(AuthenticationMiddleware::new(auth_provider))
|
.wrap(AuthenticationMiddleware::new(auth_provider.clone()))
|
||||||
.wrap(
|
.wrap(
|
||||||
ErrorHandlers::new().handler(StatusCode::METHOD_NOT_ALLOWED, |res| {
|
ErrorHandlers::new().handler(StatusCode::METHOD_NOT_ALLOWED, |res| {
|
||||||
Ok(ErrorHandlerResponse::Response(
|
Ok(ErrorHandlerResponse::Response(
|
||||||
@@ -60,7 +59,7 @@ pub fn carddav_service<AP: AuthenticationProvider, A: AddressbookStore, S: Subsc
|
|||||||
web::scope("/principal").service(
|
web::scope("/principal").service(
|
||||||
web::scope("/{principal}")
|
web::scope("/{principal}")
|
||||||
.service(
|
.service(
|
||||||
PrincipalResourceService::new(store.clone(), user_store)
|
PrincipalResourceService::new(store.clone(), auth_provider)
|
||||||
.actix_resource()
|
.actix_resource()
|
||||||
.name(PrincipalResource::route_name()),
|
.name(PrincipalResource::route_name()),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,21 +6,21 @@ use rustical_dav::extensions::{CommonPropertiesExtension, CommonPropertiesProp};
|
|||||||
use rustical_dav::privileges::UserPrivilegeSet;
|
use rustical_dav::privileges::UserPrivilegeSet;
|
||||||
use rustical_dav::resource::{NamedRoute, Resource, ResourceService};
|
use rustical_dav::resource::{NamedRoute, Resource, ResourceService};
|
||||||
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
|
use rustical_dav::xml::{HrefElement, Resourcetype, ResourcetypeInner};
|
||||||
use rustical_store::auth::{User, UserStore};
|
use rustical_store::auth::{AuthenticationProvider, User};
|
||||||
use rustical_store::AddressbookStore;
|
use rustical_store::AddressbookStore;
|
||||||
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
|
use rustical_xml::{EnumUnitVariants, EnumVariants, XmlDeserialize, XmlSerialize};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub struct PrincipalResourceService<A: AddressbookStore, US: UserStore> {
|
pub struct PrincipalResourceService<A: AddressbookStore, AP: AuthenticationProvider> {
|
||||||
addr_store: Arc<A>,
|
addr_store: Arc<A>,
|
||||||
user_store: Arc<US>,
|
auth_provider: Arc<AP>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A: AddressbookStore, US: UserStore> PrincipalResourceService<A, US> {
|
impl<A: AddressbookStore, AP: AuthenticationProvider> PrincipalResourceService<A, AP> {
|
||||||
pub fn new(addr_store: Arc<A>, user_store: Arc<US>) -> Self {
|
pub fn new(addr_store: Arc<A>, auth_provider: Arc<AP>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
addr_store,
|
addr_store,
|
||||||
user_store,
|
auth_provider,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,7 +133,9 @@ impl Resource for PrincipalResource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
impl<A: AddressbookStore, US: UserStore> ResourceService for PrincipalResourceService<A, US> {
|
impl<A: AddressbookStore, AP: AuthenticationProvider> ResourceService
|
||||||
|
for PrincipalResourceService<A, AP>
|
||||||
|
{
|
||||||
type PathComponents = (String,);
|
type PathComponents = (String,);
|
||||||
type MemberType = AddressbookResource;
|
type MemberType = AddressbookResource;
|
||||||
type Resource = PrincipalResource;
|
type Resource = PrincipalResource;
|
||||||
@@ -144,8 +146,8 @@ impl<A: AddressbookStore, US: UserStore> ResourceService for PrincipalResourceSe
|
|||||||
(principal,): &Self::PathComponents,
|
(principal,): &Self::PathComponents,
|
||||||
) -> Result<Self::Resource, Self::Error> {
|
) -> Result<Self::Resource, Self::Error> {
|
||||||
let user = self
|
let user = self
|
||||||
.user_store
|
.auth_provider
|
||||||
.get_user(principal)
|
.get_principal(principal)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(crate::Error::NotFound)?;
|
.ok_or(crate::Error::NotFound)?;
|
||||||
Ok(PrincipalResource { principal: user })
|
Ok(PrincipalResource { principal: user })
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ pbkdf2 = { workspace = true }
|
|||||||
chrono-tz = { workspace = true }
|
chrono-tz = { workspace = true }
|
||||||
derive_more = { workspace = true }
|
derive_more = { workspace = true }
|
||||||
rustical_xml.workspace = true
|
rustical_xml.workspace = true
|
||||||
|
toml.workspace = true
|
||||||
|
tokio.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rstest = { workspace = true }
|
rstest = { workspace = true }
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
pub mod middleware;
|
pub mod middleware;
|
||||||
pub mod static_user_store;
|
pub mod toml_user_store;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
mod user_store;
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait AuthenticationProvider: 'static {
|
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 validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use middleware::AuthenticationMiddleware;
|
pub use middleware::AuthenticationMiddleware;
|
||||||
pub use static_user_store::{StaticUserStore, StaticUserStoreConfig};
|
pub use toml_user_store::{TomlPrincipalStore, TomlUserStoreConfig};
|
||||||
pub use user::User;
|
pub use user::User;
|
||||||
pub use user_store::UserStore;
|
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
use crate::{auth::User, error::Error};
|
|
||||||
use async_trait::async_trait;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use super::{AuthenticationProvider, UserStore};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
|
||||||
pub struct StaticUserStoreConfig {
|
|
||||||
pub users: Vec<User>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct StaticUserStore {
|
|
||||||
pub users: HashMap<String, User>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StaticUserStore {
|
|
||||||
pub fn new(config: StaticUserStoreConfig) -> Self {
|
|
||||||
Self {
|
|
||||||
users: HashMap::from_iter(config.users.into_iter().map(|user| (user.id.clone(), user))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl UserStore for StaticUserStore {
|
|
||||||
async fn get_user(&self, id: &str) -> Result<Option<User>, crate::Error> {
|
|
||||||
Ok(self.users.get(id).cloned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl AuthenticationProvider for StaticUserStore {
|
|
||||||
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error> {
|
|
||||||
let user: User = match self.get_user(user_id).await? {
|
|
||||||
Some(user) => user,
|
|
||||||
None => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Try app tokens first since they are cheaper to calculate
|
|
||||||
// They can afford less iterations since they can be generated with high entropy
|
|
||||||
for app_token in &user.app_tokens {
|
|
||||||
if password_auth::verify_password(token, app_token).is_ok() {
|
|
||||||
return Ok(Some(user));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let password = match &user.password {
|
|
||||||
Some(password) => password,
|
|
||||||
None => return Ok(None),
|
|
||||||
};
|
|
||||||
|
|
||||||
if password_auth::verify_password(token, password).is_ok() {
|
|
||||||
return Ok(Some(user));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
73
crates/store/src/auth/toml_user_store.rs
Normal file
73
crates/store/src/auth/toml_user_store.rs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
use super::AuthenticationProvider;
|
||||||
|
use crate::{auth::User, error::Error};
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::{collections::HashMap, fs, io};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||||
|
struct TomlDataModel {
|
||||||
|
principals: Vec<User>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, Default)]
|
||||||
|
pub struct TomlUserStoreConfig {
|
||||||
|
pub path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TomlPrincipalStore {
|
||||||
|
pub principals: RwLock<HashMap<String, User>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum TomlStoreError {
|
||||||
|
#[error("IO error: {0}")]
|
||||||
|
Io(#[from] io::Error),
|
||||||
|
#[error("Error parsing users toml: {0}")]
|
||||||
|
Toml(#[from] toml::de::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TomlPrincipalStore {
|
||||||
|
pub fn new(config: TomlUserStoreConfig) -> Result<Self, TomlStoreError> {
|
||||||
|
let TomlDataModel { principals } = toml::from_str(&fs::read_to_string(&config.path)?)?;
|
||||||
|
Ok(Self {
|
||||||
|
principals: RwLock::new(HashMap::from_iter(
|
||||||
|
principals.into_iter().map(|user| (user.id.clone(), user)),
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl AuthenticationProvider for TomlPrincipalStore {
|
||||||
|
async fn get_principal(&self, id: &str) -> Result<Option<User>, crate::Error> {
|
||||||
|
Ok(self.principals.read().await.get(id).cloned())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error> {
|
||||||
|
let user: User = match self.get_principal(user_id).await? {
|
||||||
|
Some(user) => user,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try app tokens first since they are cheaper to calculate
|
||||||
|
// They can afford less iterations since they can be generated with high entropy
|
||||||
|
for app_token in &user.app_tokens {
|
||||||
|
if password_auth::verify_password(token, app_token).is_ok() {
|
||||||
|
return Ok(Some(user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let password = match &user.password {
|
||||||
|
Some(password) => password,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
|
||||||
|
if password_auth::verify_password(token, password).is_ok() {
|
||||||
|
return Ok(Some(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
use super::User;
|
|
||||||
use async_trait::async_trait;
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait UserStore: 'static {
|
|
||||||
async fn get_user(&self, id: &str) -> Result<Option<User>, crate::Error>;
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@ use actix_web::{web, App};
|
|||||||
use rustical_caldav::caldav_service;
|
use rustical_caldav::caldav_service;
|
||||||
use rustical_carddav::carddav_service;
|
use rustical_carddav::carddav_service;
|
||||||
use rustical_frontend::{configure_frontend, FrontendConfig};
|
use rustical_frontend::{configure_frontend, FrontendConfig};
|
||||||
use rustical_store::auth::{AuthenticationProvider, UserStore};
|
use rustical_store::auth::AuthenticationProvider;
|
||||||
use rustical_store::{AddressbookStore, CalendarStore, SubscriptionStore};
|
use rustical_store::{AddressbookStore, CalendarStore, SubscriptionStore};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing_actix_web::TracingLogger;
|
use tracing_actix_web::TracingLogger;
|
||||||
@@ -15,7 +15,6 @@ pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
|
|||||||
cal_store: Arc<CS>,
|
cal_store: Arc<CS>,
|
||||||
subscription_store: Arc<S>,
|
subscription_store: Arc<S>,
|
||||||
auth_provider: Arc<impl AuthenticationProvider>,
|
auth_provider: Arc<impl AuthenticationProvider>,
|
||||||
user_store: Arc<impl UserStore>,
|
|
||||||
frontend_config: FrontendConfig,
|
frontend_config: FrontendConfig,
|
||||||
) -> App<
|
) -> App<
|
||||||
impl ServiceFactory<
|
impl ServiceFactory<
|
||||||
@@ -31,7 +30,6 @@ pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
|
|||||||
.wrap(TracingLogger::default())
|
.wrap(TracingLogger::default())
|
||||||
.wrap(NormalizePath::trim())
|
.wrap(NormalizePath::trim())
|
||||||
.service(web::scope("/caldav").service(caldav_service(
|
.service(web::scope("/caldav").service(caldav_service(
|
||||||
user_store.clone(),
|
|
||||||
auth_provider.clone(),
|
auth_provider.clone(),
|
||||||
cal_store.clone(),
|
cal_store.clone(),
|
||||||
addr_store.clone(),
|
addr_store.clone(),
|
||||||
@@ -39,7 +37,6 @@ pub fn make_app<AS: AddressbookStore, CS: CalendarStore, S: SubscriptionStore>(
|
|||||||
)))
|
)))
|
||||||
.service(web::scope("/carddav").service(carddav_service(
|
.service(web::scope("/carddav").service(carddav_service(
|
||||||
auth_provider.clone(),
|
auth_provider.clone(),
|
||||||
user_store.clone(),
|
|
||||||
addr_store.clone(),
|
addr_store.clone(),
|
||||||
subscription_store,
|
subscription_store,
|
||||||
)))
|
)))
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use password_hash::PasswordHasher;
|
|||||||
use pbkdf2::Params;
|
use pbkdf2::Params;
|
||||||
use rand::{rngs::OsRng, RngCore};
|
use rand::{rngs::OsRng, RngCore};
|
||||||
use rustical_frontend::FrontendConfig;
|
use rustical_frontend::FrontendConfig;
|
||||||
use rustical_store::auth::{StaticUserStoreConfig, User};
|
use rustical_store::auth::TomlUserStoreConfig;
|
||||||
|
|
||||||
use crate::config::{
|
use crate::config::{
|
||||||
AuthConfig, Config, DataStoreConfig, DavPushConfig, HttpConfig, SqliteDataStoreConfig,
|
AuthConfig, Config, DataStoreConfig, DavPushConfig, HttpConfig, SqliteDataStoreConfig,
|
||||||
@@ -25,22 +25,8 @@ pub fn generate_frontend_secret() -> [u8; 64] {
|
|||||||
pub fn cmd_gen_config(_args: GenConfigArgs) -> anyhow::Result<()> {
|
pub fn cmd_gen_config(_args: GenConfigArgs) -> anyhow::Result<()> {
|
||||||
let config = Config {
|
let config = Config {
|
||||||
http: HttpConfig::default(),
|
http: HttpConfig::default(),
|
||||||
auth: AuthConfig::Static(StaticUserStoreConfig {
|
auth: AuthConfig::Toml(TomlUserStoreConfig {
|
||||||
users: vec![User {
|
path: "/etc/rustical/principals.toml".to_owned(),
|
||||||
id: "default".to_owned(),
|
|
||||||
displayname: Some("Default user".to_owned()),
|
|
||||||
user_type: Default::default(),
|
|
||||||
password: Some(
|
|
||||||
"generate a password hash with rustical pwhash --algorithm argon2".to_owned(),
|
|
||||||
),
|
|
||||||
app_tokens: vec![
|
|
||||||
"generate an app token hash with rustical pwhash --algorithm pbkdf2".to_owned(),
|
|
||||||
],
|
|
||||||
memberships: vec![
|
|
||||||
"Here you can specify other principals this principal should be a member of"
|
|
||||||
.to_owned(),
|
|
||||||
],
|
|
||||||
}],
|
|
||||||
}),
|
}),
|
||||||
data_store: DataStoreConfig::Sqlite(SqliteDataStoreConfig {
|
data_store: DataStoreConfig::Sqlite(SqliteDataStoreConfig {
|
||||||
db_url: "".to_owned(),
|
db_url: "".to_owned(),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use rustical_frontend::FrontendConfig;
|
use rustical_frontend::FrontendConfig;
|
||||||
use rustical_store::auth::StaticUserStoreConfig;
|
use rustical_store::auth::TomlUserStoreConfig;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
@@ -35,7 +35,7 @@ pub enum DataStoreConfig {
|
|||||||
#[serde(tag = "backend", rename_all = "snake_case")]
|
#[serde(tag = "backend", rename_all = "snake_case")]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub enum AuthConfig {
|
pub enum AuthConfig {
|
||||||
Static(StaticUserStoreConfig),
|
Toml(TomlUserStoreConfig),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Default)]
|
#[derive(Debug, Deserialize, Serialize, Default)]
|
||||||
|
|||||||
21
src/main.rs
21
src/main.rs
@@ -7,7 +7,7 @@ use clap::{Parser, Subcommand};
|
|||||||
use commands::{cmd_gen_config, cmd_pwhash};
|
use commands::{cmd_gen_config, cmd_pwhash};
|
||||||
use config::{DataStoreConfig, SqliteDataStoreConfig};
|
use config::{DataStoreConfig, SqliteDataStoreConfig};
|
||||||
use rustical_dav::push::push_notifier;
|
use rustical_dav::push::push_notifier;
|
||||||
use rustical_store::auth::StaticUserStore;
|
use rustical_store::auth::TomlPrincipalStore;
|
||||||
use rustical_store::{AddressbookStore, CalendarStore, CollectionOperation, SubscriptionStore};
|
use rustical_store::{AddressbookStore, CalendarStore, CollectionOperation, SubscriptionStore};
|
||||||
use rustical_store_sqlite::addressbook_store::SqliteAddressbookStore;
|
use rustical_store_sqlite::addressbook_store::SqliteAddressbookStore;
|
||||||
use rustical_store_sqlite::calendar_store::SqliteCalendarStore;
|
use rustical_store_sqlite::calendar_store::SqliteCalendarStore;
|
||||||
@@ -90,9 +90,9 @@ async fn main() -> Result<()> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_store = Arc::new(match config.auth {
|
let user_store = match config.auth {
|
||||||
config::AuthConfig::Static(config) => StaticUserStore::new(config),
|
config::AuthConfig::Toml(config) => Arc::new(TomlPrincipalStore::new(config)?),
|
||||||
});
|
};
|
||||||
|
|
||||||
HttpServer::new(move || {
|
HttpServer::new(move || {
|
||||||
make_app(
|
make_app(
|
||||||
@@ -100,7 +100,6 @@ async fn main() -> Result<()> {
|
|||||||
cal_store.clone(),
|
cal_store.clone(),
|
||||||
subscription_store.clone(),
|
subscription_store.clone(),
|
||||||
user_store.clone(),
|
user_store.clone(),
|
||||||
user_store.clone(),
|
|
||||||
config.frontend.clone(),
|
config.frontend.clone(),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -122,24 +121,21 @@ mod tests {
|
|||||||
use actix_web::{http::StatusCode, test::TestRequest};
|
use actix_web::{http::StatusCode, test::TestRequest};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use rustical_frontend::FrontendConfig;
|
use rustical_frontend::FrontendConfig;
|
||||||
use rustical_store::auth::{AuthenticationProvider, UserStore};
|
use rustical_store::auth::AuthenticationProvider;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct MockUserStore;
|
struct MockUserStore;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl UserStore for MockUserStore {
|
impl AuthenticationProvider for MockUserStore {
|
||||||
async fn get_user(
|
async fn get_principal(
|
||||||
&self,
|
&self,
|
||||||
id: &str,
|
id: &str,
|
||||||
) -> Result<Option<rustical_store::auth::User>, rustical_store::Error> {
|
) -> Result<Option<rustical_store::auth::User>, rustical_store::Error> {
|
||||||
Err(rustical_store::Error::NotFound)
|
Err(rustical_store::Error::NotFound)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl AuthenticationProvider for MockUserStore {
|
|
||||||
async fn validate_user_token(
|
async fn validate_user_token(
|
||||||
&self,
|
&self,
|
||||||
user_id: &str,
|
user_id: &str,
|
||||||
@@ -151,7 +147,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_main() {
|
async fn test_main() {
|
||||||
let (addr_store, cal_store, subscription_store, update_recv) = get_data_stores(
|
let (addr_store, cal_store, subscription_store, _update_recv) = get_data_stores(
|
||||||
true,
|
true,
|
||||||
&crate::config::DataStoreConfig::Sqlite(crate::config::SqliteDataStoreConfig {
|
&crate::config::DataStoreConfig::Sqlite(crate::config::SqliteDataStoreConfig {
|
||||||
db_url: "".to_owned(),
|
db_url: "".to_owned(),
|
||||||
@@ -166,7 +162,6 @@ mod tests {
|
|||||||
addr_store,
|
addr_store,
|
||||||
cal_store,
|
cal_store,
|
||||||
subscription_store,
|
subscription_store,
|
||||||
user_store.clone(),
|
|
||||||
user_store,
|
user_store,
|
||||||
FrontendConfig {
|
FrontendConfig {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user