Add anyhow, Improve Auth token code

Moved test helper code to its own module.
This commit is contained in:
Cameron Cordes
2021-10-07 20:09:05 -04:00
parent e4dac64776
commit 2c50b4ae2f
7 changed files with 138 additions and 88 deletions

7
Cargo.lock generated
View File

@@ -372,6 +372,12 @@ dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1"
[[package]]
name = "async-trait"
version = "0.1.50"
@@ -1156,6 +1162,7 @@ dependencies = [
"actix-rt",
"actix-web",
"actix-web-prom",
"anyhow",
"bcrypt",
"chrono",
"diesel",

View File

@@ -35,3 +35,4 @@ env_logger="0.8"
actix-web-prom = "0.5.1"
prometheus = "0.11"
lazy_static = "1.1"
anyhow = "1.0"

View File

@@ -32,6 +32,7 @@ pub async fn login(
user_dao: web::Data<Box<dyn UserDao>>,
) -> HttpResponse {
debug!("Logging in: {}", creds.username);
if let Some(user) = user_dao.get_user(&creds.username, &creds.password) {
let claims = Claims {
sub: user.id.to_string(),
@@ -43,6 +44,7 @@ pub async fn login(
&EncodingKey::from_secret(secret_key().as_bytes()),
)
.unwrap();
HttpResponse::Ok().json(Token { token: &token })
} else {
error!(
@@ -56,7 +58,7 @@ pub async fn login(
#[cfg(test)]
mod tests {
use super::*;
use crate::database::testhelpers::{BodyReader, TestUserDao};
use crate::testhelpers::{BodyReader, TestUserDao};
#[actix_rt::test]
async fn test_login_reports_200_when_user_exists() {

View File

@@ -35,7 +35,7 @@ impl FromStr for Claims {
let token = *(s.split("Bearer ").collect::<Vec<_>>().last().unwrap_or(&""));
match decode::<Claims>(
&token,
token,
&DecodingKey::from_secret(secret_key().as_bytes()),
&Validation::new(Algorithm::HS256),
) {
@@ -54,18 +54,27 @@ impl FromRequest for Claims {
type Config = ();
fn from_request(req: &HttpRequest, _payload: &mut dev::Payload) -> Self::Future {
let claims = match req.headers().get(header::AUTHORIZATION) {
Some(header) => Claims::from_str(header.to_str().unwrap_or("")),
None => Err(jsonwebtoken::errors::Error::from(
jsonwebtoken::errors::ErrorKind::InvalidToken,
)),
};
if let Ok(claims) = claims {
ok(claims)
} else {
req.headers()
.get(header::AUTHORIZATION)
.map_or_else(
|| Err(anyhow!("No authorization header")),
|header| {
header
.to_str()
.context("Unable to read Authorization header to string")
},
)
.and_then(|header| {
Claims::from_str(header)
.with_context(|| format!("Unable to decode token from: {}", header))
})
.map_or_else(
|e| {
error!("{}", e);
err(ErrorUnauthorized("Bad token"))
}
},
ok,
)
}
}
@@ -156,4 +165,17 @@ mod tests {
}
}
}
#[test]
fn test_junk_token_is_invalid() {
let err = Claims::from_str("uni-֍ՓՓՓՓՓՓՓՓՓՓՓՓՓՓՓ");
match err.unwrap_err().into_kind() {
ErrorKind::InvalidToken => assert!(true),
kind => {
println!("Unexpected error: {:?}", kind);
assert!(false)
}
}
}
}

View File

@@ -8,7 +8,7 @@ use std::{
use crate::database::models::{Favorite, InsertFavorite, InsertUser, User};
mod models;
pub mod models;
mod schema;
pub trait UserDao {
@@ -141,7 +141,7 @@ impl FavoriteDao for SqliteFavoriteDao {
diesel::insert_into(favorites)
.values(InsertFavorite {
userid: &user_id,
path: &favorite_path,
path: favorite_path,
})
.execute(connection)
.map_err(|_| DbError::new(DbErrorKind::InsertError))
@@ -168,74 +168,3 @@ impl FavoriteDao for SqliteFavoriteDao {
.map_err(|_| DbError::new(DbErrorKind::QueryError))
}
}
#[cfg(test)]
pub mod testhelpers {
use actix_web::dev::{Body, ResponseBody};
use super::{models::User, UserDao};
use std::cell::RefCell;
use std::option::Option;
pub struct TestUserDao {
pub user_map: RefCell<Vec<User>>,
}
impl TestUserDao {
pub fn new() -> Self {
Self {
user_map: RefCell::new(Vec::new()),
}
}
}
impl UserDao for TestUserDao {
fn create_user(&self, username: &str, password: &str) -> Option<User> {
let u = User {
id: (self.user_map.borrow().len() + 1) as i32,
username: username.to_string(),
password: password.to_string(),
};
self.user_map.borrow_mut().push(u.clone());
Some(u)
}
fn get_user(&self, user: &str, pass: &str) -> Option<User> {
match self
.user_map
.borrow()
.iter()
.find(|&u| u.username == user && u.password == pass)
{
Some(u) => {
let copy = (*u).clone();
Some(copy)
}
None => None,
}
}
fn user_exists(&self, user: &str) -> bool {
self.user_map
.borrow()
.iter()
.find(|&u| u.username == user)
.is_some()
}
}
pub trait BodyReader {
fn read_to_str(&self) -> &str;
}
impl BodyReader for ResponseBody<Body> {
fn read_to_str(&self) -> &str {
match self {
ResponseBody::Body(Body::Bytes(ref b)) => std::str::from_utf8(b).unwrap(),
_ => panic!("Unknown response body"),
}
}
}
}

View File

@@ -42,6 +42,9 @@ mod database;
mod files;
mod video;
#[cfg(test)]
mod testhelpers;
lazy_static! {
static ref IMAGE_GAUGE: IntGauge = IntGauge::new(
"imageserver_image_total",
@@ -190,7 +193,7 @@ async fn generate_video(
let filename = name.to_str().expect("Filename should convert to string");
let playlist = format!("tmp/{}.m3u8", filename);
if let Some(path) = is_valid_path(&body.path) {
if let Ok(child) = create_playlist(&path.to_str().unwrap(), &playlist).await {
if let Ok(child) = create_playlist(path.to_str().unwrap(), &playlist).await {
data.stream_manager
.do_send(ProcessMessage(playlist.clone(), child));
}

86
src/testhelpers.rs Normal file
View File

@@ -0,0 +1,86 @@
use actix_web::dev::{Body, ResponseBody};
use serde::Deserialize;
use crate::database::{models::User, UserDao};
use std::cell::RefCell;
use std::option::Option;
pub struct TestUserDao {
pub user_map: RefCell<Vec<User>>,
}
impl TestUserDao {
pub fn new() -> Self {
Self {
user_map: RefCell::new(Vec::new()),
}
}
}
impl UserDao for TestUserDao {
fn create_user(&self, username: &str, password: &str) -> Option<User> {
let u = User {
id: (self.user_map.borrow().len() + 1) as i32,
username: username.to_string(),
password: password.to_string(),
};
self.user_map.borrow_mut().push(u.clone());
Some(u)
}
fn get_user(&self, user: &str, pass: &str) -> Option<User> {
match self
.user_map
.borrow()
.iter()
.find(|&u| u.username == user && u.password == pass)
{
Some(u) => {
let copy = (*u).clone();
Some(copy)
}
None => None,
}
}
fn user_exists(&self, user: &str) -> bool {
self.user_map
.borrow()
.iter()
.find(|&u| u.username == user)
.is_some()
}
}
pub trait BodyReader {
fn read_to_str(&self) -> &str;
}
impl BodyReader for ResponseBody<Body> {
fn read_to_str(&self) -> &str {
match self {
ResponseBody::Body(Body::Bytes(ref b)) => std::str::from_utf8(b).unwrap(),
_ => panic!("Unknown response body"),
}
}
}
pub trait TypedBodyReader<'a, T>
where
T: Deserialize<'a>,
{
fn read_body(&'a self) -> T;
}
impl<'a, T: Deserialize<'a>> TypedBodyReader<'a, T> for ResponseBody<Body> {
fn read_body(&'a self) -> T {
match self {
ResponseBody::Body(Body::Bytes(ref b)) => {
serde_json::from_str(std::str::from_utf8(b).unwrap()).unwrap()
}
_ => panic!("Unknown response body"),
}
}
}