mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 11:03:27 +00:00
Basic HTTP auth for sd-server (#2314)
* basic http auth * fix types * Fix * auth docs
This commit is contained in:
parent
785dd74297
commit
9f5396133b
19
.github/workflows/ci.yml
vendored
19
.github/workflows/ci.yml
vendored
|
@ -11,6 +11,7 @@ env:
|
|||
CARGO_NET_RETRY: 10
|
||||
RUST_BACKTRACE: short
|
||||
RUSTUP_MAX_RETRIES: 10
|
||||
SD_AUTH: disabled
|
||||
|
||||
# Cancel previous runs of the same workflow on the same branch.
|
||||
concurrency:
|
||||
|
@ -107,10 +108,10 @@ jobs:
|
|||
with:
|
||||
swap-size-mb: 3072
|
||||
root-reserve-mb: 6144
|
||||
remove-dotnet: "true"
|
||||
remove-codeql: "true"
|
||||
remove-haskell: "true"
|
||||
remove-docker-images: "true"
|
||||
remove-dotnet: 'true'
|
||||
remove-codeql: 'true'
|
||||
remove-haskell: 'true'
|
||||
remove-docker-images: 'true'
|
||||
|
||||
- name: Symlink target to C:\
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
|
@ -143,7 +144,7 @@ jobs:
|
|||
if: steps.filter.outcome != 'success' || steps.filter.outputs.changes == 'true'
|
||||
uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
restore-cache: "false"
|
||||
restore-cache: 'false'
|
||||
|
||||
- name: Run rustfmt
|
||||
if: steps.filter.outcome != 'success' || steps.filter.outputs.changes == 'true'
|
||||
|
@ -162,10 +163,10 @@ jobs:
|
|||
with:
|
||||
swap-size-mb: 3072
|
||||
root-reserve-mb: 6144
|
||||
remove-dotnet: "true"
|
||||
remove-codeql: "true"
|
||||
remove-haskell: "true"
|
||||
remove-docker-images: "true"
|
||||
remove-dotnet: 'true'
|
||||
remove-codeql: 'true'
|
||||
remove-haskell: 'true'
|
||||
remove-docker-images: 'true'
|
||||
|
||||
- name: Symlink target to C:\
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
|
|
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -8679,11 +8679,13 @@ name = "sd-server"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"base64 0.21.7",
|
||||
"http",
|
||||
"include_dir",
|
||||
"mime_guess",
|
||||
"rspc",
|
||||
"sd-core",
|
||||
"secstr",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
@ -8781,6 +8783,15 @@ dependencies = [
|
|||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secstr"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e04f657244f605c4cf38f6de5993e8bd050c8a303f86aeabff142d5c7c113e12"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.9.2"
|
||||
|
|
|
@ -12,18 +12,17 @@ ai-models = ["sd-core/ai"]
|
|||
|
||||
[dependencies]
|
||||
# Spacedrive Sub-crates
|
||||
sd-core = { path = "../../core", features = [
|
||||
"ffmpeg",
|
||||
"heif",
|
||||
] }
|
||||
sd-core = { path = "../../core", features = ["ffmpeg", "heif"] }
|
||||
|
||||
axum = { workspace = true }
|
||||
axum = { workspace = true, features = ["headers"] }
|
||||
http = { workspace = true }
|
||||
rspc = { workspace = true, features = ["axum"] }
|
||||
tokio = { workspace = true, features = ["sync", "rt-multi-thread", "signal"] }
|
||||
tracing = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
|
||||
tempfile = "3.10.1"
|
||||
|
||||
include_dir = "0.7.3"
|
||||
mime_guess = "2.0.4"
|
||||
secstr = "0.5.1"
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
use std::{env, net::SocketAddr, path::Path};
|
||||
use std::{collections::HashMap, env, net::SocketAddr, path::Path};
|
||||
|
||||
use axum::routing::get;
|
||||
use axum::{
|
||||
extract::{FromRequestParts, State},
|
||||
headers::{authorization::Basic, Authorization},
|
||||
http::Request,
|
||||
middleware::{self, Next},
|
||||
response::{IntoResponse, Response},
|
||||
routing::get,
|
||||
TypedHeader,
|
||||
};
|
||||
use sd_core::{custom_uri, Node};
|
||||
use tracing::info;
|
||||
use secstr::SecStr;
|
||||
use tracing::{info, warn};
|
||||
|
||||
mod utils;
|
||||
|
||||
|
@ -10,6 +19,46 @@ mod utils;
|
|||
static ASSETS_DIR: include_dir::Dir<'static> =
|
||||
include_dir::include_dir!("$CARGO_MANIFEST_DIR/../web/dist");
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
auth: HashMap<String, SecStr>,
|
||||
}
|
||||
|
||||
async fn basic_auth<B>(
|
||||
State(state): State<AppState>,
|
||||
request: Request<B>,
|
||||
next: Next<B>,
|
||||
) -> Response {
|
||||
let (mut parts, body) = request.into_parts();
|
||||
let Ok(TypedHeader(Authorization(hdr))) =
|
||||
TypedHeader::<Authorization<Basic>>::from_request_parts(&mut parts, &()).await
|
||||
else {
|
||||
return Response::builder()
|
||||
.status(401)
|
||||
.header("WWW-Authenticate", "Basic realm=\"Spacedrive\"")
|
||||
.body("Unauthorized".into_response().into_body())
|
||||
.expect("hardcoded response will be valid");
|
||||
};
|
||||
let request = Request::from_parts(parts, body);
|
||||
|
||||
if state.auth.len() != 0 {
|
||||
if state
|
||||
.auth
|
||||
.get(hdr.username())
|
||||
.and_then(|pass| Some(*pass == SecStr::from(hdr.password())))
|
||||
!= Some(true)
|
||||
{
|
||||
return Response::builder()
|
||||
.status(401)
|
||||
.header("WWW-Authenticate", "Basic realm=\"Spacedrive\"")
|
||||
.body("Unauthorized".into_response().into_body())
|
||||
.expect("hardcoded response will be valid");
|
||||
}
|
||||
}
|
||||
|
||||
next.run(request).await
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let data_dir = match env::var("DATA_DIR") {
|
||||
|
@ -43,6 +92,54 @@ async fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
let (auth, disabled) = {
|
||||
let input = env::var("SD_AUTH").unwrap_or_default();
|
||||
|
||||
if input == "disabled" {
|
||||
(Default::default(), true)
|
||||
} else {
|
||||
(
|
||||
input
|
||||
.split(',')
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, s)| {
|
||||
if s.len() == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut parts = s.split(':');
|
||||
|
||||
let result = parts.next().and_then(|user| {
|
||||
parts
|
||||
.next()
|
||||
.map(|pass| (user.to_string(), SecStr::from(pass)))
|
||||
});
|
||||
if result.is_none() {
|
||||
warn!("Found invalid credential {i}. Skipping...");
|
||||
}
|
||||
result
|
||||
})
|
||||
.collect::<HashMap<_, _>>(),
|
||||
false,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// We require credentials in production builds (unless explicitly disabled)
|
||||
if auth.len() == 0 && !disabled {
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
warn!("The 'SD_AUTH' environment variable is not set!");
|
||||
warn!("If you want to disable auth set 'SD_AUTH=disabled', or");
|
||||
warn!("Provide your credentials in the following format 'SD_AUTH=username:password,username2:password2'");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let state = AppState { auth };
|
||||
|
||||
let (node, router) = match Node::new(
|
||||
data_dir,
|
||||
sd_core::Env {
|
||||
|
@ -140,7 +237,8 @@ async fn main() {
|
|||
#[cfg(not(feature = "assets"))]
|
||||
let app = app
|
||||
.route("/", get(|| async { "Spacedrive Server!" }))
|
||||
.fallback(|| async { "404 Not Found: We're past the event horizon..." });
|
||||
.fallback(|| async { "404 Not Found: We're past the event horizon..." })
|
||||
.layer(middleware::from_fn_with_state(state, basic_auth));
|
||||
|
||||
let mut addr = "[::]:8080".parse::<SocketAddr>().unwrap(); // This listens on IPv6 and IPv4
|
||||
addr.set_port(port);
|
||||
|
|
|
@ -34,15 +34,20 @@ You can run Spacedrive in a Docker container using the following command.
|
|||
type="note"
|
||||
text="For the best performance of the docker container, we recommend to run on Linux (linux/amd64). The container is not yet optimized for other platforms."
|
||||
/>
|
||||
<Notice
|
||||
type="warning"
|
||||
text="Currently, Spacedrive's Docker Server does not support authentication methods. Use at your own risk as your data will not be protected from other devices (or users) on your network. The feature is under active development and you can check when it will launch on the [roadmap](/roadmap)."
|
||||
/>
|
||||
|
||||
```bash
|
||||
docker run -d --name spacedrive -p 8080:8080 -v /var/spacedrive:/var/spacedrive ghcr.io/spacedriveapp/spacedrive/server
|
||||
docker run -d --name spacedrive -p 8080:8080 -e SD_AUTH=admin,spacedrive -v /var/spacedrive:/var/spacedrive ghcr.io/spacedriveapp/spacedrive/server
|
||||
```
|
||||
|
||||
#### Authentication
|
||||
|
||||
When using the Spacedrive server you can use the `SD_AUTH` environment variable to configure authentication.
|
||||
|
||||
Valid values:
|
||||
- `SD_AUTH=disabled` - Disables authentication.
|
||||
- `SD_AUTH=username:password` - Enables authentication for a single user.
|
||||
- `SD_AUTH=username:password,username1:password1` - Enables authentication with multiple users (you can add as many users as you want).
|
||||
|
||||
### Mobile (Preview)
|
||||
|
||||
Take your Spacedrive library on the go with our mobile apps. You can join the betas by following the links below.
|
||||
|
|
|
@ -88,30 +88,31 @@ const OperationGroup = ({ group }: { group: MessageGroup }) => {
|
|||
|
||||
function calculateGroups(messages: CRDTOperation[]) {
|
||||
return messages.reduce<MessageGroup[]>((acc, op) => {
|
||||
const { data } = op;
|
||||
// TODO: fix Typescript
|
||||
// const { data } = op;
|
||||
|
||||
const id = JSON.stringify(op.record_id);
|
||||
// const id = JSON.stringify(op.record_id);
|
||||
|
||||
const latest = (() => {
|
||||
const latest = acc[acc.length - 1];
|
||||
// const latest = (() => {
|
||||
// const latest = acc[acc.length - 1];
|
||||
|
||||
if (!latest || latest.model !== op.model || latest.id !== id) {
|
||||
const group: MessageGroup = {
|
||||
model: op.model,
|
||||
id,
|
||||
messages: []
|
||||
};
|
||||
// if (!latest || latest.model !== op.model || latest.id !== id) {
|
||||
// const group: MessageGroup = {
|
||||
// model: op.model,
|
||||
// id,
|
||||
// messages: []
|
||||
// };
|
||||
|
||||
acc.push(group);
|
||||
// acc.push(group);
|
||||
|
||||
return group;
|
||||
} else return latest;
|
||||
})();
|
||||
// return group;
|
||||
// } else return latest;
|
||||
// })();
|
||||
|
||||
latest.messages.push({
|
||||
data,
|
||||
timestamp: op.timestamp
|
||||
});
|
||||
// latest.messages.push({
|
||||
// data,
|
||||
// timestamp: op.timestamp
|
||||
// });
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
|
Loading…
Reference in a new issue