mirror of
https://github.com/lifegpc/pixiv_downloader.git
synced 2026-06-29 23:27:13 +08:00
Add /auth/pubkey
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -1396,6 +1396,7 @@ dependencies = [
|
||||
"link-cplusplus",
|
||||
"modular-bitfield",
|
||||
"multipart",
|
||||
"openssl",
|
||||
"parse-size",
|
||||
"proc_macros",
|
||||
"regex",
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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
101
src/server/auth/pubkey.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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! {})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
|
||||
@@ -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())))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user