Files
ImageApi/src/data/mod.rs

223 lines
5.5 KiB
Rust

use std::{fs, str::FromStr};
use anyhow::{anyhow, Context};
use chrono::{DateTime, Utc};
use log::error;
use actix_web::error::ErrorUnauthorized;
use actix_web::{dev, http::header, Error, FromRequest, HttpRequest};
use futures::future::{err, ok, Ready};
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
pub struct Token<'a> {
pub token: &'a str,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Claims {
pub sub: String,
pub exp: i64,
}
#[cfg(test)]
pub mod helper {
use super::Claims;
use chrono::{Duration, Utc};
impl Claims {
pub fn valid_user(user_id: String) -> Self {
Claims {
sub: user_id,
exp: (Utc::now() + Duration::minutes(1)).timestamp(),
}
}
}
}
pub fn secret_key() -> String {
if cfg!(test) {
String::from("test_key")
} else {
dotenv::var("SECRET_KEY").expect("SECRET_KEY env not set!")
}
}
impl FromStr for Claims {
type Err = jsonwebtoken::errors::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let token = *(s.split("Bearer ").collect::<Vec<_>>().last().unwrap_or(&""));
match decode::<Claims>(
token,
&DecodingKey::from_secret(secret_key().as_bytes()),
&Validation::new(Algorithm::HS256),
) {
Ok(data) => Ok(data.claims),
Err(other) => {
error!("DecodeError: {}", other);
Err(other)
}
}
}
}
impl FromRequest for Claims {
type Error = Error;
type Future = Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _payload: &mut dev::Payload) -> Self::Future {
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,
)
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct PhotosResponse {
pub photos: Vec<String>,
pub dirs: Vec<String>,
}
#[derive(Deserialize)]
pub struct FilesRequest {
pub path: String,
pub tag_ids: Option<String>, // comma separated numbers
pub tag_filter_mode: Option<FilterMode>,
pub recursive: Option<bool>,
}
#[derive(Copy, Clone, Deserialize, PartialEq, Debug)]
pub enum FilterMode {
Any,
All,
}
#[derive(Deserialize)]
pub struct ThumbnailRequest {
pub path: String,
pub size: Option<String>,
}
#[derive(Deserialize)]
pub struct LoginRequest {
pub username: String,
pub password: String,
}
#[derive(Deserialize)]
pub struct CreateAccountRequest {
pub username: String,
pub password: String,
pub confirmation: String,
}
#[derive(Deserialize)]
pub struct AddFavoriteRequest {
pub path: String,
}
#[derive(Debug, Serialize)]
pub struct MetadataResponse {
pub created: Option<i64>,
pub modified: Option<i64>,
pub size: u64,
}
impl From<fs::Metadata> for MetadataResponse {
fn from(metadata: fs::Metadata) -> Self {
MetadataResponse {
created: metadata.created().ok().map(|created| {
let utc: DateTime<Utc> = created.into();
utc.timestamp()
}),
modified: metadata.modified().ok().map(|modified| {
let utc: DateTime<Utc> = modified.into();
utc.timestamp()
}),
size: metadata.len(),
}
}
}
#[derive(Debug, Deserialize)]
pub struct AddTagRequest {
pub file_name: String,
pub tag_name: String,
}
#[derive(Deserialize)]
pub struct GetTagsRequest {
pub path: Option<String>,
}
#[cfg(test)]
mod tests {
use super::Claims;
use jsonwebtoken::errors::ErrorKind;
use std::str::FromStr;
#[test]
fn test_token_from_claims() {
let claims = Claims {
exp: 16136164790, // 2481-ish
sub: String::from("9"),
};
let c = Claims::from_str(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5IiwiZXhwIjoxNjEzNjE2NDc5MH0.9wwK4l8vhvq55YoueEljMbN_5uVTaAsGLLRPr0AuymE")
.unwrap();
assert_eq!(claims.sub, c.sub);
assert_eq!(claims.exp, c.exp);
}
#[test]
fn test_expired_token() {
let err = Claims::from_str(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5IiwiZXhwIjoxNn0.eZnfaNfiD54VMbphIqeBICeG9SzAtwNXntLwtTBihjY");
match err.unwrap_err().into_kind() {
ErrorKind::ExpiredSignature => assert!(true),
kind => {
println!("Unexpected error: {:?}", kind);
assert!(false)
}
}
}
#[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)
}
}
}
}