mirror of https://github.com/raftario/filite.git
Auth filters
This commit is contained in:
parent
8a6824b7c0
commit
d3032176e4
|
@ -122,7 +122,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.12.2"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
|
@ -349,6 +349,7 @@ version = "0.3.0"
|
|||
dependencies = [
|
||||
"anyhow 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"askama 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"chrono 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"openssl 0.10.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -542,7 +543,7 @@ name = "headers"
|
|||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"base64 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bytes 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"headers-core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1143,7 +1144,7 @@ name = "rust-argon2"
|
|||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"base64 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"constant_time_eq 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1929,7 +1930,7 @@ dependencies = [
|
|||
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
|
||||
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
|
||||
"checksum base64 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e223af0dc48c96d4f8342ec01a4974f139df863896b316681efd36742f22cc67"
|
||||
"checksum base64 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
|
||||
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
"checksum blake2b_simd 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a"
|
||||
"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
||||
|
|
|
@ -17,6 +17,7 @@ license = "MIT"
|
|||
[dependencies]
|
||||
anyhow = "1.0.32"
|
||||
askama = "0.10.3"
|
||||
base64 = "0.12.3"
|
||||
chrono = { version = "0.4.13", features = ["serde"] }
|
||||
futures = "0.3.5"
|
||||
rand = "0.7.3"
|
||||
|
|
61
src/auth.rs
61
src/auth.rs
|
@ -1,9 +1,69 @@
|
|||
use crate::{
|
||||
db,
|
||||
db::models::User,
|
||||
reject::{self, TryExt},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use argon2::Config;
|
||||
use rand::Rng;
|
||||
use sqlx::SqlitePool;
|
||||
use tokio::task;
|
||||
use warp::{Filter, Rejection};
|
||||
|
||||
pub fn auth_optional(
|
||||
pool: &'static SqlitePool,
|
||||
) -> impl Filter<Extract = (Option<User>,), Error = Rejection> + Copy + Send + Sync + 'static {
|
||||
warp::header::optional("Authorization").and_then(move |header| async move {
|
||||
match header {
|
||||
Some(h) => match user(h, pool).await {
|
||||
Ok(u) => Ok(Some(u)),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
None => Ok(None),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn auth_required(
|
||||
pool: &'static SqlitePool,
|
||||
) -> impl Filter<Extract = (User,), Error = Rejection> + Copy + Send + Sync + 'static {
|
||||
warp::header::header("Authorization").and_then(move |header| user(header, pool))
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
async fn user(header: String, pool: &SqlitePool) -> Result<User, Rejection> {
|
||||
if &header[..5] != "Basic" {
|
||||
return Err(reject::unauthorized());
|
||||
}
|
||||
|
||||
let decoded = base64::decode(&header[6..]).or_401()?;
|
||||
|
||||
let (user, password) = {
|
||||
let mut split = None;
|
||||
for (i, b) in decoded.iter().copied().enumerate() {
|
||||
if b == b':' {
|
||||
split = Some(i);
|
||||
}
|
||||
}
|
||||
let split = split.or_401()?;
|
||||
|
||||
let (u, p) = (&decoded[..split], &decoded[(split + 1)..]);
|
||||
(std::str::from_utf8(u).or_401()?, p)
|
||||
};
|
||||
|
||||
let user = db::user(user, pool).await.or_500()?.or_401()?;
|
||||
if !verify(user.password.clone(), password.to_owned())
|
||||
.await
|
||||
.or_500()?
|
||||
{
|
||||
return Err(reject::unauthorized());
|
||||
}
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
// TODO: Allow custom configuration
|
||||
#[tracing::instrument(level = "debug", skip(password))]
|
||||
async fn hash(password: Vec<u8>) -> Result<String> {
|
||||
let config = Config::default();
|
||||
Ok(task::spawn_blocking(move || {
|
||||
|
@ -13,6 +73,7 @@ async fn hash(password: Vec<u8>) -> Result<String> {
|
|||
.await??)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(encoded, password))]
|
||||
async fn verify(encoded: String, password: Vec<u8>) -> Result<bool> {
|
||||
Ok(task::spawn_blocking(move || argon2::verify_encoded(&encoded, &password)).await??)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::utils::DefaultExt;
|
||||
use crate::util::DefaultExt;
|
||||
use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
mod auth;
|
||||
mod config;
|
||||
mod db;
|
||||
mod reject;
|
||||
mod routes;
|
||||
mod runtime;
|
||||
mod utils;
|
||||
mod util;
|
||||
|
||||
use anyhow::Error;
|
||||
use config::Config;
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
use std::fmt::Display;
|
||||
use warp::{
|
||||
http::StatusCode,
|
||||
reject::{Reject, Rejection},
|
||||
reply::{Reply, Response},
|
||||
};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum FiliteRejection {
|
||||
NotFound,
|
||||
Unauthorized,
|
||||
InternalServerError,
|
||||
}
|
||||
impl Reject for FiliteRejection {}
|
||||
impl Reply for FiliteRejection {
|
||||
fn into_response(self) -> Response {
|
||||
match self {
|
||||
FiliteRejection::NotFound => {
|
||||
warp::reply::with_status("Not Found", StatusCode::NOT_FOUND).into_response()
|
||||
}
|
||||
FiliteRejection::Unauthorized => warp::reply::with_status(
|
||||
warp::reply::with_header(
|
||||
"Unauthorized",
|
||||
"WWW-Authenticate",
|
||||
r#"Basic realm="filite""#,
|
||||
),
|
||||
StatusCode::UNAUTHORIZED,
|
||||
)
|
||||
.into_response(),
|
||||
FiliteRejection::InternalServerError => {
|
||||
warp::reply::with_status("Internal Server Error", StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unauthorized() -> Rejection {
|
||||
warp::reject::custom(FiliteRejection::Unauthorized)
|
||||
}
|
||||
|
||||
pub trait TryExt<T> {
|
||||
fn or_404(self) -> Result<T, Rejection>;
|
||||
fn or_401(self) -> Result<T, Rejection>;
|
||||
fn or_500(self) -> Result<T, Rejection>;
|
||||
}
|
||||
|
||||
impl<T, E: Display> TryExt<T> for Result<T, E> {
|
||||
fn or_404(self) -> Result<T, Rejection> {
|
||||
self.map_err(|e| {
|
||||
tracing::info!("{}", e);
|
||||
warp::reject::custom(FiliteRejection::NotFound)
|
||||
})
|
||||
}
|
||||
|
||||
fn or_401(self) -> Result<T, Rejection> {
|
||||
self.map_err(|e| {
|
||||
tracing::info!("{}", e);
|
||||
warp::reject::custom(FiliteRejection::Unauthorized)
|
||||
})
|
||||
}
|
||||
|
||||
fn or_500(self) -> Result<T, Rejection> {
|
||||
self.map_err(|e| {
|
||||
tracing::error!("{}", e);
|
||||
warp::reject::custom(FiliteRejection::InternalServerError)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TryExt<T> for Option<T> {
|
||||
fn or_404(self) -> Result<T, Rejection> {
|
||||
self.ok_or_else(|| warp::reject::custom(FiliteRejection::NotFound))
|
||||
}
|
||||
|
||||
fn or_401(self) -> Result<T, Rejection> {
|
||||
self.ok_or_else(|| warp::reject::custom(FiliteRejection::Unauthorized))
|
||||
}
|
||||
|
||||
fn or_500(self) -> Result<T, Rejection> {
|
||||
self.ok_or_else(|| warp::reject::custom(FiliteRejection::InternalServerError))
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug")]
|
||||
pub async fn handle_rejections(err: Rejection) -> Result<impl Reply, Rejection> {
|
||||
if err.is_not_found() {
|
||||
Ok(FiliteRejection::NotFound)
|
||||
} else if let Some(err) = err.find::<FiliteRejection>() {
|
||||
Ok(*err)
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue