mirror of
https://github.com/lennart-k/rustical.git
synced 2025-12-13 22:52:22 +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
|
### 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,
|
Since it's sensitive information,
|
||||||
the secure but slow hash algorithm `argon2` is chosen.
|
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).
|
App tokens are used by your CalDAV/CardDAV client (which can be managed through the frontend).
|
||||||
These can use the faster `pbkdf2` algorithm.
|
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
|
### WebDAV Push
|
||||||
|
|
||||||
|
|||||||
@@ -70,10 +70,7 @@ pub async fn route_post_login<AP: AuthenticationProvider>(
|
|||||||
.and_then(|uri| req.full_url().make_relative(&uri))
|
.and_then(|uri| req.full_url().make_relative(&uri))
|
||||||
.unwrap_or(default_redirect);
|
.unwrap_or(default_redirect);
|
||||||
|
|
||||||
if let Ok(Some(user)) = auth_provider
|
if let Ok(Some(user)) = auth_provider.validate_password(&username, &password).await {
|
||||||
.validate_user_token(&username, &password)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
session.insert("user", user.id).unwrap();
|
session.insert("user", user.id).unwrap();
|
||||||
Redirect::to(redirect_uri)
|
Redirect::to(redirect_uri)
|
||||||
.see_other()
|
.see_other()
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ where
|
|||||||
let user_id = auth.as_ref().user_id();
|
let user_id = auth.as_ref().user_id();
|
||||||
if let Some(password) = auth.as_ref().password() {
|
if let Some(password) = auth.as_ref().password() {
|
||||||
if let Ok(Some(user)) = auth_provider
|
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"))
|
.instrument(info_span!("validate_user_token"))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ use 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 get_principal(&self, id: &str) -> Result<Option<User>, crate::Error>;
|
||||||
async fn insert_principal(&self, user: User) -> Result<(), 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
|
/// Returns a token identifier
|
||||||
async fn add_app_token(
|
async fn add_app_token(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
@@ -75,29 +75,37 @@ impl AuthenticationProvider for TomlPrincipalStore {
|
|||||||
Ok(())
|
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? {
|
let user: User = match self.get_principal(user_id).await? {
|
||||||
Some(user) => user,
|
Some(user) => user,
|
||||||
None => return Ok(None),
|
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 {
|
let password = match &user.password {
|
||||||
Some(password) => password,
|
Some(password) => password,
|
||||||
None => return Ok(None),
|
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));
|
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)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
src/main.rs
10
src/main.rs
@@ -147,7 +147,15 @@ mod tests {
|
|||||||
Err(rustical_store::Error::NotFound)
|
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,
|
&self,
|
||||||
user_id: &str,
|
user_id: &str,
|
||||||
token: &str,
|
token: &str,
|
||||||
|
|||||||
Reference in New Issue
Block a user