diff --git a/src/db/sqlite/db.rs b/src/db/sqlite/db.rs index ee2af44..18f0ab8 100644 --- a/src/db/sqlite/db.rs +++ b/src/db/sqlite/db.rs @@ -233,6 +233,19 @@ impl PixivDownloaderSqlite { Ok(af > 0) } + #[cfg(feature = "server")] + fn _extend_token( + tx: &Transaction, + id: u64, + expired_at: &DateTime, + ) -> Result<(), SqliteError> { + tx.execute( + "UPDATE token SET expired_at = ? WHERE id = ?;", + (expired_at, id), + )?; + Ok(()) + } + /// Get all exists tables async fn _get_exists_table(&self) -> Result, SqliteError> { let con = self.db.lock().await; @@ -618,6 +631,19 @@ impl PixivDownloaderDb for PixivDownloaderSqlite { Ok(re) } + #[cfg(feature = "server")] + async fn extend_token( + &self, + id: u64, + expired_at: &DateTime, + ) -> Result<(), PixivDownloaderDbError> { + let mut db = self.db.lock().await; + let mut tx = db.transaction()?; + Self::_extend_token(&mut tx, id, expired_at)?; + tx.commit()?; + Ok(()) + } + #[cfg(feature = "server")] async fn get_token(&self, id: u64) -> Result, PixivDownloaderDbError> { Ok(self.get_token(id).await?) diff --git a/src/db/traits.rs b/src/db/traits.rs index 3fe41a6..30d62e7 100644 --- a/src/db/traits.rs +++ b/src/db/traits.rs @@ -65,6 +65,15 @@ pub trait PixivDownloaderDb { /// All tokens of the user will be deleted. async fn delete_user(&self, id: u64) -> Result; #[cfg(feature = "server")] + /// Extend a token's expiration time + /// * `id` - The token ID + /// * `expired_at` - The token's new expiration time + async fn extend_token( + &self, + id: u64, + expired_at: &DateTime, + ) -> Result<(), PixivDownloaderDbError>; + #[cfg(feature = "server")] /// Get token by ID /// * `id` - The token ID async fn get_token(&self, id: u64) -> Result, PixivDownloaderDbError>; diff --git a/src/server/auth/token.rs b/src/server/auth/token.rs index b9232c6..644e533 100644 --- a/src/server/auth/token.rs +++ b/src/server/auth/token.rs @@ -12,6 +12,8 @@ pub enum AuthTokenAction { Add, /// Delete a token Delete, + /// Extend a token's expire time + Extend, } pub struct AuthTokenContext { @@ -121,6 +123,22 @@ impl AuthTokenContext { } Ok(data) } + AuthTokenAction::Extend => { + let token = self + .ctx + .verify_token2(&req, ¶ms) + .await + .try_err3(-403, gettext("Failed to verify the token:"))?; + let expired_at = chrono::Utc::now() + chrono::Duration::days(30); + self.ctx + .db + .extend_token(token.id, &expired_at) + .await + .try_err3(-1001, gettext("Failed to operate the database:"))?; + Ok( + json::object! { "id": token.id, "user_id": token.user_id, "created_at": token.created_at.timestamp(), "expired_at": expired_at.timestamp() }, + ) + } }, None => { panic!("No action specified for AuthTokenContext."); @@ -191,7 +209,7 @@ pub struct AuthTokenRoute { impl AuthTokenRoute { pub fn new() -> Self { Self { - regex: Regex::new(r"^(/+api)?/+auth/+token(/+(add|delete))?$").unwrap(), + regex: Regex::new(r"^(/+api)?/+auth/+token(/+(add|delete|extend))?$").unwrap(), } } } @@ -221,6 +239,7 @@ impl MatchRoute for AuthTokenRoute { match m { "add" => Some(AuthTokenAction::Add), "delete" => Some(AuthTokenAction::Delete), + "extend" => Some(AuthTokenAction::Extend), _ => return None, } } diff --git a/src/server/context.rs b/src/server/context.rs index e50dd77..d8addf6 100644 --- a/src/server/context.rs +++ b/src/server/context.rs @@ -2,7 +2,7 @@ use super::auth::RSAKey; use super::cors::CorsContext; use super::params::RequestParams; use super::result::JSONResult; -use crate::db::{open_and_init_database, PixivDownloaderDb, User}; +use crate::db::{open_and_init_database, PixivDownloaderDb, Token, User}; use crate::error::PixivDownloaderError; use crate::ext::json::ToJson2; use crate::get_helper; @@ -52,11 +52,11 @@ impl ServerContext { Ok(builder.body(re.to_json2())?) } - pub async fn verify_token( + pub async fn verify_token2( &self, req: &Request, params: &RequestParams, - ) -> Result { + ) -> Result { let mut token_id = None; match req.headers().get("X-TOKEN-ID") { Some(v) => { @@ -121,6 +121,15 @@ impl ServerContext { if sign != sha { return Err(PixivDownloaderError::from(gettext("Sign not match."))); } + Ok(token) + } + + pub async fn verify_token( + &self, + req: &Request, + params: &RequestParams, + ) -> Result { + let token = self.verify_token2(req, params).await?; Ok(self .db .get_user(token.user_id) diff --git a/src/server/unittest/auth.rs b/src/server/unittest/auth.rs index d004cb3..bb090d7 100644 --- a/src/server/unittest/auth.rs +++ b/src/server/unittest/auth.rs @@ -668,5 +668,12 @@ pub async fn test(ctx: &UnitTestContext) -> Result<[(u64, Vec); 2], PixivDow assert_eq!(result2.as_bool(), Some(true)); let result2 = JSONResult::from_json(&result[format!("{}", token4_id)])?.unwrap(); assert_eq!(result2.as_bool(), Some(true)); + let re = ctx + .request_json2_sign("/auth/token/extend", &json::object! {}, &token2, token2_id) + .await? + .unwrap(); + let result = JSONResult::from_json(re)?.unwrap(); + assert_eq!(result["user_id"].as_u64(), Some(1)); + assert_eq!(result["id"].as_u64(), Some(token2_id)); Ok([(token_id, token), (token2_id, token2)]) }