mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 19:22:26 +00:00
Make stricter distinction between password and app tokens
This commit is contained in:
@@ -105,13 +105,13 @@ docker run -it --rm ghcr.io/lennart-k/rustical rustical pwhash
|
||||
|
||||
### Password vs app tokens
|
||||
|
||||
The password is meant as a password you use to log in to the frontend.
|
||||
The password is optional (if you have configured OpenID Connect) and is only used to log in to the frontend.
|
||||
Since it's sensitive information,
|
||||
the secure but slow hash algorithm `argon2` is chosen.
|
||||
If you've configured OpenID Connect you can also completely omit the password.
|
||||
|
||||
I recommend to generate random app tokens for each CalDAV/CardDAV client (which can also be done through the frontend).
|
||||
These can use the faster `pbkdf2` algorithm.
|
||||
App tokens are used by your CalDAV/CardDAV client (which can be managed through the frontend).
|
||||
I recommend to generate random app tokens for each CalDAV/CardDAV client.
|
||||
Since the app tokens are random they use the faster `pbkdf2` algorithm.
|
||||
|
||||
### WebDAV Push
|
||||
|
||||
|
||||
@@ -70,10 +70,7 @@ pub async fn route_post_login<AP: AuthenticationProvider>(
|
||||
.and_then(|uri| req.full_url().make_relative(&uri))
|
||||
.unwrap_or(default_redirect);
|
||||
|
||||
if let Ok(Some(user)) = auth_provider
|
||||
.validate_user_token(&username, &password)
|
||||
.await
|
||||
{
|
||||
if let Ok(Some(user)) = auth_provider.validate_password(&username, &password).await {
|
||||
session.insert("user", user.id).unwrap();
|
||||
Redirect::to(redirect_uri)
|
||||
.see_other()
|
||||
|
||||
@@ -70,7 +70,7 @@ where
|
||||
let user_id = auth.as_ref().user_id();
|
||||
if let Some(password) = auth.as_ref().password() {
|
||||
if let Ok(Some(user)) = auth_provider
|
||||
.validate_user_token(user_id, password)
|
||||
.validate_app_token(user_id, password)
|
||||
.instrument(info_span!("validate_user_token"))
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -8,7 +8,9 @@ use async_trait::async_trait;
|
||||
pub trait AuthenticationProvider: 'static {
|
||||
async fn get_principal(&self, id: &str) -> Result<Option<User>, crate::Error>;
|
||||
async fn insert_principal(&self, user: User) -> Result<(), crate::Error>;
|
||||
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error>;
|
||||
async fn validate_password(&self, user_id: &str, password: &str)
|
||||
-> Result<Option<User>, Error>;
|
||||
async fn validate_app_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error>;
|
||||
/// Returns a token identifier
|
||||
async fn add_app_token(
|
||||
&self,
|
||||
|
||||
@@ -75,29 +75,37 @@ impl AuthenticationProvider for TomlPrincipalStore {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn validate_user_token(&self, user_id: &str, token: &str) -> Result<Option<User>, Error> {
|
||||
async fn validate_password(
|
||||
&self,
|
||||
user_id: &str,
|
||||
password_input: &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.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() {
|
||||
if password_auth::verify_password(password_input, password).is_ok() {
|
||||
return Ok(Some(user));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn validate_app_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),
|
||||
};
|
||||
|
||||
for app_token in &user.app_tokens {
|
||||
if password_auth::verify_password(token, &app_token.token).is_ok() {
|
||||
return Ok(Some(user));
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
|
||||
10
src/main.rs
10
src/main.rs
@@ -147,7 +147,15 @@ mod tests {
|
||||
Err(rustical_store::Error::NotFound)
|
||||
}
|
||||
|
||||
async fn validate_user_token(
|
||||
async fn validate_password(
|
||||
&self,
|
||||
user_id: &str,
|
||||
password: &str,
|
||||
) -> Result<Option<rustical_store::auth::User>, rustical_store::Error> {
|
||||
Err(rustical_store::Error::NotFound)
|
||||
}
|
||||
|
||||
async fn validate_app_token(
|
||||
&self,
|
||||
user_id: &str,
|
||||
token: &str,
|
||||
|
||||
Reference in New Issue
Block a user