Add docker build and update to actix-web v3
This commit is contained in:
parent
9b7d28c6fe
commit
1d1cff4f74
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
target
|
||||||
|
.git
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
# test.db
|
1519
Cargo.lock
generated
1519
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
46
Cargo.toml
46
Cargo.toml
@ -5,39 +5,39 @@ authors = ['Rohan Sircar <rohansircar@tuta.io>']
|
|||||||
edition = '2018'
|
edition = '2018'
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "2.0.0"
|
actix-web = "3.3.2"
|
||||||
actix-rt = "1.1.1"
|
# actix-rt = "1.1.1"
|
||||||
actix-service = "1.0.6"
|
actix-service = "2.0.0"
|
||||||
actix-files = "0.2.2"
|
actix-files = "0.5.0"
|
||||||
bytes = "0.5.6"
|
bytes = "1.0.1"
|
||||||
futures = "0.3.5"
|
futures = "0.3.14"
|
||||||
log = "0.4.11"
|
log = "0.4.14"
|
||||||
env_logger = "0.7.1"
|
env_logger = "0.8.3"
|
||||||
serde_json = "1.0.57"
|
serde_json = "1.0.64"
|
||||||
json = "0.12.4"
|
json = "0.12.4"
|
||||||
listenfd = "0.3.3"
|
listenfd = "0.3.3"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
r2d2 = "0.8.9"
|
r2d2 = "0.8.9"
|
||||||
validator = "0.10.1"
|
validator = "0.13.0"
|
||||||
validator_derive = "0.10.1"
|
validator_derive = "0.13.0"
|
||||||
jsonwebtoken = "7.2.0"
|
jsonwebtoken = "7.2.0"
|
||||||
actix-identity = "0.2.1"
|
actix-identity = "0.3.1"
|
||||||
actix-web-httpauth = "0.4.2"
|
actix-web-httpauth = "0.5.1"
|
||||||
rand = "0.7.3"
|
rand = "0.8.3"
|
||||||
nanoid = "0.3.0"
|
nanoid = "0.4.0"
|
||||||
bcrypt = "0.8.2"
|
bcrypt = "0.9.0"
|
||||||
timeago = "0.2.1"
|
timeago = "0.3.0"
|
||||||
comp = "0.2.1"
|
comp = "0.2.1"
|
||||||
regex = "1.3.9"
|
regex = "1.4.5"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
lazy-regex = "0.1.4"
|
lazy-regex = "0.1.4"
|
||||||
custom_error = "1.7.1"
|
custom_error = "1.9.2"
|
||||||
derive-new = "0.5.8"
|
derive-new = "0.5.9"
|
||||||
diesel_migrations = "1.4.0"
|
diesel_migrations = "1.4.0"
|
||||||
actix-threadpool = "0.3.3"
|
actix-threadpool = "0.3.3"
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1.0.115"
|
version = "1.0.125"
|
||||||
features = ['derive']
|
features = ['derive']
|
||||||
|
|
||||||
# [dependencies.yarte]
|
# [dependencies.yarte]
|
||||||
@ -53,7 +53,7 @@ features = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[dependencies.uuid]
|
[dependencies.uuid]
|
||||||
version = "0.8.1"
|
version = "0.8.2"
|
||||||
features = [
|
features = [
|
||||||
'serde',
|
'serde',
|
||||||
'v4',
|
'v4',
|
||||||
@ -64,7 +64,7 @@ version = "0.23.1"
|
|||||||
features = ['bundled']
|
features = ['bundled']
|
||||||
|
|
||||||
[dependencies.chrono]
|
[dependencies.chrono]
|
||||||
version = "0.4.15"
|
version = "0.4.19"
|
||||||
features = ['serde']
|
features = ['serde']
|
||||||
# [build-dependencies.yarte_helpers]
|
# [build-dependencies.yarte_helpers]
|
||||||
# version = '0.9.0'
|
# version = '0.9.0'
|
||||||
|
46
Dockerfile
Normal file
46
Dockerfile
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
FROM rust:1.45 as builder
|
||||||
|
|
||||||
|
# ENV CARGO_HOME=/actix-demo/.cargo
|
||||||
|
RUN USER=root cargo new --bin actix-demo
|
||||||
|
WORKDIR /actix-demo
|
||||||
|
# COPY ./.cargo ./.cargo
|
||||||
|
COPY ./Cargo.toml ./Cargo.toml
|
||||||
|
COPY ./Cargo.lock ./Cargo.lock
|
||||||
|
RUN cargo build --release
|
||||||
|
RUN rm src/*.rs
|
||||||
|
|
||||||
|
COPY ./src ./src
|
||||||
|
|
||||||
|
#RUN rm ./target/release/deps/actix-demo*
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
FROM debian:buster-slim
|
||||||
|
ARG APP=/usr/src/app
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y ca-certificates tzdata \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
ENV TZ=Etc/UTC \
|
||||||
|
APP_USER=appuser
|
||||||
|
|
||||||
|
RUN groupadd $APP_USER \
|
||||||
|
&& useradd -g $APP_USER $APP_USER \
|
||||||
|
&& mkdir -p ${APP}
|
||||||
|
|
||||||
|
COPY ./.env ${APP}/.env
|
||||||
|
COPY ./migrations ${APP}/migrations
|
||||||
|
COPY ./static ${APP}/static
|
||||||
|
COPY ./test.db ${APP}/test.db
|
||||||
|
COPY --from=builder /actix-demo/target/release/actix-demo ${APP}/actix-demo
|
||||||
|
|
||||||
|
RUN chown -R $APP_USER:$APP_USER ${APP}
|
||||||
|
|
||||||
|
USER $APP_USER
|
||||||
|
WORKDIR ${APP}
|
||||||
|
|
||||||
|
CMD ["./actix-demo"]
|
@ -43,19 +43,20 @@ pub fn get_all(
|
|||||||
|
|
||||||
/// Run query using Diesel to insert a new database row and return the result.
|
/// Run query using Diesel to insert a new database row and return the result.
|
||||||
pub fn insert_new_user(
|
pub fn insert_new_user(
|
||||||
mut nu: Rc<models::NewUser>,
|
nu: models::NewUser,
|
||||||
conn: &SqliteConnection,
|
conn: &SqliteConnection,
|
||||||
) -> Result<models::UserDTO, errors::DomainError> {
|
) -> Result<models::UserDTO, errors::DomainError> {
|
||||||
// It is common when using Diesel with Actix web to import schema-related
|
// It is common when using Diesel with Actix web to import schema-related
|
||||||
// modules inside a function's scope (rather than the normal module's scope)
|
// modules inside a function's scope (rather than the normal module's scope)
|
||||||
// to prevent import collisions and namespace pollution.
|
// to prevent import collisions and namespace pollution.
|
||||||
use crate::schema::users::dsl::*;
|
use crate::schema::users::dsl::*;
|
||||||
let mut nu2 = Rc::make_mut(&mut nu);
|
let nu = {
|
||||||
|
let mut nu2 = nu.clone();
|
||||||
nu2.password = hash(&nu2.password, DEFAULT_COST)?;
|
nu2.password = hash(&nu2.password, DEFAULT_COST)?;
|
||||||
|
nu2
|
||||||
|
};
|
||||||
|
|
||||||
diesel::insert_into(users)
|
diesel::insert_into(users).values(&nu).execute(conn)?;
|
||||||
.values(nu.as_ref())
|
|
||||||
.execute(conn)?;
|
|
||||||
let user =
|
let user =
|
||||||
query::_get_user_by_name(&nu.name).first::<models::UserDTO>(conn)?;
|
query::_get_user_by_name(&nu.name).first::<models::UserDTO>(conn)?;
|
||||||
Ok(user)
|
Ok(user)
|
||||||
|
27
src/main.rs
Normal file → Executable file
27
src/main.rs
Normal file → Executable file
@ -7,14 +7,14 @@ extern crate custom_error;
|
|||||||
extern crate regex;
|
extern crate regex;
|
||||||
extern crate validator;
|
extern crate validator;
|
||||||
|
|
||||||
use actix_web::{middleware, web, App, HttpServer, cookie::SameSite};
|
use actix_web::{cookie::SameSite, middleware, web, App, HttpServer};
|
||||||
|
|
||||||
use actix_web_httpauth::middleware::HttpAuthentication;
|
use actix_web_httpauth::middleware::HttpAuthentication;
|
||||||
|
|
||||||
|
use actix_files as fs;
|
||||||
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
|
use services::UserServiceImpl;
|
||||||
use actix_files as fs;
|
|
||||||
|
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
use diesel::r2d2::{self, ConnectionManager};
|
use diesel::r2d2::{self, ConnectionManager};
|
||||||
@ -27,6 +27,7 @@ mod middlewares;
|
|||||||
mod models;
|
mod models;
|
||||||
mod routes;
|
mod routes;
|
||||||
mod schema;
|
mod schema;
|
||||||
|
mod services;
|
||||||
mod types;
|
mod types;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
@ -39,14 +40,14 @@ pub struct AppConfig {
|
|||||||
pool: DbPool,
|
pool: DbPool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::main]
|
#[actix_web::main]
|
||||||
async fn main() -> std::io::Result<()> {
|
async fn main() -> std::io::Result<()> {
|
||||||
std::env::set_var("RUST_LOG", "debug");
|
std::env::set_var("RUST_LOG", "debug");
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
dotenv::dotenv().ok();
|
dotenv::dotenv().ok();
|
||||||
|
|
||||||
let basic_auth_middleware =
|
// let _basic_auth_middleware =
|
||||||
HttpAuthentication::basic(utils::auth::validator);
|
// HttpAuthentication::basic(utils::auth::validator);
|
||||||
|
|
||||||
// set up database connection pool
|
// set up database connection pool
|
||||||
let connspec =
|
let connspec =
|
||||||
@ -67,7 +68,14 @@ async fn main() -> std::io::Result<()> {
|
|||||||
8
|
8
|
||||||
});
|
});
|
||||||
|
|
||||||
let config: AppConfig = AppConfig { pool, hash_cost };
|
let config: AppConfig = AppConfig {
|
||||||
|
pool: pool.clone(),
|
||||||
|
hash_cost,
|
||||||
|
};
|
||||||
|
|
||||||
|
// let user_controller = UserController {
|
||||||
|
// user_service: &user_service,
|
||||||
|
// };
|
||||||
|
|
||||||
let addr = std::env::var("BIND_ADDRESS").expect("BIND ADDRESS NOT FOUND");
|
let addr = std::env::var("BIND_ADDRESS").expect("BIND ADDRESS NOT FOUND");
|
||||||
info!("Starting server {}", addr);
|
info!("Starting server {}", addr);
|
||||||
@ -79,15 +87,16 @@ async fn main() -> std::io::Result<()> {
|
|||||||
CookieIdentityPolicy::new(&private_key)
|
CookieIdentityPolicy::new(&private_key)
|
||||||
.name("my-app-auth")
|
.name("my-app-auth")
|
||||||
.secure(false)
|
.secure(false)
|
||||||
.same_site(SameSite::Lax)
|
.same_site(SameSite::Lax),
|
||||||
))
|
))
|
||||||
.wrap(middleware::Logger::default())
|
.wrap(middleware::Logger::default())
|
||||||
.service(
|
.service(
|
||||||
web::scope("/api/authzd") // endpoint requiring authentication
|
web::scope("/api/authzd") // endpoint requiring authentication
|
||||||
.wrap(basic_auth_middleware.clone())
|
// .wrap(_basic_auth_middleware.clone())
|
||||||
.service(routes::users::get_user)
|
.service(routes::users::get_user)
|
||||||
.service(routes::users::get_all_users),
|
.service(routes::users::get_all_users),
|
||||||
)
|
)
|
||||||
|
// .route("/api/users/get", web::get().to(user_controller.get_user.into()))
|
||||||
.service(web::scope("/api/public")) // public endpoint - not implemented yet
|
.service(web::scope("/api/public")) // public endpoint - not implemented yet
|
||||||
.service(routes::auth::login)
|
.service(routes::auth::login)
|
||||||
.service(routes::auth::logout)
|
.service(routes::auth::logout)
|
||||||
|
@ -68,7 +68,7 @@ pub async fn index(id: Identity) -> String {
|
|||||||
/// basic auth middleware function
|
/// basic auth middleware function
|
||||||
pub fn validate_basic_auth(
|
pub fn validate_basic_auth(
|
||||||
credentials: BasicAuth,
|
credentials: BasicAuth,
|
||||||
config: &web::Data<AppConfig>,
|
config: &AppConfig,
|
||||||
) -> Result<bool, errors::DomainError> {
|
) -> Result<bool, errors::DomainError> {
|
||||||
let result = if let Some(password_ref) = credentials.password() {
|
let result = if let Some(password_ref) = credentials.password() {
|
||||||
let pool = &config.pool;
|
let pool = &config.pool;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use actix_web::{get, post, web, HttpResponse};
|
use actix_web::{get, post, web, HttpResponse};
|
||||||
|
|
||||||
use crate::actions;
|
use crate::errors::DomainError;
|
||||||
use crate::models;
|
use crate::services::UserService;
|
||||||
use crate::AppConfig;
|
use crate::AppConfig;
|
||||||
|
use crate::{actions, models};
|
||||||
use actix_web::error::ResponseError;
|
use actix_web::error::ResponseError;
|
||||||
use std::rc::Rc;
|
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
/// Finds user by UID.
|
/// Finds user by UID.
|
||||||
@ -12,7 +12,7 @@ use validator::Validate;
|
|||||||
pub async fn get_user(
|
pub async fn get_user(
|
||||||
config: web::Data<AppConfig>,
|
config: web::Data<AppConfig>,
|
||||||
user_id: web::Path<i32>,
|
user_id: web::Path<i32>,
|
||||||
) -> Result<HttpResponse, impl ResponseError> {
|
) -> Result<HttpResponse, DomainError> {
|
||||||
let u_id = user_id.into_inner();
|
let u_id = user_id.into_inner();
|
||||||
// use web::block to offload blocking Diesel code without blocking server thread
|
// use web::block to offload blocking Diesel code without blocking server thread
|
||||||
let res = web::block(move || {
|
let res = web::block(move || {
|
||||||
@ -21,16 +21,36 @@ pub async fn get_user(
|
|||||||
actions::find_user_by_uid(u_id, &conn)
|
actions::find_user_by_uid(u_id, &conn)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.and_then(|maybe_user| {
|
.map_err(|err| {
|
||||||
if let Some(user) = maybe_user {
|
let res = DomainError::new_generic_error(format!(
|
||||||
|
"No user found with uid: {}",
|
||||||
|
u_id
|
||||||
|
));
|
||||||
|
res
|
||||||
|
})?;
|
||||||
|
if let Some(user) = res {
|
||||||
|
Ok(HttpResponse::Ok().json(user))
|
||||||
|
} else {
|
||||||
|
let res = HttpResponse::NotFound()
|
||||||
|
.body(format!("No user found with uid: {}", u_id));
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/get/users/{user_id}")]
|
||||||
|
pub async fn get_user2(
|
||||||
|
user_service: web::Data<dyn UserService>,
|
||||||
|
user_id: web::Path<i32>,
|
||||||
|
) -> Result<HttpResponse, DomainError> {
|
||||||
|
let u_id = user_id.into_inner();
|
||||||
|
let user = user_service.find_user_by_uid(u_id)?;
|
||||||
|
if let Some(user) = user {
|
||||||
Ok(HttpResponse::Ok().json(user))
|
Ok(HttpResponse::Ok().json(user))
|
||||||
} else {
|
} else {
|
||||||
let res = HttpResponse::NotFound()
|
let res = HttpResponse::NotFound()
|
||||||
.body(format!("No user found with uid: {}", u_id));
|
.body(format!("No user found with uid: {}", u_id));
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
});
|
|
||||||
res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/get/users")]
|
#[get("/get/users")]
|
||||||
@ -75,7 +95,7 @@ pub async fn add_user(
|
|||||||
Ok(_) => web::block(move || {
|
Ok(_) => web::block(move || {
|
||||||
let pool = &config.pool;
|
let pool = &config.pool;
|
||||||
let conn = pool.get()?;
|
let conn = pool.get()?;
|
||||||
actions::insert_new_user(Rc::new(form.0), &conn)
|
actions::insert_new_user(form.0, &conn)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.and_then(|user| {
|
.and_then(|user| {
|
||||||
|
2
src/services.rs
Normal file
2
src/services.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod user_service;
|
||||||
|
pub use self::user_service::*;
|
84
src/services/user_service.rs
Normal file
84
src/services/user_service.rs
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use diesel::SqliteConnection;
|
||||||
|
|
||||||
|
use crate::{actions, errors, models, types::DbPool};
|
||||||
|
|
||||||
|
pub trait UserService {
|
||||||
|
fn find_user_by_uid(
|
||||||
|
&self,
|
||||||
|
uid: i32,
|
||||||
|
) -> Result<Option<models::UserDTO>, errors::DomainError>;
|
||||||
|
fn _find_user_by_name(
|
||||||
|
&self,
|
||||||
|
user_name: String,
|
||||||
|
) -> Result<Option<models::UserDTO>, errors::DomainError>;
|
||||||
|
|
||||||
|
fn get_all(
|
||||||
|
&self,
|
||||||
|
) -> Result<Option<Vec<models::UserDTO>>, errors::DomainError>;
|
||||||
|
|
||||||
|
fn insert_new_user(
|
||||||
|
&self,
|
||||||
|
nu: models::NewUser,
|
||||||
|
) -> Result<models::UserDTO, errors::DomainError>;
|
||||||
|
|
||||||
|
// fn woot(&self) -> i32;
|
||||||
|
|
||||||
|
fn verify_password<'a>(
|
||||||
|
&self,
|
||||||
|
user_name: &'a String,
|
||||||
|
given_password: &'a String,
|
||||||
|
) -> Result<bool, errors::DomainError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct UserServiceImpl {
|
||||||
|
pub pool: DbPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserService for UserServiceImpl {
|
||||||
|
fn find_user_by_uid(
|
||||||
|
&self,
|
||||||
|
uid: i32,
|
||||||
|
) -> Result<Option<models::UserDTO>, errors::DomainError> {
|
||||||
|
let conn = self.pool.get()?;
|
||||||
|
actions::find_user_by_uid(uid, &conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _find_user_by_name(
|
||||||
|
&self,
|
||||||
|
user_name: String,
|
||||||
|
) -> Result<Option<models::UserDTO>, errors::DomainError> {
|
||||||
|
let conn = self.pool.get()?;
|
||||||
|
actions::_find_user_by_name(user_name, &conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_all(
|
||||||
|
&self,
|
||||||
|
) -> Result<Option<Vec<models::UserDTO>>, errors::DomainError> {
|
||||||
|
let conn = self.pool.get()?;
|
||||||
|
actions::get_all(&conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_new_user(
|
||||||
|
&self,
|
||||||
|
nu: models::NewUser,
|
||||||
|
) -> Result<models::UserDTO, errors::DomainError> {
|
||||||
|
let conn = self.pool.get()?;
|
||||||
|
actions::insert_new_user(nu, &conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_password<'b>(
|
||||||
|
&self,
|
||||||
|
user_name: &'b String,
|
||||||
|
given_password: &'b String,
|
||||||
|
) -> Result<bool, errors::DomainError> {
|
||||||
|
let conn = self.pool.get()?;
|
||||||
|
actions::verify_password(user_name, given_password, &conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// async fn woot(&self) -> i32 {
|
||||||
|
// 1
|
||||||
|
// }
|
||||||
|
}
|
43
src/utils/auth.rs
Normal file → Executable file
43
src/utils/auth.rs
Normal file → Executable file
@ -1,28 +1,31 @@
|
|||||||
use actix_web_httpauth::extractors::basic::BasicAuth;
|
use actix_web_httpauth::extractors::basic::BasicAuth;
|
||||||
|
|
||||||
use crate::AppConfig;
|
|
||||||
use crate::routes::validate_basic_auth;
|
use crate::routes::validate_basic_auth;
|
||||||
|
use crate::AppConfig;
|
||||||
|
|
||||||
use actix_web::{dev::ServiceRequest, web, Error};
|
use actix_web::{dev::ServiceRequest, web, Error};
|
||||||
|
|
||||||
|
// pub async fn validator(
|
||||||
|
// req: ServiceRequest,
|
||||||
|
// credentials: BasicAuth,
|
||||||
|
// ) -> Result<ServiceRequest, Error> {
|
||||||
|
// println!("{}", credentials.user_id());
|
||||||
|
// println!("{:?}", credentials.password());
|
||||||
|
// // verify credentials from db
|
||||||
|
// let config = req.app_data::<AppConfig>().expect("Error getting config");
|
||||||
|
|
||||||
pub async fn validator(
|
// let valid =
|
||||||
req: ServiceRequest,
|
// web::block(move || validate_basic_auth(credentials, config)).await?;
|
||||||
credentials: BasicAuth,
|
// if valid {
|
||||||
) -> Result<ServiceRequest, Error> {
|
// debug!("Success");
|
||||||
println!("{}", credentials.user_id());
|
// Ok(req)
|
||||||
println!("{:?}", credentials.password());
|
// } else {
|
||||||
// verify credentials from db
|
// println!("blah");
|
||||||
let config = req.app_data::<AppConfig>().expect("Error getting config");
|
// let err: Error = crate::errors::DomainError::new_password_error(
|
||||||
|
// "Wrong password or account does not exist".to_string(),
|
||||||
|
// )
|
||||||
|
// .into();
|
||||||
|
|
||||||
let valid =
|
// Err(err)
|
||||||
web::block(move || validate_basic_auth(credentials, &config)).await?;
|
// }
|
||||||
if valid {
|
// }
|
||||||
debug!("Success");
|
|
||||||
Ok(req)
|
|
||||||
} else {
|
|
||||||
Err(crate::errors::DomainError::new_password_error(
|
|
||||||
"Wrong password or account does not exist".to_string(),
|
|
||||||
).into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user