mirror of
https://github.com/sigoden/dufs.git
synced 2026-04-09 09:09:03 +03:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7dc0b0e218 | ||
|
|
6be36b8e51 | ||
|
|
8be545d3da | ||
|
|
4f3a8d275b | ||
|
|
9c412f4276 | ||
|
|
27c269d6a0 | ||
|
|
57b4a74279 |
18
CHANGELOG.md
18
CHANGELOG.md
@@ -2,6 +2,24 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [0.34.2] - 2023-06-05
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Ui refresh page after login ([#230](https://github.com/sigoden/dufs/issues/230))
|
||||
- Webdav only see public folder even logging in ([#231](https://github.com/sigoden/dufs/issues/231))
|
||||
|
||||
## [0.34.1] - 2023-06-02
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Auth logic ([#224](https://github.com/sigoden/dufs/issues/224))
|
||||
- Allow all cors headers and methods ([#225](https://github.com/sigoden/dufs/issues/225))
|
||||
|
||||
### Refactor
|
||||
|
||||
- Ui checkAuth ([#226](https://github.com/sigoden/dufs/issues/226))
|
||||
|
||||
## [0.34.0] - 2023-06-01
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -436,7 +436,7 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "dufs"
|
||||
version = "0.34.0"
|
||||
version = "0.34.2"
|
||||
dependencies = [
|
||||
"alphanumeric-sort",
|
||||
"anyhow",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dufs"
|
||||
version = "0.34.0"
|
||||
version = "0.34.2"
|
||||
edition = "2021"
|
||||
authors = ["sigoden <sigoden@gmail.com>"]
|
||||
description = "Dufs is a distinctive utility file server"
|
||||
|
||||
@@ -218,8 +218,11 @@ Uploader.runQueue = async () => {
|
||||
let uploader = Uploader.queues.shift();
|
||||
if (!Uploader.auth) {
|
||||
Uploader.auth = true;
|
||||
const success = await checkAuth(true);
|
||||
Uploader.auth = !!success;
|
||||
try {
|
||||
await checkAuth()
|
||||
} catch {
|
||||
Uploader.auth = false;
|
||||
}
|
||||
}
|
||||
uploader.ajax();
|
||||
}
|
||||
@@ -439,7 +442,14 @@ function setupAuth() {
|
||||
} else {
|
||||
const $loginBtn = document.querySelector(".login-btn");
|
||||
$loginBtn.classList.remove("hidden");
|
||||
$loginBtn.addEventListener("click", () => checkAuth(true));
|
||||
$loginBtn.addEventListener("click", async () => {
|
||||
try {
|
||||
await checkAuth()
|
||||
location.reload();
|
||||
} catch (err) {
|
||||
alert(err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -651,9 +661,8 @@ async function saveChange() {
|
||||
}
|
||||
}
|
||||
|
||||
async function checkAuth(alert = false) {
|
||||
async function checkAuth() {
|
||||
if (!DATA.auth) return;
|
||||
try {
|
||||
const res = await fetch(baseUrl(), {
|
||||
method: "WRITEABLE",
|
||||
});
|
||||
@@ -661,15 +670,6 @@ async function checkAuth(alert = false) {
|
||||
document.querySelector(".login-btn").classList.add("hidden");
|
||||
$userBtn.classList.remove("hidden");
|
||||
$userBtn.title = "";
|
||||
return true;
|
||||
} catch (err) {
|
||||
let message = `Check auth, ${err.message}`;
|
||||
if (alert) {
|
||||
alert(message);
|
||||
} else {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -808,7 +808,7 @@ function encodedStr(rawStr) {
|
||||
|
||||
async function assertResOK(res) {
|
||||
if (!(res.status >= 200 && res.status < 300)) {
|
||||
throw new Error(await res.text())
|
||||
throw new Error(await res.text() || `Invalid status ${res.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
30
src/auth.rs
30
src/auth.rs
@@ -80,8 +80,8 @@ impl AccessControl {
|
||||
Ok(Self { users, anony })
|
||||
}
|
||||
|
||||
pub fn valid(&self) -> bool {
|
||||
!self.users.is_empty() || self.anony.is_some()
|
||||
pub fn exist(&self) -> bool {
|
||||
!self.users.is_empty()
|
||||
}
|
||||
|
||||
pub fn guard(
|
||||
@@ -229,8 +229,8 @@ impl AccessPaths {
|
||||
pub enum AccessPerm {
|
||||
#[default]
|
||||
IndexOnly,
|
||||
ReadWrite,
|
||||
ReadOnly,
|
||||
ReadWrite,
|
||||
}
|
||||
|
||||
impl AccessPerm {
|
||||
@@ -257,18 +257,14 @@ pub enum AuthMethod {
|
||||
}
|
||||
|
||||
impl AuthMethod {
|
||||
pub fn www_auth(&self, stale: bool) -> Result<String> {
|
||||
pub fn www_auth(&self) -> Result<String> {
|
||||
match self {
|
||||
AuthMethod::Basic => Ok(format!("Basic realm=\"{REALM}\"")),
|
||||
AuthMethod::Digest => {
|
||||
let str_stale = if stale { "stale=true," } else { "" };
|
||||
Ok(format!(
|
||||
"Digest realm=\"{}\",nonce=\"{}\",{}qop=\"auth\"",
|
||||
AuthMethod::Digest => Ok(format!(
|
||||
"Digest realm=\"{}\",nonce=\"{}\",qop=\"auth\"",
|
||||
REALM,
|
||||
create_nonce()?,
|
||||
str_stale
|
||||
))
|
||||
}
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,4 +515,16 @@ mod tests {
|
||||
assert_eq!(paths.find("dir2", true), None);
|
||||
assert!(paths.find("dir1/file", true).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_access_paths_perm() {
|
||||
let mut paths = AccessPaths::default();
|
||||
assert_eq!(paths.perm(), AccessPerm::IndexOnly);
|
||||
paths.set_perm(AccessPerm::ReadOnly);
|
||||
assert_eq!(paths.perm(), AccessPerm::ReadOnly);
|
||||
paths.set_perm(AccessPerm::ReadWrite);
|
||||
assert_eq!(paths.perm(), AccessPerm::ReadWrite);
|
||||
paths.set_perm(AccessPerm::ReadOnly);
|
||||
assert_eq!(paths.perm(), AccessPerm::ReadWrite);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use crate::auth::AccessPaths;
|
||||
use crate::auth::{AccessPaths, AccessPerm};
|
||||
use crate::streamer::Streamer;
|
||||
use crate::utils::{
|
||||
decode_uri, encode_uri, get_file_mtime_and_mode, get_file_name, glob, try_get_file_name,
|
||||
@@ -340,6 +340,12 @@ impl Server {
|
||||
method => match method.as_str() {
|
||||
"PROPFIND" => {
|
||||
if is_dir {
|
||||
let access_paths = if access_paths.perm().indexonly() {
|
||||
// see https://github.com/sigoden/dufs/issues/229
|
||||
AccessPaths::new(AccessPerm::ReadOnly)
|
||||
} else {
|
||||
access_paths
|
||||
};
|
||||
self.handle_propfind_dir(path, headers, access_paths, &mut res)
|
||||
.await?;
|
||||
} else if is_file {
|
||||
@@ -759,7 +765,7 @@ impl Server {
|
||||
uri_prefix: self.args.uri_prefix.clone(),
|
||||
allow_upload: self.args.allow_upload,
|
||||
allow_delete: self.args.allow_delete,
|
||||
auth: self.args.auth.valid(),
|
||||
auth: self.args.auth.exist(),
|
||||
user,
|
||||
editable,
|
||||
};
|
||||
@@ -974,7 +980,7 @@ impl Server {
|
||||
allow_search: self.args.allow_search,
|
||||
allow_archive: self.args.allow_archive,
|
||||
dir_exists: exist,
|
||||
auth: self.args.auth.valid(),
|
||||
auth: self.args.auth.exist(),
|
||||
user,
|
||||
paths,
|
||||
};
|
||||
@@ -999,7 +1005,7 @@ impl Server {
|
||||
}
|
||||
|
||||
fn auth_reject(&self, res: &mut Response) -> Result<()> {
|
||||
let value = self.args.auth_method.www_auth(false)?;
|
||||
let value = self.args.auth_method.www_auth()?;
|
||||
set_webdav_headers(res);
|
||||
res.headers_mut().insert(WWW_AUTHENTICATE, value.parse()?);
|
||||
// set 401 to make the browser pop up the login box
|
||||
@@ -1286,17 +1292,15 @@ fn add_cors(res: &mut Response) {
|
||||
.typed_insert(AccessControlAllowCredentials);
|
||||
res.headers_mut().insert(
|
||||
"Access-Control-Allow-Methods",
|
||||
HeaderValue::from_static("GET,HEAD,PUT,OPTIONS,DELETE,PROPFIND,COPY,MOVE"),
|
||||
HeaderValue::from_static("*"),
|
||||
);
|
||||
res.headers_mut().insert(
|
||||
"Access-Control-Allow-Headers",
|
||||
HeaderValue::from_static("Authorization,Destination,Range,Content-Type"),
|
||||
HeaderValue::from_static("Authorization,*"),
|
||||
);
|
||||
res.headers_mut().insert(
|
||||
"Access-Control-Expose-Headers",
|
||||
HeaderValue::from_static(
|
||||
"WWW-Authenticate,Content-Range,Accept-Ranges,Content-Disposition",
|
||||
),
|
||||
HeaderValue::from_static("Authorization,*"),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,23 @@ fn auth(#[with(&["--auth", "user:pass@/:rw", "-A"])] server: TestServer) -> Resu
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn auth_and_public(
|
||||
#[with(&["--auth", "user:pass@/:rw|@/", "-A"])] server: TestServer,
|
||||
) -> Result<(), Error> {
|
||||
let url = format!("{}file1", server.url());
|
||||
let resp = fetch!(b"PUT", &url).body(b"abc".to_vec()).send()?;
|
||||
assert_eq!(resp.status(), 401);
|
||||
let resp = fetch!(b"PUT", &url)
|
||||
.body(b"abc".to_vec())
|
||||
.send_with_digest_auth("user", "pass")?;
|
||||
assert_eq!(resp.status(), 201);
|
||||
let resp = fetch!(b"GET", &url).send()?;
|
||||
assert_eq!(resp.status(), 200);
|
||||
assert_eq!(resp.text()?, "abc");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn auth_skip(#[with(&["--auth", "@/"])] server: TestServer) -> Result<(), Error> {
|
||||
let resp = reqwest::blocking::get(server.url())?;
|
||||
@@ -184,3 +201,15 @@ fn auth_partial_index(
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
fn no_auth_propfind_dir(
|
||||
#[with(&["--auth", "user:pass@/:rw", "--auth", "@/dir-assets", "-A"])] server: TestServer,
|
||||
) -> Result<(), Error> {
|
||||
let resp = fetch!(b"PROPFIND", server.url()).send()?;
|
||||
assert_eq!(resp.status(), 207);
|
||||
let body = resp.text()?;
|
||||
assert!(body.contains("<D:href>/dir-assets/</D:href>"));
|
||||
assert!(body.contains("<D:href>/dir1/</D:href>"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -19,15 +19,15 @@ fn cors(#[with(&["--enable-cors"])] server: TestServer) -> Result<(), Error> {
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers().get("access-control-allow-methods").unwrap(),
|
||||
"GET,HEAD,PUT,OPTIONS,DELETE,PROPFIND,COPY,MOVE"
|
||||
"*"
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers().get("access-control-allow-headers").unwrap(),
|
||||
"Authorization,Destination,Range,Content-Type"
|
||||
"Authorization,*"
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers().get("access-control-expose-headers").unwrap(),
|
||||
"WWW-Authenticate,Content-Range,Accept-Ranges,Content-Disposition"
|
||||
"Authorization,*"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user