Initial API setup

Right now we are just listing files in a given subdirectory with not
authentication.
This commit is contained in:
Cameron Cordes
2020-07-07 19:53:12 -04:00
commit 36f7351627
6 changed files with 1907 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

1738
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

15
Cargo.toml Normal file
View File

@@ -0,0 +1,15 @@
[package]
name = "image-api"
version = "0.1.0"
authors = ["Cameron Cordes <cameronc.dev@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "2.0"
actix-rt = "1.0"
futures = "0.3.5"
jsonwebtoken = "7.2.0"
serde = "1.0"
serde_json = "1.0"

70
src/data/mod.rs Normal file
View File

@@ -0,0 +1,70 @@
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;
use std::str::FromStr;
#[derive(Deserialize)]
pub struct Claims {
pub sub: String,
pub exp: u32,
}
impl FromStr for Claims {
type Err = jsonwebtoken::errors::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
println!("Parsing token: {}", s);
let claims = match decode::<Claims>(
s,
&DecodingKey::from_secret("secret_token".as_ref()),
&Validation::new(Algorithm::HS256),
) {
Ok(data) => Ok(data.claims),
Err(other) => Err(other),
};
return claims;
}
}
impl FromRequest for Claims {
type Error = Error;
type Future = Ready<Result<Self, Self::Error>>;
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_else(|_| "")),
None => Err(jsonwebtoken::errors::Error::from(
jsonwebtoken::errors::ErrorKind::InvalidToken,
)),
};
if let Ok(claims) = claims {
ok(claims)
} else {
err(ErrorUnauthorized("Bad token"))
}
}
}
#[derive(Deserialize)]
pub struct ThumbnailRequest {
pub path: 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,
}

33
src/files.rs Normal file
View File

@@ -0,0 +1,33 @@
use std::ffi::OsStr;
use std::fs::read_dir;
use std::io;
use std::path::{Path, PathBuf};
pub fn list_files(dir: PathBuf) -> io::Result<Vec<PathBuf>> {
let files = read_dir(dir)?
.map(|res| res.unwrap())
.filter(|entry| is_image_or_video(&entry.path()) || entry.file_type().unwrap().is_dir())
.map(|entry| entry.path())
.map(|path: PathBuf| {
let relative = path.strip_prefix(std::env::current_dir().unwrap()).unwrap();
relative.to_path_buf()
})
.collect::<Vec<PathBuf>>();
Ok(files)
}
fn is_image_or_video(path: &Path) -> bool {
let extension = &path
.extension()
.unwrap_or_else(|| OsStr::new(""))
.to_str()
.unwrap_or_else(|| "")
.to_lowercase();
return extension == &"png"
|| extension == &"jpg"
|| extension == &"jpeg"
|| extension == &"rs"
|| extension == &"mp4";
}

50
src/main.rs Normal file
View File

@@ -0,0 +1,50 @@
use actix_web::web::{HttpResponse, Json};
use actix_web::{get, post, App, HttpServer, Responder};
use data::{LoginRequest, ThumbnailRequest};
use std::path::PathBuf;
use crate::files::list_files;
mod data;
mod files;
#[post("/register")]
async fn register() -> impl Responder {
"".to_owned()
}
#[post("/login")]
async fn login(_creds: Json<LoginRequest>) -> impl Responder {
"".to_owned()
}
#[get("/photos")]
async fn list_photos(req: Json<ThumbnailRequest>) -> impl Responder {
println!("{}", req.path);
let path = &req.path;
match path {
path if path.contains("..") => HttpResponse::BadRequest().finish(),
path => {
let path = PathBuf::from(path);
if path.is_relative() {
let mut full_path = std::env::current_dir().unwrap();
full_path.push(path);
let files = list_files(full_path);
HttpResponse::Ok().json(files.unwrap_or_default())
} else {
HttpResponse::BadRequest().finish()
}
}
}
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(login).service(list_photos))
.bind("127.0.0.1:8088")?
.run()
.await
}