Finish mocking the user -> session flow
This commit is contained in:
parent
977059c90d
commit
772d2c01f4
|
@ -1,5 +1,6 @@
|
|||
use crate::errors::{error, fatal, ok, AppResult};
|
||||
use crate::errors::{error, fatal, ok, AppResult, FatalError};
|
||||
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ValueRef};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::{convert::Infallible, str::FromStr};
|
||||
use thiserror::Error;
|
||||
|
@ -20,7 +21,7 @@ pub enum AuthenticationError {
|
|||
UserNotFound,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct SessionToken(String);
|
||||
|
||||
impl From<&str> for SessionToken {
|
||||
|
@ -43,7 +44,7 @@ impl From<SessionToken> for String {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Invitation(String);
|
||||
|
||||
impl From<&str> for Invitation {
|
||||
|
@ -66,7 +67,7 @@ impl From<Invitation> for String {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct UserId(String);
|
||||
|
||||
impl From<&str> for UserId {
|
||||
|
@ -100,7 +101,7 @@ impl FromSql for UserId {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Username(String);
|
||||
|
||||
impl From<&str> for Username {
|
||||
|
@ -156,9 +157,14 @@ pub trait AuthenticationDB: Send + Sync {
|
|||
|
||||
fn delete_user(&mut self, user: UserId) -> AppResult<(), AuthenticationError>;
|
||||
|
||||
fn validate_session(&self, session: SessionToken) -> AppResult<(), AuthenticationError>;
|
||||
fn validate_session(
|
||||
&self,
|
||||
session: SessionToken,
|
||||
) -> AppResult<(Username, UserId), AuthenticationError>;
|
||||
|
||||
fn get_user_id(&self, username: Username) -> AppResult<UserId, AuthenticationError>;
|
||||
fn get_userid(&self, username: Username) -> AppResult<UserId, AuthenticationError>;
|
||||
|
||||
fn get_username(&self, userid: UserId) -> AppResult<Username, AuthenticationError>;
|
||||
|
||||
fn list_users(&self) -> AppResult<Vec<Username>, AuthenticationError>;
|
||||
}
|
||||
|
@ -261,15 +267,22 @@ impl AuthenticationDB for MemoryAuth {
|
|||
}
|
||||
}
|
||||
|
||||
fn validate_session(&self, session: SessionToken) -> AppResult<(), AuthenticationError> {
|
||||
if self.sessions.contains_key(&session) {
|
||||
ok(())
|
||||
fn validate_session(
|
||||
&self,
|
||||
session: SessionToken,
|
||||
) -> AppResult<(Username, UserId), AuthenticationError> {
|
||||
if let Some(userid) = self.sessions.get(&session) {
|
||||
if let Some(username) = self.inverse_users.get(&userid) {
|
||||
ok((username.clone(), userid.clone()))
|
||||
} else {
|
||||
error::<(), AuthenticationError>(AuthenticationError::InvalidSession)
|
||||
fatal(FatalError::DatabaseInconsistency)
|
||||
}
|
||||
} else {
|
||||
error(AuthenticationError::InvalidSession)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_user_id(&self, username: Username) -> AppResult<UserId, AuthenticationError> {
|
||||
fn get_userid(&self, username: Username) -> AppResult<UserId, AuthenticationError> {
|
||||
Ok(self
|
||||
.users
|
||||
.get(&username)
|
||||
|
@ -277,6 +290,14 @@ impl AuthenticationDB for MemoryAuth {
|
|||
.ok_or(AuthenticationError::UserNotFound))
|
||||
}
|
||||
|
||||
fn get_username(&self, userid: UserId) -> AppResult<Username, AuthenticationError> {
|
||||
Ok(self
|
||||
.inverse_users
|
||||
.get(&userid)
|
||||
.map(|u| u.clone())
|
||||
.ok_or(AuthenticationError::UserNotFound))
|
||||
}
|
||||
|
||||
fn list_users(&self) -> AppResult<Vec<Username>, AuthenticationError> {
|
||||
ok(self.users.keys().cloned().collect::<Vec<Username>>())
|
||||
}
|
||||
|
|
|
@ -5,6 +5,8 @@ use thiserror::Error;
|
|||
/// down and that the administrator fix a problem.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FatalError {
|
||||
#[error("database is inconsistent")]
|
||||
DatabaseInconsistency,
|
||||
#[error("disk is full")]
|
||||
DiskFull,
|
||||
#[error("io error: {0}")]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use errors::{ok, AppResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
|
@ -6,7 +7,9 @@ use std::{
|
|||
use warp::Filter;
|
||||
|
||||
mod authentication;
|
||||
use authentication::{AuthenticationDB, AuthenticationError, MemoryAuth, UserId, Username};
|
||||
use authentication::{
|
||||
AuthenticationDB, AuthenticationError, Invitation, MemoryAuth, SessionToken, UserId, Username,
|
||||
};
|
||||
|
||||
mod database;
|
||||
mod errors;
|
||||
|
@ -15,7 +18,7 @@ mod errors;
|
|||
struct AuthenticationRefused;
|
||||
impl warp::reject::Reject for AuthenticationRefused {}
|
||||
|
||||
fn with_authentication(
|
||||
fn with_session(
|
||||
auth_ctx: Arc<RwLock<impl AuthenticationDB>>,
|
||||
) -> impl Filter<Extract = ((Username, UserId),), Error = warp::Rejection> + Clone {
|
||||
let auth_ctx = auth_ctx.clone();
|
||||
|
@ -25,13 +28,11 @@ fn with_authentication(
|
|||
let auth_ctx = auth_ctx.clone();
|
||||
async move {
|
||||
if auth_header.starts_with("Basic ") {
|
||||
let username = auth_header.split(" ").skip(1).collect::<String>();
|
||||
match auth_ctx
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_user_id(Username::from(username.as_str()))
|
||||
{
|
||||
Ok(Ok(userid)) => Ok((Username::from(username.as_str()), userid)),
|
||||
let session_token = SessionToken::from(
|
||||
auth_header.split(" ").skip(1).collect::<String>().as_str(),
|
||||
);
|
||||
match auth_ctx.read().unwrap().validate_session(session_token) {
|
||||
Ok(Ok((username, userid))) => Ok((username, userid)),
|
||||
Ok(Err(_)) => Err(warp::reject::custom(AuthenticationRefused)),
|
||||
Err(err) => panic!("{}", err),
|
||||
}
|
||||
|
@ -49,13 +50,13 @@ struct ErrorResponse {
|
|||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MakeUserParameters {
|
||||
username: String,
|
||||
struct MakeUserParams {
|
||||
username: Username,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct MakeUserResponse {
|
||||
userid: String,
|
||||
userid: UserId,
|
||||
}
|
||||
|
||||
fn make_user(
|
||||
|
@ -64,13 +65,67 @@ fn make_user(
|
|||
warp::path!("api" / "v1" / "users")
|
||||
.and(warp::put())
|
||||
.and(warp::body::json())
|
||||
.map(move |params: MakeUserParameters| {
|
||||
.map(move |params: MakeUserParams| {
|
||||
let mut auth_ctx = auth_ctx.write().unwrap();
|
||||
match (*auth_ctx).create_user(Username::from(params.username.as_str())) {
|
||||
Ok(Ok(userid)) => warp::reply::json(&MakeUserResponse {
|
||||
userid: String::from(userid),
|
||||
match (*auth_ctx).create_user(Username::from(params.username)) {
|
||||
Ok(Ok(userid)) => warp::reply::json(&MakeUserResponse { userid }),
|
||||
Ok(Err(auth_error)) => warp::reply::json(&ErrorResponse {
|
||||
error: format!("{:?}", auth_error),
|
||||
}),
|
||||
Ok(auth_error) => warp::reply::json(&ErrorResponse {
|
||||
Err(err) => panic!("{}", err),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct MakeInvitationParams {
|
||||
userid: UserId,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct MakeInvitationResponse {
|
||||
invitation: Invitation,
|
||||
}
|
||||
|
||||
fn make_invitation(
|
||||
auth_ctx: Arc<RwLock<impl AuthenticationDB>>,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "v1" / "invitations")
|
||||
.and(warp::put())
|
||||
.and(warp::body::json())
|
||||
.map(move |params: MakeInvitationParams| {
|
||||
let mut auth_ctx = auth_ctx.write().unwrap();
|
||||
match (*auth_ctx).create_invitation(params.userid) {
|
||||
Ok(Ok(invitation)) => warp::reply::json(&MakeInvitationResponse { invitation }),
|
||||
Ok(Err(auth_error)) => warp::reply::json(&ErrorResponse {
|
||||
error: format!("{:?}", auth_error),
|
||||
}),
|
||||
Err(err) => panic!("{}", err),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AuthenticateParams {
|
||||
invitation: Invitation,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AuthenticateResponse {
|
||||
session_token: SessionToken,
|
||||
}
|
||||
|
||||
fn authenticate(
|
||||
auth_ctx: Arc<RwLock<impl AuthenticationDB>>,
|
||||
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "v1" / "authenticate")
|
||||
.and(warp::put())
|
||||
.and(warp::body::json())
|
||||
.map(move |params: AuthenticateParams| {
|
||||
let mut auth_ctx = auth_ctx.write().unwrap();
|
||||
match (*auth_ctx).authenticate(params.invitation) {
|
||||
Ok(Ok(session_token)) => warp::reply::json(&AuthenticateResponse { session_token }),
|
||||
Ok(Err(auth_error)) => warp::reply::json(&ErrorResponse {
|
||||
error: format!("{:?}", auth_error),
|
||||
}),
|
||||
Err(err) => panic!("{}", err),
|
||||
|
@ -117,7 +172,7 @@ pub async fn main() {
|
|||
*/
|
||||
|
||||
let echo_authenticated = warp::path!("api" / "v1" / "echo" / String)
|
||||
.and(with_authentication(auth_ctx.clone()))
|
||||
.and(with_session(auth_ctx.clone()))
|
||||
.map(|param: String, (username, userid)| {
|
||||
println!("param: {:?}", username);
|
||||
println!("param: {:?}", userid);
|
||||
|
@ -127,6 +182,8 @@ pub async fn main() {
|
|||
|
||||
let filter = list_users(auth_ctx.clone())
|
||||
.or(make_user(auth_ctx.clone()))
|
||||
.or(make_invitation(auth_ctx.clone()))
|
||||
.or(authenticate(auth_ctx.clone()))
|
||||
.or(echo_authenticated)
|
||||
.or(echo_unauthenticated);
|
||||
|
||||
|
|
Loading…
Reference in New Issue