mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-20 17:02:53 -04:00
oncall: fix paging on users not being logged in (#1258)
This commit is contained in:
@@ -676,7 +676,6 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
DeleteWeightError,
|
||||
DeleteWeightPathInfo,
|
||||
DeleteWeightRequest,
|
||||
DismissFinishedSessionJobsError,
|
||||
DismissFinishedSessionJobsSuccessResponse,
|
||||
EditBetaKeyDistributedFlagError,
|
||||
EditBetaKeyDistributedFlagPathInfo,
|
||||
|
||||
+11
-1
@@ -1,9 +1,10 @@
|
||||
use actix_http::StatusCode;
|
||||
use actix_web::{HttpResponse, HttpResponseBuilder, ResponseError};
|
||||
use log::error;
|
||||
use log::{error, warn};
|
||||
use mysql_queries::errors::mysql_error::{MysqlCrateErrorSubtype, MysqlError};
|
||||
use std::error::Error;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use crate::http_server::web_utils::user_session::require_user_session::RequireUserSessionError;
|
||||
|
||||
#[derive(Debug, utoipa::ToSchema)]
|
||||
pub enum CommonWebError {
|
||||
@@ -99,6 +100,15 @@ impl <T: MysqlCrateErrorSubtype> From<MysqlError<T>> for CommonWebError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RequireUserSessionError> for CommonWebError {
|
||||
fn from(value: RequireUserSessionError) -> Self {
|
||||
match value {
|
||||
RequireUserSessionError::NotAuthorized => Self::NotAuthorized,
|
||||
RequireUserSessionError::ServerError => Self::ServerError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ use crate::http_server::requests::request_headers::has_debug_header::has_debug_h
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::RequireUserSessionError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended_using_connection::require_user_session_extended_using_connection;
|
||||
use crate::state::server_state::ServerState;
|
||||
use crate::util::cleaners::empty_media_file_token_to_null::empty_media_file_token_to_null;
|
||||
|
||||
@@ -150,7 +150,7 @@ pub async fn enqueue_face_fusion_workflow_handler(
|
||||
let maybe_avt_token = server_state.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
let user_session = require_user_session_extended_using_connection(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&mut mysql_connection)
|
||||
|
||||
+2
-2
@@ -29,7 +29,7 @@ use crate::http_server::requests::request_headers::has_debug_header::has_debug_h
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::RequireUserSessionError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended_using_connection::require_user_session_extended_using_connection;
|
||||
use crate::state::server_state::ServerState;
|
||||
use crate::util::cleaners::empty_media_file_token_to_null::empty_media_file_token_to_null;
|
||||
|
||||
@@ -158,7 +158,7 @@ pub async fn enqueue_live_portrait_workflow_handler(
|
||||
let maybe_avt_token = server_state.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
let user_session = require_user_session_extended_using_connection(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&mut mysql_connection)
|
||||
|
||||
+16
-66
@@ -1,17 +1,14 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{web, HttpMessage, HttpRequest, HttpResponse};
|
||||
use log::{error, warn};
|
||||
use actix_web::{web, HttpMessage, HttpRequest};
|
||||
use actix_web::web::Json;
|
||||
use log::error;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use mysql_queries::queries::generic_inference::web::dismiss_finished_jobs_for_user::dismiss_finished_jobs_for_user;
|
||||
use mysql_queries::queries::generic_inference::web::mark_generic_inference_job_cancelled_by_user::mark_generic_inference_job_cancelled_by_user;
|
||||
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
@@ -19,40 +16,6 @@ pub struct DismissFinishedSessionJobsSuccessResponse {
|
||||
pub success: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, ToSchema)]
|
||||
pub enum DismissFinishedSessionJobsError {
|
||||
ServerError,
|
||||
NotFound,
|
||||
NotAuthorized,
|
||||
}
|
||||
|
||||
impl ResponseError for DismissFinishedSessionJobsError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
DismissFinishedSessionJobsError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
DismissFinishedSessionJobsError::NotFound => StatusCode::NOT_FOUND,
|
||||
DismissFinishedSessionJobsError::NotAuthorized => StatusCode::UNAUTHORIZED,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let error_reason = match self {
|
||||
Self::ServerError => "server error".to_string(),
|
||||
Self::NotFound => "not found".to_string(),
|
||||
Self::NotAuthorized => "unauthorized".to_string(),
|
||||
};
|
||||
|
||||
to_simple_json_error(&error_reason, self.status_code())
|
||||
}
|
||||
}
|
||||
|
||||
// NB: Not using derive_more::Display since Clion doesn't understand it.
|
||||
impl fmt::Display for DismissFinishedSessionJobsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark all jobs that are finished (or that failed completely and will not retry) as dismissed.
|
||||
///
|
||||
/// This will prevent these jobs from being returned in the "list session jobs" endpoint.
|
||||
@@ -62,46 +25,33 @@ impl fmt::Display for DismissFinishedSessionJobsError {
|
||||
path = "/v1/jobs/session/dismiss_finished",
|
||||
responses(
|
||||
(status = 200, body = DismissFinishedSessionJobsSuccessResponse),
|
||||
(status = 500, body = DismissFinishedSessionJobsError),
|
||||
(status = 401, body = CommonWebError),
|
||||
(status = 500, body = CommonWebError),
|
||||
),
|
||||
)]
|
||||
pub async fn dismiss_finished_session_jobs_handler(
|
||||
http_request: HttpRequest,
|
||||
server_state: web::Data<Arc<ServerState>>) -> Result<HttpResponse, DismissFinishedSessionJobsError>
|
||||
server_state: web::Data<Arc<ServerState>>) -> Result<Json<DismissFinishedSessionJobsSuccessResponse>, CommonWebError>
|
||||
{
|
||||
// TODO(bt,2024-06-16): Reuse connection
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Could not acquire DB pool: {:?}", e);
|
||||
DismissFinishedSessionJobsError::ServerError
|
||||
error!("Could not acquire DB pool: {:?}", e);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session(&http_request, &server_state)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
DismissFinishedSessionJobsError::ServerError
|
||||
})?;
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut mysql_connection)
|
||||
.await?;
|
||||
|
||||
dismiss_finished_jobs_for_user(&mut mysql_connection, &user_session.user_token)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("tts job query error: {:?}", err);
|
||||
DismissFinishedSessionJobsError::ServerError
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let response = DismissFinishedSessionJobsSuccessResponse {
|
||||
Ok(Json(DismissFinishedSessionJobsSuccessResponse {
|
||||
success: true,
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&response)
|
||||
.map_err(|e| {
|
||||
error!("error returning response: {:?}", e);
|
||||
DismissFinishedSessionJobsError::ServerError
|
||||
})?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(body))
|
||||
}))
|
||||
}
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@ use crate::http_server::endpoints::media_files::upload::upload_studio_shot::extr
|
||||
use crate::http_server::endpoints::media_files::upload::upload_studio_shot::ffmpeg_frames_to_mp4::ffmpeg_frames_to_mp4;
|
||||
use crate::http_server::requests::request_headers::has_debug_header::has_debug_header;
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended_using_connection::require_user_session_extended_using_connection;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const DEFAULT_FPS : u8 = 12;
|
||||
@@ -111,7 +111,7 @@ pub async fn upload_studio_shot_media_file_handler(
|
||||
// ==================== READ SESSION ==================== //
|
||||
|
||||
// NB: We require a moderator to upload PMX files.
|
||||
let user_session = require_user_session_using_connection(&http_request, &server_state.session_checker, &mut mysql_connection)
|
||||
let user_session = require_user_session_extended_using_connection(&http_request, &server_state.session_checker, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("User session error: {:?}", e);
|
||||
|
||||
+2
-2
@@ -28,7 +28,7 @@ use crate::http_server::requests::request_headers::has_debug_header::has_debug_h
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::http_server::validations::validate_model_title::validate_model_title;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::RequireUserSessionError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended_using_connection::require_user_session_extended_using_connection;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
@@ -111,7 +111,7 @@ pub async fn enqueue_gptsovits_model_download_handler(
|
||||
let maybe_avt_token = server_state.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
let user_session = require_user_session_extended_using_connection(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&mut mysql_connection)
|
||||
|
||||
+2
-2
@@ -8,7 +8,7 @@ use std::sync::Arc;
|
||||
|
||||
use crate::http_server::common_responses::tag_info::TagInfo;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended_using_connection::require_user_session_extended_using_connection;
|
||||
use crate::state::server_state::ServerState;
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
@@ -130,7 +130,7 @@ pub async fn set_tags_for_entity_handler(
|
||||
SetTagsForEntityError::ServerError
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
let user_session = require_user_session_extended_using_connection(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&mut mysql_connection)
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ use utoipa::ToSchema;
|
||||
use artcraft_api_defs::users::change_password::{ChangePasswordRequest, ChangePasswordResponse};
|
||||
use crate::http_server::validations::validate_passwords::validate_passwords;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended_using_connection::require_user_session_extended_using_connection;
|
||||
use crate::state::server_state::ServerState;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use mysql_queries::queries::users::user::update::update_password::{update_password, UpdatePasswordArgs};
|
||||
@@ -93,7 +93,7 @@ pub async fn change_password_handler(
|
||||
ChangePasswordError::ServerError
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
let user_session = require_user_session_extended_using_connection(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&mut mysql_connection)
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ use log::warn;
|
||||
use utoipa::ToSchema;
|
||||
use artcraft_api_defs::users::edit_email::{EditEmailRequest, EditEmailResponse};
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended_using_connection::require_user_session_extended_using_connection;
|
||||
use crate::state::server_state::ServerState;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use mysql_queries::queries::users::user::update::update_email::{update_email, UpdateEmailArgs, UpdateEmailError};
|
||||
@@ -92,7 +92,7 @@ pub async fn edit_email_handler(
|
||||
EditEmailError::ServerError
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
let user_session = require_user_session_extended_using_connection(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&mut mysql_connection)
|
||||
|
||||
+2
-2
@@ -16,7 +16,7 @@ use artcraft_api_defs::users::edit_username::{EditUsernameRequest, EditUsernameR
|
||||
use crate::http_server::validations::is_reserved_username::is_reserved_username;
|
||||
use crate::http_server::validations::validate_username::validate_username;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended_using_connection::require_user_session_extended_using_connection;
|
||||
use crate::state::server_state::ServerState;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use mysql_queries::queries::users::user::update::update_username::{update_username, UpdateUsernameArgs, UpdateUsernameError};
|
||||
@@ -102,7 +102,7 @@ pub async fn edit_username_handler(
|
||||
EditUsernameError::ServerError
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
let user_session = require_user_session_extended_using_connection(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&mut mysql_connection)
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ use utoipa::ToSchema;
|
||||
|
||||
use crate::configs::supported_languages_for_models::{get_canonicalized_language_tag_for_model, get_primary_language_subtag};
|
||||
use crate::http_server::web_utils::user_session::require_user_session::RequireUserSessionError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended_using_connection::require_user_session_extended_using_connection;
|
||||
use crate::state::server_state::ServerState;
|
||||
use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::by_table::model_weights::weights_types::WeightsType;
|
||||
@@ -124,7 +124,7 @@ pub async fn update_weight_handler(
|
||||
UpdateWeightError::ServerError
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
let user_session = require_user_session_extended_using_connection(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&mut mysql_connection)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod require_moderator;
|
||||
pub mod require_user_session;
|
||||
pub mod require_user_session_extended_using_connection;
|
||||
pub mod require_user_session_using_connection;
|
||||
|
||||
+4
-22
@@ -6,6 +6,7 @@ use log::warn;
|
||||
|
||||
use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord;
|
||||
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -25,6 +26,7 @@ impl Display for RequireUserSessionError {
|
||||
|
||||
impl Error for RequireUserSessionError {}
|
||||
|
||||
#[deprecated(note = "Use require_user_session_using_connection instead, which reuses an existing connection")]
|
||||
pub async fn require_user_session(
|
||||
http_request: &HttpRequest,
|
||||
server_state: &ServerState,
|
||||
@@ -38,27 +40,7 @@ pub async fn require_user_session(
|
||||
RequireUserSessionError::ServerError
|
||||
})?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
require_user_session_using_connection(
|
||||
http_request, &server_state.session_checker, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
RequireUserSessionError::ServerError
|
||||
})?;
|
||||
|
||||
let user_session = match maybe_user_session {
|
||||
Some(session) => session,
|
||||
None => {
|
||||
warn!("not logged in");
|
||||
return Err(RequireUserSessionError::NotAuthorized);
|
||||
}
|
||||
};
|
||||
|
||||
if user_session.is_banned {
|
||||
warn!("user is banned: {:?}", user_session.user_token.as_str());
|
||||
return Err(RequireUserSessionError::NotAuthorized);
|
||||
}
|
||||
|
||||
Ok(user_session)
|
||||
}
|
||||
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
use std::error::Error;
|
||||
|
||||
use actix_web::HttpRequest;
|
||||
use log::warn;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::MySql;
|
||||
|
||||
|
||||
use crate::http_server::session::lookup::user_session_extended::UserSessionExtended;
|
||||
use crate::http_server::session::session_checker::SessionChecker;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::RequireUserSessionError;
|
||||
|
||||
pub async fn require_user_session_extended_using_connection(
|
||||
http_request: &HttpRequest,
|
||||
session_checker: &SessionChecker,
|
||||
mysql_connection: &mut PoolConnection<MySql>,
|
||||
) -> Result<UserSessionExtended, RequireUserSessionError> {
|
||||
|
||||
let maybe_user_session = session_checker
|
||||
.maybe_get_user_session_extended_from_connection(&http_request, mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
RequireUserSessionError::ServerError
|
||||
})?;
|
||||
|
||||
let user_session = match maybe_user_session {
|
||||
Some(session) => session,
|
||||
None => {
|
||||
warn!("not logged in");
|
||||
return Err(RequireUserSessionError::NotAuthorized);
|
||||
}
|
||||
};
|
||||
|
||||
if user_session.role.is_banned {
|
||||
warn!("user is banned: {:?}", user_session.user_token.as_str());
|
||||
return Err(RequireUserSessionError::NotAuthorized);
|
||||
}
|
||||
|
||||
Ok(user_session)
|
||||
}
|
||||
+4
-6
@@ -1,12 +1,10 @@
|
||||
use std::error::Error;
|
||||
|
||||
use actix_web::HttpRequest;
|
||||
use log::warn;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::MySql;
|
||||
|
||||
use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord;
|
||||
|
||||
use crate::http_server::session::lookup::user_session_extended::UserSessionExtended;
|
||||
use crate::http_server::session::session_checker::SessionChecker;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::RequireUserSessionError;
|
||||
|
||||
@@ -14,10 +12,10 @@ pub async fn require_user_session_using_connection(
|
||||
http_request: &HttpRequest,
|
||||
session_checker: &SessionChecker,
|
||||
mysql_connection: &mut PoolConnection<MySql>,
|
||||
) -> Result<UserSessionExtended, RequireUserSessionError> {
|
||||
) -> Result<SessionUserRecord, RequireUserSessionError> {
|
||||
|
||||
let maybe_user_session = session_checker
|
||||
.maybe_get_user_session_extended_from_connection(&http_request, mysql_connection)
|
||||
.maybe_get_user_session_from_connection(http_request, mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
@@ -32,7 +30,7 @@ pub async fn require_user_session_using_connection(
|
||||
}
|
||||
};
|
||||
|
||||
if user_session.role.is_banned {
|
||||
if user_session.is_banned {
|
||||
warn!("user is banned: {:?}", user_session.user_token.as_str());
|
||||
return Err(RequireUserSessionError::NotAuthorized);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user