mirror of
https://github.com/lifegpc/pixiv_downloader.git
synced 2026-07-08 01:32:41 +08:00
Impl /auth/user/update
This commit is contained in:
@@ -12,6 +12,11 @@
|
||||
* 方法: `GET` 或 `POST`
|
||||
* RESTful: `PUT /api/auth/user` 或 `PUT /auth/user`
|
||||
* 鉴权: 一般需要(如服务器状态内的`has_root_user`为`false`则无需鉴权)
|
||||
## 更新用户
|
||||
* 路径: `/api/auth/user/update`、 `/auth/user/update`
|
||||
* 方法: `GET` 或 `POST`
|
||||
* RESTful: `PATCH /api/auth/user` 或 `PATCH /auth/user`
|
||||
* 鉴权: 需要
|
||||
## 获取Token
|
||||
* 路径: `/api/auth/token/add`、 `/auth/token/add`
|
||||
* 方法: `GET` 或 `POST`
|
||||
|
||||
@@ -404,6 +404,36 @@ impl PixivDownloaderSqlite {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
async fn _update_user(
|
||||
&self,
|
||||
id: u64,
|
||||
name: &str,
|
||||
username: &str,
|
||||
password: &[u8],
|
||||
is_admin: bool,
|
||||
) -> Result<usize, SqliteError> {
|
||||
let con = self.db.lock().await;
|
||||
let has_username = con
|
||||
.query_row(
|
||||
"SELECT * FROM users WHERE username = ?;",
|
||||
[username],
|
||||
|row| {
|
||||
let uid: u64 = row.get(0)?;
|
||||
Ok(uid != id)
|
||||
},
|
||||
)
|
||||
.optional()?
|
||||
.unwrap_or(false);
|
||||
if has_username {
|
||||
return Err(SqliteError::UserNameAlreadyExists);
|
||||
}
|
||||
Ok(con.execute(
|
||||
"UPDATE users SET name = ?, username = ?, password = ?, is_admin = ? WHERE id = ?;",
|
||||
(name, username, password, is_admin, id),
|
||||
)?)
|
||||
}
|
||||
|
||||
fn _write_version<'a>(&self, ts: &Transaction<'a>) -> Result<(), SqliteError> {
|
||||
let mut stmt = ts.prepare(
|
||||
"INSERT OR REPLACE INTO version (id, v1, v2, v3, v4) VALUES ('main', ?, ?, ?, ?);",
|
||||
@@ -549,4 +579,18 @@ impl PixivDownloaderDb for PixivDownloaderSqlite {
|
||||
._set_user(id, name, username, password, is_admin)
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
async fn update_user(
|
||||
&self,
|
||||
id: u64,
|
||||
name: &str,
|
||||
username: &str,
|
||||
password: &[u8],
|
||||
is_admin: bool,
|
||||
) -> Result<User, PixivDownloaderDbError> {
|
||||
self._update_user(id, name, username, password, is_admin)
|
||||
.await?;
|
||||
Ok(self.get_user(id).await?.expect("User not found:"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,4 +93,22 @@ pub trait PixivDownloaderDb {
|
||||
password: &[u8],
|
||||
is_admin: bool,
|
||||
) -> Result<User, PixivDownloaderDbError>;
|
||||
#[cfg(feature = "server")]
|
||||
/// Update a user's information
|
||||
/// * `id`: The user's ID
|
||||
/// * `name`: The user's name
|
||||
/// * `username`: The user's username
|
||||
/// * `password`: The user's hashed password
|
||||
/// * `is_admin`: Whether the user is an admin
|
||||
/// # Note
|
||||
/// If the user does not exist, the operation must failed.
|
||||
/// If the user's `username` is taked by another user, the operation must failed.
|
||||
async fn update_user(
|
||||
&self,
|
||||
id: u64,
|
||||
name: &str,
|
||||
username: &str,
|
||||
password: &[u8],
|
||||
is_admin: bool,
|
||||
) -> Result<User, PixivDownloaderDbError>;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use super::{PASSWORD_ITER, PASSWORD_SALT};
|
||||
use crate::ext::json::ToJson2;
|
||||
use crate::ext::try_err::{TryErr, TryErr3};
|
||||
use crate::gettext;
|
||||
use bytes::BytesMut;
|
||||
use openssl::{hash::MessageDigest, pkcs5::pbkdf2_hmac};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -10,6 +11,8 @@ use openssl::{hash::MessageDigest, pkcs5::pbkdf2_hmac};
|
||||
pub enum AuthUserAction {
|
||||
/// Add a new user.
|
||||
Add,
|
||||
/// Update a existed user.
|
||||
Update,
|
||||
}
|
||||
|
||||
pub struct AuthUserContext {
|
||||
@@ -150,6 +153,99 @@ impl AuthUserContext {
|
||||
None => return Err((5, gettext("No RSA key found.")).into()),
|
||||
}
|
||||
}
|
||||
AuthUserAction::Update => {
|
||||
if root_user.is_some() {
|
||||
if !user.as_ref().expect("User not found:").is_admin {
|
||||
return Err((9, gettext("Admin privileges required.")).into());
|
||||
}
|
||||
}
|
||||
let id = params.get_u64("id").try_err3(
|
||||
13,
|
||||
&gettext("Failed to parse <opt>:").replace("<opt>", "user id"),
|
||||
)?;
|
||||
let username = params.get("username");
|
||||
let mut user = if let Some(id) = id {
|
||||
self.ctx.db.get_user(id).await
|
||||
} else if let Some(username) = username {
|
||||
self.ctx.db.get_user_by_username(username).await
|
||||
} else {
|
||||
return Err((14, gettext("No user id or username specified.")).into());
|
||||
}
|
||||
.try_err3(-1001, gettext("Failed to operate the database:"))?
|
||||
.ok_or((15, gettext("User not found.")))?;
|
||||
if let Some(username) = username {
|
||||
if username != user.username {
|
||||
user.username = username.to_owned();
|
||||
}
|
||||
}
|
||||
if let Some(name) = params.get("name") {
|
||||
if name != user.name {
|
||||
user.name = name.to_owned();
|
||||
}
|
||||
}
|
||||
if let Some(password) = params.get("password") {
|
||||
let password = base64::decode(password)
|
||||
.try_err3(4, gettext("Failed to decode password with base64:"))?;
|
||||
let rsa_key = self.ctx.rsa_key.lock().await;
|
||||
match *rsa_key {
|
||||
Some(ref key) => {
|
||||
if key.is_too_old() {
|
||||
return Err((
|
||||
6,
|
||||
gettext(
|
||||
"RSA key is too old. A new key should be generated.",
|
||||
),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
let password = key
|
||||
.decrypt(&password)
|
||||
.try_err3(7, gettext("Failed to decrypt password with RSA:"))?;
|
||||
let mut hashed_password = [0; 64];
|
||||
pbkdf2_hmac(
|
||||
&password,
|
||||
&PASSWORD_SALT,
|
||||
PASSWORD_ITER,
|
||||
MessageDigest::sha512(),
|
||||
&mut hashed_password,
|
||||
)
|
||||
.try_err3(11, gettext("Failed to hash password:"))?;
|
||||
let pw: &[u8] = &user.password;
|
||||
if &hashed_password != pw {
|
||||
let pw: &[u8] = &hashed_password;
|
||||
user.password = BytesMut::from(pw);
|
||||
}
|
||||
}
|
||||
None => return Err((5, gettext("No RSA key found.")).into()),
|
||||
}
|
||||
}
|
||||
if let Some(is_admin) = params.get_bool("is_admin").try_err3(
|
||||
16,
|
||||
&gettext("Failed to parse <opt>:").replace("<opt>", "is_admin"),
|
||||
)? {
|
||||
if user.id == 0 && !is_admin {
|
||||
return Err((
|
||||
17,
|
||||
gettext("Cannot change admin privileges of root user."),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
user.is_admin = is_admin;
|
||||
}
|
||||
let user = self
|
||||
.ctx
|
||||
.db
|
||||
.update_user(
|
||||
user.id,
|
||||
&user.name,
|
||||
&user.username,
|
||||
&user.password,
|
||||
user.is_admin,
|
||||
)
|
||||
.await
|
||||
.try_err3(-1001, gettext("Failed to operate the database:"))?;
|
||||
Ok(user.to_json2())
|
||||
}
|
||||
},
|
||||
None => {
|
||||
panic!("No action specified for AuthUserContext.");
|
||||
@@ -200,7 +296,7 @@ pub struct AuthUserRoute {
|
||||
impl AuthUserRoute {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
regex: Regex::new(r"^(/+api)?/+auth/+user(/+add)?$").unwrap(),
|
||||
regex: Regex::new(r"^(/+api)?/+auth/+user(/+(add|update))?$").unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,12 +325,16 @@ impl MatchRoute<Body, Body> for AuthUserRoute {
|
||||
let m = m.as_str().trim_start_matches("/");
|
||||
match m {
|
||||
"add" => Some(AuthUserAction::Add),
|
||||
"update" => Some(AuthUserAction::Update),
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if req.method() == Method::PUT {
|
||||
let m = req.method();
|
||||
if m == Method::PUT {
|
||||
Some(AuthUserAction::Add)
|
||||
} else if m == Method::PATCH {
|
||||
Some(AuthUserAction::Update)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user