Add /auth/pubkey

This commit is contained in:
2022-09-20 03:59:32 +00:00
committed by GitHub
parent c7ccf7ebe2
commit 5f2cc3a08a
12 changed files with 180 additions and 4 deletions

1
Cargo.lock generated
View File

@@ -1396,6 +1396,7 @@ dependencies = [
"link-cplusplus",
"modular-bitfield",
"multipart",
"openssl",
"parse-size",
"proc_macros",
"regex",

View File

@@ -31,6 +31,7 @@ json = "0.12"
lazy_static = "1.4"
modular-bitfield = "0.11"
multipart = { features = ["server"], git = 'https://github.com/lifegpc/multipart', optional = true, default-features = false }
openssl = { version = "0.10", optional = true }
parse-size = "1"
proc_macros = { path = "proc_macros" }
regex = "1"
@@ -53,7 +54,7 @@ db = ["anyhow", "async-trait", "bytes"]
db_all = ["db", "db_sqlite"]
db_sqlite = ["rusqlite"]
exif = ["bindgen", "c_fixed_string", "cmake", "link-cplusplus", "utf16string"]
server = ["async-trait", "db", "hyper", "multipart"]
server = ["async-trait", "db", "hyper", "multipart", "openssl"]
ugoira = ["avdict", "bindgen", "cmake", "link-cplusplus"]
[profile.release-with-debug]

View File

@@ -3,6 +3,10 @@
* 路径: `/api/auth/status``/auth/status``/api/auth``/auth`
* 方法: `GET``POST`
* 鉴权: 无需
## 获取服务器公钥
* 路径: `/api/auth/pubkey``/auth/pubkey`
* 方法: `GET``POST`
* 鉴权: 无需
## 新增用户
* 路径: `/api/auth/user/add``/auth/user/add`
* 方法: `GET``POST`

View File

@@ -1,5 +1,7 @@
pub mod pubkey;
pub mod status;
pub mod user;
pub use pubkey::{AuthPubkeyContext, AuthPubkeyRoute, RSAKey};
pub use status::{AuthStatusContext, AuthStatusRoute};
pub use user::{AuthUserContext, AuthUserRoute};

101
src/server/auth/pubkey.rs Normal file
View File

@@ -0,0 +1,101 @@
use super::super::preclude::*;
use crate::ext::{json::ToJson2, try_err::TryErr};
use crate::gettext;
use chrono::{DateTime, Utc};
use openssl::{pkey::Private, rsa::Rsa};
pub struct RSAKey {
pub key: Rsa<Private>,
pub generated_time: DateTime<Utc>,
}
impl RSAKey {
pub fn new() -> Result<Self, openssl::error::ErrorStack> {
Ok(Self {
key: Rsa::generate(4096)?,
generated_time: Utc::now(),
})
}
pub fn is_too_old(&self) -> bool {
self.generated_time < Utc::now() - chrono::Duration::hours(1)
}
}
pub struct AuthPubkeyContext {
ctx: Arc<ServerContext>,
}
impl AuthPubkeyContext {
pub fn new(ctx: Arc<ServerContext>) -> Self {
Self { ctx }
}
async fn handle(&self) -> JSONResult {
let mut rsa_key = self.ctx.rsa_key.lock().await;
match &*rsa_key {
Some(key) => {
if key.is_too_old() {
*rsa_key =
Some(RSAKey::new().try_err((1, gettext("Failed to generate RSA key.")))?);
}
}
None => {
*rsa_key =
Some(RSAKey::new().try_err((1, gettext("Failed to generate RSA key.")))?);
}
}
let rsa_key = rsa_key.as_ref().unwrap();
let key = rsa_key.key.public_key_to_pem().try_err((2, gettext("Failed to serializes the public key into a PEM-encoded SubjectPublicKeyInfo structure")))?;
Ok(json::object! {
"key": String::from_utf8(key).try_err((3, gettext("Failed to encode pem with UTF-8.")))?,
"generated_time": rsa_key.generated_time.timestamp(),
})
}
}
#[async_trait]
impl ResponseJsonFor<Body> for AuthPubkeyContext {
async fn response_json(
&self,
req: Request<Body>,
) -> Result<Response<JsonValue>, PixivDownloaderError> {
filter_http_methods!(
req,
json::object! {},
true,
self.ctx,
allow_headers = [CONTENT_TYPE],
GET,
OPTIONS,
POST,
);
Ok(builder.body(self.handle().await.to_json2())?)
}
}
pub struct AuthPubkeyRoute {
regex: Regex,
}
impl AuthPubkeyRoute {
pub fn new() -> Self {
Self {
regex: Regex::new(r"^(/+api)?/+auth/+pubkey(/.*)?$").unwrap(),
}
}
}
impl MatchRoute<Body, Body> for AuthPubkeyRoute {
fn match_route(
&self,
ctx: &Arc<ServerContext>,
req: &http::Request<Body>,
) -> Option<Box<ResponseForType>> {
if self.regex.is_match(req.uri().path()) {
Some(Box::new(AuthPubkeyContext::new(Arc::clone(ctx))))
} else {
None
}
}
}

View File

@@ -1,5 +1,7 @@
use super::super::preclude::*;
use crate::ext::json::ToJson2;
use crate::ext::try_err::TryErr;
use crate::gettext;
#[derive(Clone, Debug)]
/// Action to perform on a user.
@@ -30,12 +32,21 @@ impl AuthUserContext {
// # TODO auth
}
match &self.action {
Some(act) => {}
Some(act) => match act {
AuthUserAction::Add => {
let name = params
.get("name")
.try_err((1, gettext("No user's name specified.")))?;
let username = params
.get("username")
.try_err((2, gettext("No username specified.")))?;
Ok(json::object! {})
}
},
None => {
panic!("No action specified for AuthUserContext.");
}
}
Ok(json::object! {})
}
}

View File

@@ -1,10 +1,13 @@
use super::auth::RSAKey;
use super::cors::CorsContext;
use crate::db::{open_and_init_database, PixivDownloaderDb};
use crate::gettext;
use futures_util::lock::Mutex;
pub struct ServerContext {
pub cors: CorsContext,
pub db: Box<dyn PixivDownloaderDb + Send + Sync>,
pub rsa_key: Mutex<Option<RSAKey>>,
}
impl ServerContext {
@@ -15,6 +18,7 @@ impl ServerContext {
Ok(db) => db,
Err(e) => panic!("{} {}", gettext("Failed to open database:"), e),
},
rsa_key: Mutex::new(None),
}
}
}

View File

@@ -8,11 +8,16 @@ use multipart::server::{Multipart, ReadEntryResult};
use std::collections::HashMap;
use std::io::Read;
/// Parameters from request.
pub struct RequestParams {
/// Parameters.
pub params: HashMap<String, Vec<String>>,
}
#[allow(dead_code)]
impl RequestParams {
/// Get parameter.
/// * `name` - Parameter name.
pub fn get<S: AsRef<str> + ?Sized>(&self, name: &S) -> Option<&str> {
match self.params.get(name.as_ref()) {
Some(v) => {
@@ -25,6 +30,21 @@ impl RequestParams {
None => None,
}
}
/// Get all parameters with same name.
/// * `name` - Parameter name.
pub fn get_all<S: AsRef<str> + ?Sized>(&self, name: &S) -> Option<&Vec<String>> {
match self.params.get(name.as_ref()) {
Some(v) => {
if v.len() > 0 {
Some(v)
} else {
None
}
}
None => None,
}
}
}
#[async_trait]

View File

@@ -1,4 +1,5 @@
pub use super::context::ServerContext;
pub use super::params::RequestParams;
pub use super::result::JSONResult;
pub use super::route::ResponseForType;
pub use super::traits::{GetRequestParams, MatchRoute, ResponseFor, ResponseJsonFor};

View File

@@ -23,6 +23,19 @@ impl From<(i32, String)> for JSONError {
}
}
impl<S> From<(i32, &S)> for JSONError
where
S: AsRef<str> + ?Sized,
{
fn from((code, msg): (i32, &S)) -> Self {
Self {
code,
msg: msg.as_ref().to_owned(),
debug_msg: None,
}
}
}
impl From<(i32, String, Option<JsonValue>)> for JSONError {
fn from((code, msg, debug_msg): (i32, String, Option<JsonValue>)) -> Self {
Self {
@@ -33,6 +46,19 @@ impl From<(i32, String, Option<JsonValue>)> for JSONError {
}
}
impl<S> From<(i32, &S, Option<JsonValue>)> for JSONError
where
S: AsRef<str> + ?Sized,
{
fn from((code, msg, debug_msg): (i32, &S, Option<JsonValue>)) -> Self {
Self {
code,
msg: msg.as_ref().to_owned(),
debug_msg,
}
}
}
impl From<crate::db::PixivDownloaderDbError> for JSONError {
fn from(e: crate::db::PixivDownloaderDbError) -> Self {
Self {

View File

@@ -20,6 +20,7 @@ impl ServerRoutes {
routes.push(Box::new(VersionRoute::new()));
routes.push(Box::new(AuthStatusRoute::new()));
routes.push(Box::new(AuthUserRoute::new()));
routes.push(Box::new(AuthPubkeyRoute::new()));
Self { routes }
}

View File

@@ -43,7 +43,11 @@ where
{
async fn response(&self, req: Request<T>) -> Result<Response<Body>, PixivDownloaderError> {
let re = self.response_json(req).await?;
let (parts, body) = re.into_parts();
let (mut parts, body) = re.into_parts();
parts.headers.insert(
hyper::header::CONTENT_TYPE,
"application/json; charset=utf-8".parse()?,
);
Ok(Response::from_parts(parts, Body::from(body.to_string())))
}
}