feat: support downloading via token auth (#603)

This commit is contained in:
sigoden
2025-08-02 14:37:49 +08:00
committed by GitHub
parent 089d30c5a5
commit 9c9fca75d3
7 changed files with 277 additions and 33 deletions

View File

@@ -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());