mirror of
https://github.com/sigoden/dufs.git
synced 2026-04-09 09:09:03 +03:00
feat: support downloading via token auth (#603)
This commit is contained in:
69
src/auth.rs
69
src/auth.rs
@@ -2,11 +2,13 @@ use crate::{args::Args, server::Response, utils::unix_now};
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use base64::{engine::general_purpose::STANDARD, Engine as _};
|
||||
use ed25519_dalek::{ed25519::signature::SignerMut, Signature, SigningKey};
|
||||
use headers::HeaderValue;
|
||||
use hyper::{header::WWW_AUTHENTICATE, Method};
|
||||
use indexmap::IndexMap;
|
||||
use lazy_static::lazy_static;
|
||||
use md5::Context;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
@@ -14,7 +16,8 @@ use std::{
|
||||
use uuid::Uuid;
|
||||
|
||||
const REALM: &str = "DUFS";
|
||||
const DIGEST_AUTH_TIMEOUT: u32 = 604800; // 7 days
|
||||
const DIGEST_AUTH_TIMEOUT: u32 = 60 * 60 * 24 * 7; // 7 days
|
||||
const TOKEN_EXPIRATION: u64 = 1000 * 60 * 60 * 24 * 3; // 3 days
|
||||
|
||||
lazy_static! {
|
||||
static ref NONCESTARTHASH: Context = {
|
||||
@@ -105,11 +108,21 @@ impl AccessControl {
|
||||
path: &str,
|
||||
method: &Method,
|
||||
authorization: Option<&HeaderValue>,
|
||||
token: Option<&String>,
|
||||
guard_options: bool,
|
||||
) -> (Option<String>, Option<AccessPaths>) {
|
||||
if self.users.is_empty() {
|
||||
return (None, Some(AccessPaths::new(AccessPerm::ReadWrite)));
|
||||
}
|
||||
|
||||
if method == Method::GET {
|
||||
if let Some(token) = token {
|
||||
if let Ok((user, ap)) = self.verifty_token(token, path) {
|
||||
return (Some(user), ap.guard(path, method));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(authorization) = authorization {
|
||||
if let Some(user) = get_auth_user(authorization) {
|
||||
if let Some((pass, ap)) = self.users.get(&user) {
|
||||
@@ -135,6 +148,49 @@ impl AccessControl {
|
||||
|
||||
(None, None)
|
||||
}
|
||||
|
||||
pub fn generate_token(&self, path: &str, user: &str) -> Result<String> {
|
||||
let (pass, _) = self
|
||||
.users
|
||||
.get(user)
|
||||
.ok_or_else(|| anyhow!("Not found user '{user}'"))?;
|
||||
let exp = unix_now().as_millis() as u64 + TOKEN_EXPIRATION;
|
||||
let message = format!("{path}:{exp}");
|
||||
let mut signing_key = derive_secret_key(user, pass);
|
||||
let sig = signing_key.sign(message.as_bytes()).to_bytes();
|
||||
|
||||
let mut raw = Vec::with_capacity(64 + 8 + user.len());
|
||||
raw.extend_from_slice(&sig);
|
||||
raw.extend_from_slice(&exp.to_be_bytes());
|
||||
raw.extend_from_slice(user.as_bytes());
|
||||
|
||||
Ok(hex::encode(raw))
|
||||
}
|
||||
|
||||
fn verifty_token<'a>(&'a self, token: &str, path: &str) -> Result<(String, &'a AccessPaths)> {
|
||||
let raw = hex::decode(token)?;
|
||||
|
||||
let sig_bytes = &raw[..64];
|
||||
let exp_bytes = &raw[64..72];
|
||||
let user_bytes = &raw[72..];
|
||||
|
||||
let exp = u64::from_be_bytes(exp_bytes.try_into()?);
|
||||
if unix_now().as_millis() as u64 > exp {
|
||||
bail!("Token expired");
|
||||
}
|
||||
|
||||
let user = std::str::from_utf8(user_bytes)?;
|
||||
let (pass, ap) = self
|
||||
.users
|
||||
.get(user)
|
||||
.ok_or_else(|| anyhow!("Not found user '{user}'"))?;
|
||||
|
||||
let sig = Signature::from_bytes(&<[u8; 64]>::try_from(sig_bytes)?);
|
||||
|
||||
let message = format!("{path}:{exp}");
|
||||
derive_secret_key(user, pass).verify(message.as_bytes(), &sig)?;
|
||||
Ok((user.to_string(), ap))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, Eq)]
|
||||
@@ -422,6 +478,13 @@ pub fn check_auth(
|
||||
}
|
||||
}
|
||||
|
||||
fn derive_secret_key(user: &str, pass: &str) -> SigningKey {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(format!("{user}:{pass}").as_bytes());
|
||||
let hash = hasher.finalize();
|
||||
SigningKey::from_bytes(&hash.into())
|
||||
}
|
||||
|
||||
/// Check if a nonce is still valid.
|
||||
/// Return an error if it was never valid
|
||||
fn validate_nonce(nonce: &[u8]) -> Result<bool> {
|
||||
@@ -433,7 +496,7 @@ fn validate_nonce(nonce: &[u8]) -> Result<bool> {
|
||||
//get time
|
||||
if let Ok(secs_nonce) = u32::from_str_radix(&n[..8], 16) {
|
||||
//check time
|
||||
let now = unix_now()?;
|
||||
let now = unix_now();
|
||||
let secs_now = now.as_secs() as u32;
|
||||
|
||||
if let Some(dur) = secs_now.checked_sub(secs_nonce) {
|
||||
@@ -513,7 +576,7 @@ fn to_headermap(header: &[u8]) -> Result<HashMap<&[u8], &[u8]>, ()> {
|
||||
}
|
||||
|
||||
fn create_nonce() -> Result<String> {
|
||||
let now = unix_now()?;
|
||||
let now = unix_now();
|
||||
let secs = now.as_secs() as u32;
|
||||
let mut h = NONCESTARTHASH.clone();
|
||||
h.consume(secs.to_be_bytes());
|
||||
|
||||
Reference in New Issue
Block a user