Skip to content

Commit

Permalink
Eliminate auth modes, add token TTL
Browse files Browse the repository at this point in the history
  • Loading branch information
marcua committed Aug 13, 2023
1 parent 735ccd0 commit 67dbad1
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 97 deletions.
4 changes: 2 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ impl actix_web::error::ResponseError for AybError {
}

impl From<fernet::DecryptionError> for AybError {
fn from(cause: fernet::DecryptionError) -> Self {
fn from(_cause: fernet::DecryptionError) -> Self {
AybError {
message: format!("{:?}", cause),
message: "Invalid or expired token".to_owned(),
}
}
}
Expand Down
82 changes: 32 additions & 50 deletions src/http/endpoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use crate::error::AybError;
use crate::hosted_db::paths::database_path;
use crate::hosted_db::{run_query, QueryResult};
use crate::http::structs::{
APIKey as APIAPIKey, AuthenticationDetails, AuthenticationMode, AybConfig,
Database as APIDatabase, EmptyResponse, EntityDatabasePath,
APIKey as APIAPIKey, AuthenticationDetails, AybConfig, Database as APIDatabase, EmptyResponse,
EntityDatabasePath,
};
use crate::http::tokens::{decrypt_auth_token, encrypt_auth_token};
use crate::http::utils::get_header;
Expand All @@ -31,58 +31,42 @@ async fn confirm(
})
.await?;

// Ensure that there are no verified authentication methods, and
// check to see if this method has been previously attempted but
// not verified.
// Check if there are authentication methods already, and if this
// method in particular is verified.
let auth_methods = ayb_db.list_authentication_methods(&created_entity).await?;
let mut already_verified = false;
let mut auth_method: Option<InstantiatedAuthenticationMethod> = None;
let mut found_auth_method: Option<InstantiatedAuthenticationMethod> = None;
for method in auth_methods {
if method.status == (AuthenticationMethodStatus::Verified as i16) {
already_verified = true;
if method.method_type == (AuthenticationMethodType::Email as i16)
&& method.email_address == auth_details.email_address
{
auth_method = Some(method)
}
already_verified = true;
if method.method_type == (AuthenticationMethodType::Email as i16)
&& method.email_address == auth_details.email_address
{
found_auth_method = Some(method)
}
}

match AuthenticationMode::from_i16(auth_details.mode) {
AuthenticationMode::Register => {
// If registering, either accept this authentication
// method if it was previously created, or if there is no
// other verification method already verified.
if let None = auth_method {
if already_verified {
return Err(AybError {
message: format!("This entity has already been registered"),
});
}
ayb_db
.create_authentication_method(&AuthenticationMethod {
entity_id: created_entity.id,
method_type: AuthenticationMethodType::Email as i16,
status: AuthenticationMethodStatus::Verified as i16,
email_address: auth_details.email_address,
})
.await?;
}
}
AuthenticationMode::Login => {
// TODO(marcua): After creating the login endpoint,
// consider whether this code path is necessary, or if we
// can remove Register vs. Login mode. When doing that,
// think about entity that hasn't registered, has verified
// with the current authentication method, and has
// registered/verified with another authentication method.
if let None = auth_method {
return Err(AybError {
message: format!("Login failed due to unverified authentication method"),
});
}
if let None = found_auth_method {
// If the user was logging in an already verified account,
// auth_method can't be empty. So the only way to reach this
// branch is when registering.
// When registering, either accept this authentication method
// if it was previously created, or if there is no other
// verification method already verified.
if already_verified {
return Err(AybError {
message: format!("{} has already been registered", created_entity.slug),
});
}
ayb_db
.create_authentication_method(&AuthenticationMethod {
entity_id: created_entity.id,
method_type: AuthenticationMethodType::Email as i16,
status: AuthenticationMethodStatus::Verified as i16,
email_address: auth_details.email_address,
})
.await?;
}

// TODO(marcua): When we implement permissions, get_or_create default API keys.
// Ok(HttpResponse::Ok().json(APIAPIKey::from_persisted(&created_key)))
Ok(HttpResponse::Ok().json(APIAPIKey {
Expand Down Expand Up @@ -131,7 +115,6 @@ async fn log_in(
let token = encrypt_auth_token(
&AuthenticationDetails {
version: 1,
mode: AuthenticationMode::Register as i16,
entity: entity,
entity_type: instantiated_entity.entity_type,
email_address: method.email_address.to_owned(),
Expand All @@ -145,7 +128,7 @@ async fn log_in(
}

return Err(AybError {
message: format!("No email authentication method for this entity."),
message: format!("No account or email authentication method for {}", entity),
});
}

Expand Down Expand Up @@ -195,14 +178,13 @@ async fn register(

if already_verified {
return Err(AybError {
message: format!("This entity has already been registered"),
message: format!("{} has already been registered", entity),
});
}

let token = encrypt_auth_token(
&AuthenticationDetails {
version: 1,
mode: AuthenticationMode::Register as i16,
entity: entity.clone(),
entity_type: EntityType::from_str(&entity_type) as i16,
email_address: email_address.to_owned(),
Expand Down
44 changes: 0 additions & 44 deletions src/http/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@ use crate::ayb_db::models::{
DBType, EntityType, InstantiatedDatabase as PersistedDatabase,
InstantiatedEntity as PersistedEntity,
};
use clap::ValueEnum;
use serde::{Deserialize, Serialize};
use serde_repr::{Deserialize_repr, Serialize_repr};
use std::fmt;

#[derive(Clone, Serialize, Deserialize)]
pub struct AybConfigAuthentication {
Expand Down Expand Up @@ -72,50 +69,9 @@ pub struct EntityDatabasePath {
pub database: String,
}

#[derive(
Serialize_repr, Deserialize_repr, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum,
)]
#[repr(i16)]
pub enum AuthenticationMode {
Register = 0,
Login = 1,
}

impl fmt::Display for AuthenticationMode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}

impl AuthenticationMode {
pub fn from_i16(value: i16) -> AuthenticationMode {
match value {
0 => AuthenticationMode::Register,
1 => AuthenticationMode::Login,
_ => panic!("Unknown value: {}", value),
}
}

pub fn from_str(value: &str) -> AuthenticationMode {
match value {
"register" => AuthenticationMode::Register,
"login" => AuthenticationMode::Login,
_ => panic!("Unknown value: {}", value),
}
}

pub fn to_str(&self) -> &str {
match self {
AuthenticationMode::Register => "register",
AuthenticationMode::Login => "login",
}
}
}

#[derive(Debug, Serialize, Deserialize)]
pub struct AuthenticationDetails {
pub version: u16,
pub mode: i16,
pub entity: String,
pub entity_type: i16,
pub email_address: String,
Expand Down
5 changes: 4 additions & 1 deletion src/http/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@ pub fn decrypt_auth_token(
auth_config: &AybConfigAuthentication,
) -> Result<AuthenticationDetails, AybError> {
let generator = get_fernet_generator(auth_config)?;
Ok(serde_json::from_slice(&generator.decrypt(&cyphertext)?)?)
Ok(serde_json::from_slice(&generator.decrypt_with_ttl(
&cyphertext,
auth_config.token_expiration_seconds,
)?)?)
}

0 comments on commit 67dbad1

Please sign in to comment.