mirror of https://github.com/veypi/OneAuth.git
update
parent
c180cd4241
commit
dfd1549f11
@ -0,0 +1,126 @@
|
||||
//
|
||||
// app.rs
|
||||
// Copyright (C) 2023 veypi <i@veypi.com>
|
||||
// 2023-11-07 00:58
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use actix_web::web;
|
||||
|
||||
use dav_server::{
|
||||
actix::{DavRequest, DavResponse},
|
||||
body::Body,
|
||||
fakels::FakeLs,
|
||||
localfs::LocalFs,
|
||||
DavConfig, DavHandler,
|
||||
};
|
||||
|
||||
use http::Response;
|
||||
use http_auth_basic::Credentials;
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::{
|
||||
models::{self, UserPlugin},
|
||||
AppState, Error, Result,
|
||||
};
|
||||
|
||||
pub fn client() -> DavHandler {
|
||||
DavHandler::builder()
|
||||
.locksystem(FakeLs::new())
|
||||
.strip_prefix("/fs/a/")
|
||||
.build_handler()
|
||||
}
|
||||
pub async fn dav_handler(
|
||||
id: web::Path<(String, String)>,
|
||||
req: DavRequest,
|
||||
davhandler: web::Data<DavHandler>,
|
||||
stat: web::Data<AppState>,
|
||||
) -> DavResponse {
|
||||
let root = stat.fs_root.clone();
|
||||
let id = id.into_inner().0;
|
||||
info!("start app: {}", id);
|
||||
match handle_file(&req, stat).await {
|
||||
Ok(()) => {
|
||||
let p = Path::new(&root).join(format!("app/{}/", id));
|
||||
if !p.exists() {
|
||||
match fs::create_dir_all(p.clone()) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
warn!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("mount {}", p.to_str().unwrap());
|
||||
let config = DavConfig::new()
|
||||
.filesystem(LocalFs::new(p, false, false, true))
|
||||
.strip_prefix(format!("/fs/a/{}/", id));
|
||||
davhandler.handle_with(config, req.request).await.into()
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("handle file failed: {}", e);
|
||||
Response::builder()
|
||||
.status(401)
|
||||
.header("WWW-Authenticate", "Basic realm=\"file\"")
|
||||
.body(Body::from("please auth".to_string()))
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_file(
|
||||
req: &dav_server::actix::DavRequest,
|
||||
stat: web::Data<AppState>,
|
||||
) -> Result<()> {
|
||||
let p = req.request.uri();
|
||||
let headers = req.request.headers();
|
||||
let m = req.request.method();
|
||||
info!("access {} to {}", m, p);
|
||||
let authorization = headers.get("authorization");
|
||||
match authorization {
|
||||
Some(au) => {
|
||||
if let Some((auth_type, encoded_credentials)) =
|
||||
au.to_str().unwrap_or("").split_once(' ')
|
||||
{
|
||||
if encoded_credentials.contains(' ') {
|
||||
// Invalid authorization token received
|
||||
return Err(Error::InvalidToken);
|
||||
}
|
||||
match auth_type.to_lowercase().as_str() {
|
||||
"basic" => {
|
||||
let credentials = Credentials::decode(encoded_credentials.to_string())?;
|
||||
info!("{}|{}", credentials.user_id, credentials.password);
|
||||
if credentials.user_id == "cli" && credentials.password == "cli" {
|
||||
return Ok(());
|
||||
}
|
||||
match models::user::Entity::find()
|
||||
.filter(models::user::Column::Username.eq(credentials.user_id))
|
||||
.one(stat.db())
|
||||
.await?
|
||||
{
|
||||
Some(u) => {
|
||||
u.check_pass(&credentials.password)?;
|
||||
return Ok(());
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
"bearer" => {
|
||||
let t = models::Token::from(encoded_credentials, &stat.key)?;
|
||||
if t.is_valid() {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::InvalidScheme(auth_type.to_string()));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
Err(Error::NotAuthed)
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
//
|
||||
// mod.rs
|
||||
// Copyright (C) 2023 veypi <i@veypi.com>
|
||||
// 2023-11-07 00:07
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
mod app;
|
||||
mod usr;
|
||||
use std::future::{ready, Ready};
|
||||
|
||||
use actix_web::{
|
||||
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
|
||||
web, Error,
|
||||
};
|
||||
use futures_util::future::LocalBoxFuture;
|
||||
|
||||
pub fn routes(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(
|
||||
actix_web::web::scope("u")
|
||||
.app_data(web::Data::new(usr::client()))
|
||||
.service(web::resource("/{tail:.*}").to(usr::dav_handler)),
|
||||
);
|
||||
cfg.service(
|
||||
actix_web::web::scope("a")
|
||||
.app_data(web::Data::new(app::client()))
|
||||
.service(web::resource("/{id}/{tail:.*}").to(app::dav_handler)),
|
||||
);
|
||||
}
|
||||
|
||||
pub struct FsWrap;
|
||||
|
||||
impl<S, B> Transform<S, ServiceRequest> for FsWrap
|
||||
where
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
S::Future: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Transform = FsMiddleware<S>;
|
||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
ready(Ok(FsMiddleware { service }))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FsMiddleware<S> {
|
||||
service: S,
|
||||
}
|
||||
|
||||
impl<S, B> Service<ServiceRequest> for FsMiddleware<S>
|
||||
where
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
S::Future: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = Error;
|
||||
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||
|
||||
forward_ready!(service);
|
||||
|
||||
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||
println!("start fs: {}", req.path());
|
||||
let reqheaders = req.headers().clone();
|
||||
let is_preflight = is_request_preflight(&req);
|
||||
let fut = self.service.call(req);
|
||||
|
||||
Box::pin(async move {
|
||||
let mut res = fut.await?;
|
||||
if is_preflight {
|
||||
let mut rt = actix_web::HttpResponse::Ok();
|
||||
if let Some(o) = reqheaders.get("Access-Control-Request-Headers") {
|
||||
rt.insert_header(("Access-Control-Allow-Headers", o.to_str().unwrap_or("")));
|
||||
};
|
||||
if let Some(o) = reqheaders.get("Access-Control-Request-Method") {
|
||||
rt.insert_header(("Access-Control-Allow-Methods", o.to_str().unwrap_or("")));
|
||||
};
|
||||
if let Some(o) = reqheaders.get("Origin") {
|
||||
rt.insert_header(("Access-Control-Allow-Origin", o.to_str().unwrap_or("")));
|
||||
};
|
||||
rt.insert_header((
|
||||
"Access-Control-Expose-Headers",
|
||||
"access-control-allow-origin, content-type",
|
||||
));
|
||||
rt.insert_header(("Access-Control-Allow-Credentials", "true"));
|
||||
rt.insert_header(("WWW-Authenticate", "Basic realm=\"file\""));
|
||||
res = ServiceResponse::new(
|
||||
res.request().to_owned(),
|
||||
rt.message_body(res.into_body())?,
|
||||
);
|
||||
return Ok(res);
|
||||
} else if let Some(o) = reqheaders.get("Origin") {
|
||||
res.headers_mut().insert(
|
||||
http::header::HeaderName::try_from("Access-Control-Allow-Origin").unwrap(),
|
||||
o.to_owned(),
|
||||
);
|
||||
};
|
||||
Ok(res)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to parse header value as HTTP method.
|
||||
fn header_value_try_into_method(hdr: &http::header::HeaderValue) -> Option<http::Method> {
|
||||
hdr.to_str()
|
||||
.ok()
|
||||
.and_then(|meth| http::Method::try_from(meth).ok())
|
||||
}
|
||||
|
||||
fn is_request_preflight(req: &ServiceRequest) -> bool {
|
||||
// check request method is OPTIONS
|
||||
if req.method() != http::Method::OPTIONS {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check follow-up request method is present and valid
|
||||
if req
|
||||
.headers()
|
||||
.get(http::header::ACCESS_CONTROL_REQUEST_METHOD)
|
||||
.and_then(header_value_try_into_method)
|
||||
.is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
//
|
||||
// fs.rs
|
||||
// Copyright (C) 2023 veypi <i@veypi.com>
|
||||
// 2023-10-02 22:51
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
//
|
||||
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use actix_web::web;
|
||||
|
||||
use dav_server::{
|
||||
actix::{DavRequest, DavResponse},
|
||||
body::Body,
|
||||
fakels::FakeLs,
|
||||
localfs::LocalFs,
|
||||
DavConfig, DavHandler,
|
||||
};
|
||||
|
||||
use http::Response;
|
||||
use http_auth_basic::Credentials;
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::{
|
||||
models::{self, UserPlugin},
|
||||
AppState, Error, Result,
|
||||
};
|
||||
|
||||
pub fn client() -> DavHandler {
|
||||
DavHandler::builder()
|
||||
.locksystem(FakeLs::new())
|
||||
.strip_prefix("/fs/u/")
|
||||
.build_handler()
|
||||
}
|
||||
pub async fn dav_handler(
|
||||
req: DavRequest,
|
||||
davhandler: web::Data<DavHandler>,
|
||||
stat: web::Data<AppState>,
|
||||
) -> DavResponse {
|
||||
let root = stat.fs_root.clone();
|
||||
match handle_file(&req, stat).await {
|
||||
Ok(p) => {
|
||||
let p = Path::new(&root).join(p);
|
||||
if !p.exists() {
|
||||
match fs::create_dir_all(p.clone()) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
warn!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("mount {}", p.to_str().unwrap());
|
||||
let config = DavConfig::new().filesystem(LocalFs::new(p, false, false, true));
|
||||
davhandler.handle_with(config, req.request).await.into()
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("handle file failed: {}", e);
|
||||
Response::builder()
|
||||
.status(401)
|
||||
.header("WWW-Authenticate", "Basic realm=\"file\"")
|
||||
.body(Body::from("please auth".to_string()))
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_file(req: &DavRequest, stat: web::Data<AppState>) -> Result<String> {
|
||||
let p = req.request.uri();
|
||||
let headers = req.request.headers();
|
||||
let m = req.request.method();
|
||||
info!("access {} to {}", m, p);
|
||||
let authorization = headers.get("authorization");
|
||||
match authorization {
|
||||
Some(au) => {
|
||||
if let Some((auth_type, encoded_credentials)) =
|
||||
au.to_str().unwrap_or("").split_once(' ')
|
||||
{
|
||||
if encoded_credentials.contains(' ') {
|
||||
// Invalid authorization token received
|
||||
return Err(Error::InvalidToken);
|
||||
}
|
||||
match auth_type.to_lowercase().as_str() {
|
||||
"basic" => {
|
||||
let credentials = Credentials::decode(encoded_credentials.to_string())?;
|
||||
info!("{}|{}", credentials.user_id, credentials.password);
|
||||
match models::user::Entity::find()
|
||||
.filter(models::user::Column::Username.eq(credentials.user_id))
|
||||
.one(stat.db())
|
||||
.await?
|
||||
{
|
||||
Some(u) => {
|
||||
u.check_pass(&credentials.password)?;
|
||||
return Ok(format!("user/{}/", u.id));
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
"bearer" => {
|
||||
let t = models::Token::from(encoded_credentials, &stat.key)?;
|
||||
if t.is_valid() {
|
||||
return Ok(format!("user/{}/", t.id));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return Err(Error::InvalidScheme(auth_type.to_string()));
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
Err(Error::NotAuthed)
|
||||
}
|
@ -1,178 +0,0 @@
|
||||
//
|
||||
// fs.rs
|
||||
// Copyright (C) 2023 veypi <i@veypi.com>
|
||||
// 2023-10-02 22:51
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
//
|
||||
|
||||
use std::{fs, path::Path};
|
||||
|
||||
use actix_web::web;
|
||||
|
||||
use dav_server::{
|
||||
actix::{DavRequest, DavResponse},
|
||||
body::Body,
|
||||
fakels::FakeLs,
|
||||
localfs::LocalFs,
|
||||
DavConfig, DavHandler,
|
||||
};
|
||||
|
||||
use http::{header, Method, Response};
|
||||
use http_auth_basic::Credentials;
|
||||
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
|
||||
use tracing::{info, warn};
|
||||
|
||||
use crate::{
|
||||
models::{self, UserPlugin},
|
||||
AppState, Error, Result,
|
||||
};
|
||||
|
||||
pub fn core() -> DavHandler {
|
||||
DavHandler::builder()
|
||||
.locksystem(FakeLs::new())
|
||||
.strip_prefix("/file/")
|
||||
.build_handler()
|
||||
}
|
||||
/// Try to parse header value as HTTP method.
|
||||
fn header_value_try_into_method(hdr: &header::HeaderValue) -> Option<Method> {
|
||||
hdr.to_str()
|
||||
.ok()
|
||||
.and_then(|meth| Method::try_from(meth).ok())
|
||||
}
|
||||
|
||||
fn is_request_preflight(req: &DavRequest) -> bool {
|
||||
// check request method is OPTIONS
|
||||
if req.request.method() != Method::OPTIONS {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check follow-up request method is present and valid
|
||||
if req
|
||||
.request
|
||||
.headers()
|
||||
.get(header::ACCESS_CONTROL_REQUEST_METHOD)
|
||||
.and_then(header_value_try_into_method)
|
||||
.is_none()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub async fn dav_handler(
|
||||
req: DavRequest,
|
||||
davhandler: web::Data<DavHandler>,
|
||||
stat: web::Data<AppState>,
|
||||
) -> DavResponse {
|
||||
let root = stat.fs_root.clone();
|
||||
match handle_file(&req, stat).await {
|
||||
Ok(p) => {
|
||||
let p = Path::new(&root).join(p);
|
||||
if !p.exists() {
|
||||
match fs::create_dir_all(p.clone()) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
warn!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
info!("mount {}", p.to_str().unwrap());
|
||||
let config = DavConfig::new().filesystem(LocalFs::new(p, false, false, true));
|
||||
davhandler.handle_with(config, req.request).await.into()
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("handle file failed: {}", e);
|
||||
if is_request_preflight(&req) {
|
||||
let origin = match req.request.headers().get("Origin") {
|
||||
Some(o) => o.to_str().unwrap(),
|
||||
None => "",
|
||||
};
|
||||
let allowed_headers =
|
||||
match req.request.headers().get("Access-Control-Request-Headers") {
|
||||
Some(o) => o.to_str().unwrap(),
|
||||
None => "",
|
||||
};
|
||||
let allowed_method =
|
||||
match req.request.headers().get("Access-Control-Request-Method") {
|
||||
Some(o) => o.to_str().unwrap(),
|
||||
None => "",
|
||||
};
|
||||
Response::builder()
|
||||
.status(200)
|
||||
.header("WWW-Authenticate", "Basic realm=\"file\"")
|
||||
.header("Access-Control-Allow-Origin", origin)
|
||||
.header("Access-Control-Allow-Credentials", "true")
|
||||
.header("Access-Control-Allow-Headers", allowed_headers)
|
||||
.header("Access-Control-Allow-Methods", allowed_method)
|
||||
.header(
|
||||
"Access-Control-Expose-Headers",
|
||||
"access-control-allow-origin, content-type",
|
||||
)
|
||||
.body(Body::from("please auth".to_string()))
|
||||
.unwrap()
|
||||
.into()
|
||||
} else {
|
||||
Response::builder()
|
||||
.status(401)
|
||||
.header("WWW-Authenticate", "Basic realm=\"file\"")
|
||||
.body(Body::from("please auth".to_string()))
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_file(req: &DavRequest, stat: web::Data<AppState>) -> Result<String> {
|
||||
let p = req.request.uri();
|
||||
let headers = req.request.headers();
|
||||
let m = req.request.method();
|
||||
// handle_authorization(req.request.headers());
|
||||
info!("access {} to {}", m, p);
|
||||
let auth_token = headers.get("auth_token");
|
||||
let authorization = headers.get("authorization");
|
||||
let app_id = match headers.get("app_id") {
|
||||
Some(i) => i.to_str().unwrap_or(""),
|
||||
None => "",
|
||||
};
|
||||
match auth_token {
|
||||
Some(t) => match models::Token::from(t.to_str().unwrap_or(""), &stat.key) {
|
||||
Ok(t) => {
|
||||
if t.is_valid() {
|
||||
if app_id != "" {
|
||||
// 只有秘钥才能访问app数据
|
||||
if t.can_read("app", app_id) {
|
||||
return Ok(format!("app/{}/", app_id));
|
||||
}
|
||||
} else {
|
||||
return Ok(format!("user/{}/", t.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {}
|
||||
},
|
||||
None => {}
|
||||
}
|
||||
match authorization {
|
||||
Some(au) => {
|
||||
let credentials =
|
||||
Credentials::from_header(au.to_str().unwrap_or("").to_string()).unwrap();
|
||||
info!("{}|{}", credentials.user_id, credentials.password);
|
||||
match models::user::Entity::find()
|
||||
.filter(models::user::Column::Username.eq(credentials.user_id))
|
||||
.one(stat.db())
|
||||
.await?
|
||||
{
|
||||
Some(u) => {
|
||||
u.check_pass(&credentials.password)?;
|
||||
return Ok(format!("user/{}/", u.id));
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
Err(Error::NotAuthed)
|
||||
}
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue