Compare commits
4 Commits
2c1c19690f
...
deb3415c30
Author | SHA1 | Date |
---|---|---|
Savanni D'Gerinel | deb3415c30 | |
Savanni D'Gerinel | 064b754786 | |
Savanni D'Gerinel | 95a597c8ea | |
Savanni D'Gerinel | 45df1233e5 |
|
@ -6,9 +6,6 @@ use std::{convert::Infallible, str::FromStr};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::{adapter::Hyphenated, Uuid};
|
use uuid::{adapter::Hyphenated, Uuid};
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
use crate::errors::maybe_fail;
|
|
||||||
|
|
||||||
#[derive(Debug, Error, PartialEq)]
|
#[derive(Debug, Error, PartialEq)]
|
||||||
pub enum AuthenticationError {
|
pub enum AuthenticationError {
|
||||||
#[error("username already exists")]
|
#[error("username already exists")]
|
||||||
|
@ -144,7 +141,7 @@ impl FromSql for Username {
|
||||||
pub trait AuthenticationDB: Send + Sync {
|
pub trait AuthenticationDB: Send + Sync {
|
||||||
fn create_user(&mut self, username: Username) -> AppResult<UserId, AuthenticationError>;
|
fn create_user(&mut self, username: Username) -> AppResult<UserId, AuthenticationError>;
|
||||||
|
|
||||||
fn create_invitation(&mut self, user: UserId) -> AppResult<Invitation, AuthenticationError>;
|
fn create_invitation(&mut self, user: &UserId) -> AppResult<Invitation, AuthenticationError>;
|
||||||
|
|
||||||
fn authenticate(
|
fn authenticate(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -179,8 +176,9 @@ pub struct MemoryAuth {
|
||||||
|
|
||||||
impl AuthenticationDB for MemoryAuth {
|
impl AuthenticationDB for MemoryAuth {
|
||||||
fn create_user(&mut self, username: Username) -> AppResult<UserId, AuthenticationError> {
|
fn create_user(&mut self, username: Username) -> AppResult<UserId, AuthenticationError> {
|
||||||
#[cfg(test)]
|
if self.users.contains_key(&username) {
|
||||||
let _ = maybe_fail::<AuthenticationError>(vec![])?;
|
return Ok(Err(AuthenticationError::DuplicateUsername));
|
||||||
|
}
|
||||||
|
|
||||||
let userid = UserId::from(format!("{}", Hyphenated::from_uuid(Uuid::new_v4())).as_str());
|
let userid = UserId::from(format!("{}", Hyphenated::from_uuid(Uuid::new_v4())).as_str());
|
||||||
self.users.insert(username.clone(), userid.clone());
|
self.users.insert(username.clone(), userid.clone());
|
||||||
|
@ -189,17 +187,14 @@ impl AuthenticationDB for MemoryAuth {
|
||||||
ok(userid)
|
ok(userid)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_invitation(&mut self, user: UserId) -> AppResult<Invitation, AuthenticationError> {
|
fn create_invitation(&mut self, user: &UserId) -> AppResult<Invitation, AuthenticationError> {
|
||||||
#[cfg(test)]
|
|
||||||
let _ = maybe_fail::<AuthenticationError>(vec![])?;
|
|
||||||
|
|
||||||
if !self.inverse_users.contains_key(&user) {
|
if !self.inverse_users.contains_key(&user) {
|
||||||
return error::<Invitation, AuthenticationError>(AuthenticationError::UserNotFound);
|
return error::<Invitation, AuthenticationError>(AuthenticationError::UserNotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
let invitation =
|
let invitation =
|
||||||
Invitation::from(format!("{}", Hyphenated::from_uuid(Uuid::new_v4())).as_str());
|
Invitation::from(format!("{}", Hyphenated::from_uuid(Uuid::new_v4())).as_str());
|
||||||
self.invitations.insert(invitation.clone(), user);
|
self.invitations.insert(invitation.clone(), user.clone());
|
||||||
ok(invitation)
|
ok(invitation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,9 +202,6 @@ impl AuthenticationDB for MemoryAuth {
|
||||||
&mut self,
|
&mut self,
|
||||||
invitation: Invitation,
|
invitation: Invitation,
|
||||||
) -> AppResult<SessionToken, AuthenticationError> {
|
) -> AppResult<SessionToken, AuthenticationError> {
|
||||||
#[cfg(test)]
|
|
||||||
let _ = maybe_fail::<AuthenticationError>(vec![])?;
|
|
||||||
|
|
||||||
if let Some(user) = self.invitations.get(&invitation) {
|
if let Some(user) = self.invitations.get(&invitation) {
|
||||||
let session_token =
|
let session_token =
|
||||||
SessionToken::from(format!("{}", Hyphenated::from_uuid(Uuid::new_v4())).as_str());
|
SessionToken::from(format!("{}", Hyphenated::from_uuid(Uuid::new_v4())).as_str());
|
||||||
|
@ -222,9 +214,6 @@ impl AuthenticationDB for MemoryAuth {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_session(&mut self, session: SessionToken) -> AppResult<(), AuthenticationError> {
|
fn delete_session(&mut self, session: SessionToken) -> AppResult<(), AuthenticationError> {
|
||||||
#[cfg(test)]
|
|
||||||
let _ = maybe_fail::<AuthenticationError>(vec![])?;
|
|
||||||
|
|
||||||
if let Some(_) = self.sessions.remove(&session) {
|
if let Some(_) = self.sessions.remove(&session) {
|
||||||
ok(())
|
ok(())
|
||||||
} else {
|
} else {
|
||||||
|
@ -233,9 +222,6 @@ impl AuthenticationDB for MemoryAuth {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_invitation(&mut self, invitation: Invitation) -> AppResult<(), AuthenticationError> {
|
fn delete_invitation(&mut self, invitation: Invitation) -> AppResult<(), AuthenticationError> {
|
||||||
#[cfg(test)]
|
|
||||||
let _ = maybe_fail::<AuthenticationError>(vec![])?;
|
|
||||||
|
|
||||||
if let Some(_) = self.invitations.remove(&invitation) {
|
if let Some(_) = self.invitations.remove(&invitation) {
|
||||||
ok(())
|
ok(())
|
||||||
} else {
|
} else {
|
||||||
|
@ -244,9 +230,6 @@ impl AuthenticationDB for MemoryAuth {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_user(&mut self, user: UserId) -> AppResult<(), AuthenticationError> {
|
fn delete_user(&mut self, user: UserId) -> AppResult<(), AuthenticationError> {
|
||||||
#[cfg(test)]
|
|
||||||
let _ = maybe_fail::<AuthenticationError>(vec![])?;
|
|
||||||
|
|
||||||
if let Some(username) = self.inverse_users.remove(&user) {
|
if let Some(username) = self.inverse_users.remove(&user) {
|
||||||
let _ = self.users.remove(&username);
|
let _ = self.users.remove(&username);
|
||||||
self.invitations = self
|
self.invitations = self
|
||||||
|
@ -306,10 +289,11 @@ impl AuthenticationDB for MemoryAuth {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::panic;
|
||||||
|
|
||||||
fn with_memory_db<F>(test: F)
|
fn with_memory_db<F>(test: F)
|
||||||
where
|
where
|
||||||
F: Fn(Box<dyn AuthenticationDB>),
|
F: Fn(Box<dyn AuthenticationDB>) -> () + panic::UnwindSafe,
|
||||||
{
|
{
|
||||||
let authdb: Box<MemoryAuth> = Box::new(Default::default());
|
let authdb: Box<MemoryAuth> = Box::new(Default::default());
|
||||||
test(authdb);
|
test(authdb);
|
||||||
|
@ -327,6 +311,7 @@ mod test {
|
||||||
assert_eq!(authdb.get_username(&userid).unwrap(), Ok(username));
|
assert_eq!(authdb.get_username(&userid).unwrap(), Ok(username));
|
||||||
assert!(authdb.list_users().unwrap().unwrap().contains(&userid));
|
assert!(authdb.list_users().unwrap().unwrap().contains(&userid));
|
||||||
}
|
}
|
||||||
|
|
||||||
with_memory_db(test_case);
|
with_memory_db(test_case);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,43 +331,197 @@ mod test {
|
||||||
with_memory_db(test_case);
|
with_memory_db(test_case);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_allows_multiple_invitations_per_user() {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_exchanges_an_invitation_for_a_session() {
|
fn it_exchanges_an_invitation_for_a_session() {
|
||||||
unimplemented!()
|
fn test_case(mut authdb: Box<dyn AuthenticationDB>) {
|
||||||
|
let username = Username::from("shephard");
|
||||||
|
let userid = authdb
|
||||||
|
.create_user(username.clone())
|
||||||
|
.expect("no fatal errors")
|
||||||
|
.expect("user to be created");
|
||||||
|
let invitation = authdb
|
||||||
|
.create_invitation(&userid)
|
||||||
|
.expect("no fatal errors")
|
||||||
|
.expect("invitation to be created");
|
||||||
|
let _ = authdb
|
||||||
|
.authenticate(invitation)
|
||||||
|
.expect("no fatal errors")
|
||||||
|
.expect("to receive a session token");
|
||||||
}
|
}
|
||||||
|
with_memory_db(test_case);
|
||||||
#[test]
|
|
||||||
fn it_allows_multiple_sessions_per_user() {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_disallows_invitation_reuse() {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_identifies_a_user_by_session() {
|
fn it_identifies_a_user_by_session() {
|
||||||
unimplemented!()
|
fn test_case(mut authdb: Box<dyn AuthenticationDB>) {
|
||||||
|
let shephard = Username::from("shephard");
|
||||||
|
let garrus = Username::from("garrus");
|
||||||
|
|
||||||
|
let (shephard_session, shephard_id) = {
|
||||||
|
let userid = authdb.create_user(shephard.clone()).unwrap().unwrap();
|
||||||
|
let invitation = authdb.create_invitation(&userid).unwrap().unwrap();
|
||||||
|
(authdb.authenticate(invitation).unwrap().unwrap(), userid)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (garrus_session, garrus_id) = {
|
||||||
|
let userid = authdb.create_user(garrus.clone()).unwrap().unwrap();
|
||||||
|
let invitation = authdb.create_invitation(&userid).unwrap().unwrap();
|
||||||
|
(authdb.authenticate(invitation).unwrap().unwrap(), userid)
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
authdb.validate_session(shephard_session).unwrap().unwrap(),
|
||||||
|
(shephard, shephard_id)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
authdb.validate_session(garrus_session).unwrap().unwrap(),
|
||||||
|
(garrus, garrus_id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
with_memory_db(test_case);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_allows_multiple_invitations_per_user() {
|
||||||
|
fn test_case(mut authdb: Box<dyn AuthenticationDB>) {
|
||||||
|
let username = Username::from("shephard");
|
||||||
|
let userid = authdb.create_user(username).unwrap().unwrap();
|
||||||
|
let invite1 = authdb.create_invitation(&userid).unwrap().unwrap();
|
||||||
|
let invite2 = authdb.create_invitation(&userid).unwrap().unwrap();
|
||||||
|
|
||||||
|
assert_ne!(invite1, invite2);
|
||||||
|
|
||||||
|
authdb.authenticate(invite1).unwrap().unwrap();
|
||||||
|
authdb.authenticate(invite2).unwrap().unwrap();
|
||||||
|
}
|
||||||
|
with_memory_db(test_case);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_allows_multiple_sessions_per_user() {
|
||||||
|
fn test_case(mut authdb: Box<dyn AuthenticationDB>) {
|
||||||
|
let username = Username::from("shephard");
|
||||||
|
let userid = authdb.create_user(username.clone()).unwrap().unwrap();
|
||||||
|
let invite1 = authdb.create_invitation(&userid).unwrap().unwrap();
|
||||||
|
let invite2 = authdb.create_invitation(&userid).unwrap().unwrap();
|
||||||
|
|
||||||
|
assert_ne!(invite1, invite2);
|
||||||
|
|
||||||
|
let session1 = authdb.authenticate(invite1).unwrap().unwrap();
|
||||||
|
let session2 = authdb.authenticate(invite2).unwrap().unwrap();
|
||||||
|
|
||||||
|
assert_ne!(session1, session2);
|
||||||
|
assert_eq!(
|
||||||
|
authdb.validate_session(session1).unwrap().unwrap(),
|
||||||
|
(username.clone(), userid.clone())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
authdb.validate_session(session2).unwrap().unwrap(),
|
||||||
|
(username, userid)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
with_memory_db(test_case);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_disallows_invitation_reuse() {
|
||||||
|
fn test_case(mut authdb: Box<dyn AuthenticationDB>) {
|
||||||
|
let username = Username::from("shephard");
|
||||||
|
let userid = authdb
|
||||||
|
.create_user(username.clone())
|
||||||
|
.expect("no fatal errors")
|
||||||
|
.expect("user to be created");
|
||||||
|
let invitation = authdb
|
||||||
|
.create_invitation(&userid)
|
||||||
|
.expect("no fatal errors")
|
||||||
|
.expect("invitation to be created");
|
||||||
|
let _ = authdb
|
||||||
|
.authenticate(invitation.clone())
|
||||||
|
.expect("no fatal errors")
|
||||||
|
.expect("to receive a session token");
|
||||||
|
let reuse_result = authdb.authenticate(invitation).expect("no fatal errors");
|
||||||
|
assert_eq!(reuse_result, Err(AuthenticationError::InvalidInvitation));
|
||||||
|
}
|
||||||
|
with_memory_db(test_case);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_deletes_a_user_and_invalidates_tokens() {
|
fn it_deletes_a_user_and_invalidates_tokens() {
|
||||||
unimplemented!()
|
fn test_case(mut authdb: Box<dyn AuthenticationDB>) {
|
||||||
|
let shephard = Username::from("shephard");
|
||||||
|
let garrus = Username::from("garrus");
|
||||||
|
|
||||||
|
let (shephard_session, shephard_id) = {
|
||||||
|
let userid = authdb.create_user(shephard.clone()).unwrap().unwrap();
|
||||||
|
let invitation = authdb.create_invitation(&userid).unwrap().unwrap();
|
||||||
|
(authdb.authenticate(invitation).unwrap().unwrap(), userid)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (garrus_invitation, garrus_id) = {
|
||||||
|
let userid = authdb.create_user(garrus.clone()).unwrap().unwrap();
|
||||||
|
(authdb.create_invitation(&userid).unwrap().unwrap(), userid)
|
||||||
|
};
|
||||||
|
|
||||||
|
authdb.delete_user(shephard_id).unwrap().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
authdb.validate_session(shephard_session).unwrap(),
|
||||||
|
Err(AuthenticationError::InvalidSession)
|
||||||
|
);
|
||||||
|
|
||||||
|
authdb.delete_user(garrus_id).unwrap().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
authdb.authenticate(garrus_invitation).unwrap(),
|
||||||
|
Err(AuthenticationError::InvalidInvitation)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
with_memory_db(test_case);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_deletes_a_session() {
|
fn it_deletes_a_session() {
|
||||||
unimplemented!()
|
fn test_case(mut authdb: Box<dyn AuthenticationDB>) {
|
||||||
|
let username = Username::from("shephard");
|
||||||
|
let userid = authdb
|
||||||
|
.create_user(username.clone())
|
||||||
|
.expect("no fatal errors")
|
||||||
|
.expect("user to be created");
|
||||||
|
let invitation = authdb
|
||||||
|
.create_invitation(&userid)
|
||||||
|
.expect("no fatal errors")
|
||||||
|
.expect("invitation to be created");
|
||||||
|
let session = authdb.authenticate(invitation).unwrap().unwrap();
|
||||||
|
|
||||||
|
authdb.delete_session(session.clone()).unwrap().unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
authdb.validate_session(session).unwrap(),
|
||||||
|
Err(AuthenticationError::InvalidSession)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
with_memory_db(test_case);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn it_deletes_an_invitation() {
|
fn it_deletes_an_invitation() {
|
||||||
unimplemented!()
|
fn test_case(mut authdb: Box<dyn AuthenticationDB>) {
|
||||||
|
let username = Username::from("shephard");
|
||||||
|
let userid = authdb
|
||||||
|
.create_user(username.clone())
|
||||||
|
.expect("no fatal errors")
|
||||||
|
.expect("user to be created");
|
||||||
|
let invitation = authdb
|
||||||
|
.create_invitation(&userid)
|
||||||
|
.expect("no fatal errors")
|
||||||
|
.expect("invitation to be created");
|
||||||
|
|
||||||
|
authdb
|
||||||
|
.delete_invitation(invitation.clone())
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
authdb.authenticate(invitation).unwrap(),
|
||||||
|
Err(AuthenticationError::InvalidInvitation)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
with_memory_db(test_case);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,3 @@ pub fn error<A, E>(err: E) -> AppResult<A, E> {
|
||||||
pub fn fatal<A, E>(err: FatalError) -> AppResult<A, E> {
|
pub fn fatal<A, E>(err: FatalError) -> AppResult<A, E> {
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn maybe_fail<E>(possible_errors: Vec<E>) -> AppResult<(), E> {
|
|
||||||
ok(())
|
|
||||||
}
|
|
||||||
|
|
|
@ -87,6 +87,7 @@ struct MakeInvitationResponse {
|
||||||
invitation: Invitation,
|
invitation: Invitation,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
fn make_invitation(
|
fn make_invitation(
|
||||||
auth_ctx: Arc<RwLock<impl AuthenticationDB>>,
|
auth_ctx: Arc<RwLock<impl AuthenticationDB>>,
|
||||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||||
|
@ -104,6 +105,7 @@ fn make_invitation(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct AuthenticateParams {
|
struct AuthenticateParams {
|
||||||
|
@ -191,7 +193,6 @@ pub async fn main() {
|
||||||
.or(echo_unauthenticated);
|
.or(echo_unauthenticated);
|
||||||
*/
|
*/
|
||||||
let filter = make_user(auth_ctx.clone())
|
let filter = make_user(auth_ctx.clone())
|
||||||
.or(make_invitation(auth_ctx.clone()))
|
|
||||||
.or(authenticate(auth_ctx.clone()))
|
.or(authenticate(auth_ctx.clone()))
|
||||||
.or(echo_authenticated)
|
.or(echo_authenticated)
|
||||||
.or(echo_unauthenticated);
|
.or(echo_unauthenticated);
|
||||||
|
|
Loading…
Reference in New Issue