feat: ✨ one time password
feat: ✨ json web token feat: ✨ mail template
This commit is contained in:
parent
c7863c806a
commit
bb2b70ccac
16 changed files with 536 additions and 9 deletions
|
@ -17,7 +17,8 @@ strip = "symbols"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = "0.7.9"
|
axum = "0.7.9"
|
||||||
chrono = { version = "0.4.39", features = ["serde"] }
|
chrono = { version = "0.4.39", features = ["serde"] }
|
||||||
lettre = { version = "0.11.11", default-features = false, features = ["builder", "smtp-transport", "tokio1-rustls-tls"] }
|
jwt-simple = { version = "0.12.11", default-features = false, features = ["pure-rust"] }
|
||||||
|
lettre = { version = "0.11.11", default-features = false, features = ["builder", "serde", "smtp-transport", "tokio1-rustls-tls"] }
|
||||||
serde = { version = "1.0.216", features = ["derive"] }
|
serde = { version = "1.0.216", features = ["derive"] }
|
||||||
serde_json = "1.0.133"
|
serde_json = "1.0.133"
|
||||||
sqlx = { version = "0.8.2", features = ["chrono", "macros", "postgres", "runtime-tokio-rustls"] }
|
sqlx = { version = "0.8.2", features = ["chrono", "macros", "postgres", "runtime-tokio-rustls"] }
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
[server_config]
|
[server_config]
|
||||||
address = "localhost:2344"
|
address = "localhost:2344"
|
||||||
|
otp_time_limit = 15
|
||||||
|
login_token_time_limit = 15
|
||||||
|
concurrency_limit = -1
|
3
mail_templates/one_time_password.toml
Normal file
3
mail_templates/one_time_password.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[one_time_password]
|
||||||
|
subject = "Your One Time Password"
|
||||||
|
body = "Dear *, \nHere is your One Time Password = * \n Best Wishes"
|
2
migrations/20250110224901_login.down.sql
Normal file
2
migrations/20250110224901_login.down.sql
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
-- Add down migration script here
|
||||||
|
DROP TABLE IF EXISTS "login";
|
6
migrations/20250110224901_login.up.sql
Normal file
6
migrations/20250110224901_login.up.sql
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
-- Add up migration script here
|
||||||
|
CREATE TABLE IF NOT EXISTS "login"(
|
||||||
|
user_id BIGSERIAL NOT NULL REFERENCES "user"(id),
|
||||||
|
token VARCHAR(1024) NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, token)
|
||||||
|
);
|
|
@ -2,6 +2,7 @@ pub mod comment;
|
||||||
pub mod comment_interaction;
|
pub mod comment_interaction;
|
||||||
pub mod contact;
|
pub mod contact;
|
||||||
pub mod interaction;
|
pub mod interaction;
|
||||||
|
pub mod login;
|
||||||
pub mod permission;
|
pub mod permission;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod post_interaction;
|
pub mod post_interaction;
|
||||||
|
|
124
src/database/login.rs
Normal file
124
src/database/login.rs
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
|
use crate::feature::login::Login;
|
||||||
|
|
||||||
|
pub async fn create(
|
||||||
|
user_id: &i64,
|
||||||
|
token: &String,
|
||||||
|
database_connection: &Pool<Postgres>,
|
||||||
|
) -> Result<Login, sqlx::Error> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
Login,
|
||||||
|
r#"
|
||||||
|
INSERT INTO "login"(user_id, token)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
RETURNING *
|
||||||
|
"#,
|
||||||
|
user_id,
|
||||||
|
token,
|
||||||
|
)
|
||||||
|
.fetch_one(database_connection)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read(
|
||||||
|
user_id: &i64,
|
||||||
|
token: &String,
|
||||||
|
database_connection: &Pool<Postgres>,
|
||||||
|
) -> Result<Login, sqlx::Error> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
Login,
|
||||||
|
r#"
|
||||||
|
SELECT * FROM "login" WHERE "user_id" = $1 AND "token" = $2
|
||||||
|
"#,
|
||||||
|
user_id,
|
||||||
|
token
|
||||||
|
)
|
||||||
|
.fetch_one(database_connection)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update(
|
||||||
|
user_id: &i64,
|
||||||
|
token: &String,
|
||||||
|
database_connection: &Pool<Postgres>,
|
||||||
|
) -> Result<Login, sqlx::Error> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
Login,
|
||||||
|
r#"
|
||||||
|
UPDATE "login" SET "token" = $2 WHERE "user_id" = $1
|
||||||
|
RETURNING *
|
||||||
|
"#,
|
||||||
|
user_id,
|
||||||
|
token,
|
||||||
|
)
|
||||||
|
.fetch_one(database_connection)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete(
|
||||||
|
user_id: &i64,
|
||||||
|
token: &String,
|
||||||
|
database_connection: &Pool<Postgres>,
|
||||||
|
) -> Result<Login, sqlx::Error> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
Login,
|
||||||
|
r#"
|
||||||
|
DELETE FROM "login" WHERE "user_id" = $1 AND "token" = $2
|
||||||
|
RETURNING *
|
||||||
|
"#,
|
||||||
|
user_id,
|
||||||
|
token,
|
||||||
|
)
|
||||||
|
.fetch_one(database_connection)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read_all_for_user(
|
||||||
|
user_id: &i64,
|
||||||
|
database_connection: &Pool<Postgres>,
|
||||||
|
) -> Result<Vec<Login>, sqlx::Error> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
Login,
|
||||||
|
r#"
|
||||||
|
SELECT * FROM "login" WHERE "user_id" = $1
|
||||||
|
"#,
|
||||||
|
user_id,
|
||||||
|
)
|
||||||
|
.fetch_all(database_connection)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_all_for_user(
|
||||||
|
user_id: &i64,
|
||||||
|
database_connection: &Pool<Postgres>,
|
||||||
|
) -> Result<Vec<Login>, sqlx::Error> {
|
||||||
|
sqlx::query_as!(
|
||||||
|
Login,
|
||||||
|
r#"
|
||||||
|
DELETE FROM "login" WHERE "user_id" = $1
|
||||||
|
RETURNING *
|
||||||
|
"#,
|
||||||
|
user_id,
|
||||||
|
)
|
||||||
|
.fetch_all(database_connection)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn count_all_for_user(
|
||||||
|
user_id: &i64,
|
||||||
|
database_connection: &Pool<Postgres>,
|
||||||
|
) -> Result<u64, sqlx::Error> {
|
||||||
|
sqlx::query!(
|
||||||
|
r#"
|
||||||
|
SELECT COUNT(user_id) FROM "login" WHERE "user_id" = $1
|
||||||
|
"#,
|
||||||
|
user_id,
|
||||||
|
)
|
||||||
|
.fetch_one(database_connection)
|
||||||
|
.await?
|
||||||
|
.count
|
||||||
|
.map_or(0, |count| count)
|
||||||
|
.try_into()
|
||||||
|
.or(Ok(0))
|
||||||
|
}
|
25
src/error.rs
25
src/error.rs
|
@ -26,3 +26,28 @@ impl std::error::Error for ForumInputError {
|
||||||
self.source()
|
self.source()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub enum ForumMailError {
|
||||||
|
TemplateHeader,
|
||||||
|
TemplateLackOfParameter,
|
||||||
|
Send(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ForumMailError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ForumMailError::TemplateHeader => write!(f, "Template Header is Wrong"),
|
||||||
|
ForumMailError::TemplateLackOfParameter => {
|
||||||
|
write!(f, "Template Parameters Are Not Enough")
|
||||||
|
}
|
||||||
|
ForumMailError::Send(error) => write!(f, "Sending | {}", error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ForumMailError {
|
||||||
|
fn cause(&self) -> Option<&dyn std::error::Error> {
|
||||||
|
self.source()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
|
pub mod auth;
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
pub mod comment_interaction;
|
pub mod comment_interaction;
|
||||||
pub mod contact;
|
pub mod contact;
|
||||||
pub mod interaction;
|
pub mod interaction;
|
||||||
|
pub mod login;
|
||||||
pub mod permission;
|
pub mod permission;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod post_interaction;
|
pub mod post_interaction;
|
||||||
|
|
62
src/feature/auth.rs
Normal file
62
src/feature/auth.rs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::ForumMailError,
|
||||||
|
mail::{MailFieldsOneTimePassword, MailTemplate},
|
||||||
|
ONE_TIME_PASSWORDS,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
pub struct OneTimePassword {
|
||||||
|
pub user_id: i64,
|
||||||
|
pub one_time_password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OneTimePassword {
|
||||||
|
pub fn init() -> RwLock<Vec<OneTimePassword>> {
|
||||||
|
RwLock::new(vec![])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new(
|
||||||
|
user_id: &i64,
|
||||||
|
user_name: &String,
|
||||||
|
user_email: &String,
|
||||||
|
) -> Result<(), ForumMailError> {
|
||||||
|
let one_time_password = "123".to_owned();
|
||||||
|
let new_self = Self {
|
||||||
|
user_id: *user_id,
|
||||||
|
one_time_password,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mail_template =
|
||||||
|
MailTemplate::OneTimePassword(MailFieldsOneTimePassword::new(user_name, &new_self));
|
||||||
|
|
||||||
|
mail_template.send_mail(user_email).await?;
|
||||||
|
|
||||||
|
let mut one_time_passwords = ONE_TIME_PASSWORDS.write().await;
|
||||||
|
one_time_passwords.push(new_self);
|
||||||
|
one_time_passwords.sort_by_key(|one_time_password| one_time_password.user_id);
|
||||||
|
drop(one_time_passwords);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn verify(one_time_password: &OneTimePassword) -> bool {
|
||||||
|
let one_time_password_search = ONE_TIME_PASSWORDS
|
||||||
|
.read()
|
||||||
|
.await
|
||||||
|
.binary_search_by(|one_time_password_| one_time_password_.cmp(one_time_password));
|
||||||
|
match one_time_password_search {
|
||||||
|
Ok(one_time_password_index) => {
|
||||||
|
let mut one_time_passwords = ONE_TIME_PASSWORDS.write().await;
|
||||||
|
one_time_passwords.swap_remove(one_time_password_index);
|
||||||
|
one_time_passwords.sort_by_key(|one_time_password| one_time_password.user_id);
|
||||||
|
drop(one_time_passwords);
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
77
src/feature/login.rs
Normal file
77
src/feature/login.rs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
use jwt_simple::{
|
||||||
|
claims::Claims,
|
||||||
|
common::VerificationOptions,
|
||||||
|
prelude::{HS256Key, MACLike},
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::{Pool, Postgres};
|
||||||
|
|
||||||
|
use crate::{database::login, SERVER_CONFIG};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Login {
|
||||||
|
pub user_id: i64,
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Login {
|
||||||
|
pub async fn create(
|
||||||
|
user_id: &i64,
|
||||||
|
database_connection: &Pool<Postgres>,
|
||||||
|
) -> Result<Login, sqlx::Error> {
|
||||||
|
let key = HS256Key::generate();
|
||||||
|
let claims = Claims::create(jwt_simple::prelude::Duration::from_mins(
|
||||||
|
SERVER_CONFIG.login_token_time_limit as u64,
|
||||||
|
));
|
||||||
|
let mut verification_options = VerificationOptions::default();
|
||||||
|
verification_options.time_tolerance = Some(jwt_simple::prelude::Duration::from(0));
|
||||||
|
|
||||||
|
let token = key.authenticate(claims).unwrap();
|
||||||
|
|
||||||
|
login::create(user_id, &token, database_connection).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read(
|
||||||
|
user_id: &i64,
|
||||||
|
token: &String,
|
||||||
|
database_connection: &Pool<Postgres>,
|
||||||
|
) -> Result<Login, sqlx::Error> {
|
||||||
|
login::read(user_id, token, database_connection).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update(
|
||||||
|
user_id: &i64,
|
||||||
|
token: &String,
|
||||||
|
database_connection: &Pool<Postgres>,
|
||||||
|
) -> Result<Login, sqlx::Error> {
|
||||||
|
login::update(user_id, token, database_connection).await
|
||||||
|
}
|
||||||
|
pub async fn delete(
|
||||||
|
user_id: &i64,
|
||||||
|
token: &String,
|
||||||
|
database_connection: &Pool<Postgres>,
|
||||||
|
) -> Result<Login, sqlx::Error> {
|
||||||
|
login::delete(user_id, token, database_connection).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read_all_for_user(
|
||||||
|
user_id: &i64,
|
||||||
|
database_connection: &Pool<Postgres>,
|
||||||
|
) -> Result<Vec<Login>, sqlx::Error> {
|
||||||
|
login::read_all_for_user(user_id, database_connection).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_all_for_user(
|
||||||
|
user_id: &i64,
|
||||||
|
database_connection: &Pool<Postgres>,
|
||||||
|
) -> Result<Vec<Login>, sqlx::Error> {
|
||||||
|
login::delete_all_for_user(user_id, database_connection).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn count_all_for_user(
|
||||||
|
user_id: &i64,
|
||||||
|
database_connection: &Pool<Postgres>,
|
||||||
|
) -> Result<u64, sqlx::Error> {
|
||||||
|
login::count_all_for_user(user_id, database_connection).await
|
||||||
|
}
|
||||||
|
}
|
15
src/lib.rs
15
src/lib.rs
|
@ -6,9 +6,17 @@ pub mod routing;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
|
use feature::auth::OneTimePassword;
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
|
use tokio::sync::RwLock;
|
||||||
use utils::naive_toml_parser;
|
use utils::naive_toml_parser;
|
||||||
|
|
||||||
|
pub static SERVER_CONFIG: LazyLock<ServerConfig> = LazyLock::new(ServerConfig::default);
|
||||||
|
pub static ONE_TIME_PASSWORDS: LazyLock<RwLock<Vec<OneTimePassword>>> =
|
||||||
|
LazyLock::new(OneTimePassword::init);
|
||||||
|
|
||||||
const DATABASE_CONFIG_FILE_LOCATION: &str = "./configs/database_config.toml";
|
const DATABASE_CONFIG_FILE_LOCATION: &str = "./configs/database_config.toml";
|
||||||
const SERVER_CONFIG_FILE_LOCATION: &str = "./configs/server_config.toml";
|
const SERVER_CONFIG_FILE_LOCATION: &str = "./configs/server_config.toml";
|
||||||
|
|
||||||
|
@ -43,15 +51,22 @@ impl Default for DatabaseConfig {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ServerConfig {
|
pub struct ServerConfig {
|
||||||
pub address: String,
|
pub address: String,
|
||||||
|
pub otp_time_limit: usize,
|
||||||
|
pub login_token_time_limit: usize,
|
||||||
|
pub concurrency_limit: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ServerConfig {
|
impl Default for ServerConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let (header, mut server_configs) = naive_toml_parser(SERVER_CONFIG_FILE_LOCATION);
|
let (header, mut server_configs) = naive_toml_parser(SERVER_CONFIG_FILE_LOCATION);
|
||||||
|
let value_or_max = |value: String| value.parse().map_or(usize::MAX, |value| value);
|
||||||
|
|
||||||
if header == "[server_config]" {
|
if header == "[server_config]" {
|
||||||
Self {
|
Self {
|
||||||
address: server_configs.pop_front().unwrap().parse().unwrap(),
|
address: server_configs.pop_front().unwrap().parse().unwrap(),
|
||||||
|
otp_time_limit: value_or_max(server_configs.pop_front().unwrap()),
|
||||||
|
login_token_time_limit: value_or_max(server_configs.pop_front().unwrap()),
|
||||||
|
concurrency_limit: value_or_max(server_configs.pop_front().unwrap()),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
panic!("Server Config File Must Include [server_config] at the First Line")
|
panic!("Server Config File Must Include [server_config] at the First Line")
|
||||||
|
|
57
src/mail.rs
57
src/mail.rs
|
@ -4,9 +4,11 @@ use lettre::{
|
||||||
AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor,
|
AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::utils::naive_toml_parser;
|
use crate::{error::ForumMailError, feature::auth::OneTimePassword, utils::naive_toml_parser};
|
||||||
|
|
||||||
const MAIL_CONFIG_FILE_LOCATION: &str = "./configs/mail_config.toml";
|
const MAIL_CONFIG_FILE_LOCATION: &str = "./configs/mail_config.toml";
|
||||||
|
const ONE_TIME_PASSWORD_MAIL_TEMPLATE_FILE_LOCATION: &str =
|
||||||
|
"./mail_templates/one_time_password.toml";
|
||||||
|
|
||||||
pub struct MailConfig {
|
pub struct MailConfig {
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -38,7 +40,58 @@ impl Default for MailConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_mail(
|
pub struct MailFieldsOneTimePassword {
|
||||||
|
receiver_name: String,
|
||||||
|
one_time_password: OneTimePassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MailFieldsOneTimePassword {
|
||||||
|
pub fn new(receiver_name: &String, one_time_password: &OneTimePassword) -> Self {
|
||||||
|
Self {
|
||||||
|
receiver_name: receiver_name.to_owned(),
|
||||||
|
one_time_password: one_time_password.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub enum MailTemplate {
|
||||||
|
OneTimePassword(MailFieldsOneTimePassword),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MailTemplate {
|
||||||
|
pub async fn send_mail(
|
||||||
|
&self,
|
||||||
|
receiver: &String,
|
||||||
|
) -> Result<smtp::response::Response, ForumMailError> {
|
||||||
|
match self {
|
||||||
|
MailTemplate::OneTimePassword(mail_fields) => {
|
||||||
|
let mut mail_template_from_file =
|
||||||
|
naive_toml_parser(ONE_TIME_PASSWORD_MAIL_TEMPLATE_FILE_LOCATION);
|
||||||
|
if mail_template_from_file.0 == "one_time_password" {
|
||||||
|
let subject = match mail_template_from_file.1.pop_front() {
|
||||||
|
Some(subject) => subject,
|
||||||
|
None => return Err(ForumMailError::TemplateLackOfParameter),
|
||||||
|
};
|
||||||
|
let body = match mail_template_from_file.1.pop_front() {
|
||||||
|
Some(body) => body,
|
||||||
|
None => return Err(ForumMailError::TemplateLackOfParameter),
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = body.replacen('*', &mail_fields.receiver_name, 1);
|
||||||
|
let body =
|
||||||
|
body.replacen('*', &mail_fields.one_time_password.one_time_password, 1);
|
||||||
|
|
||||||
|
send_mail(receiver, &subject, &body)
|
||||||
|
.await
|
||||||
|
.map_err(|error| ForumMailError::Send(error.to_string()))
|
||||||
|
} else {
|
||||||
|
Err(ForumMailError::TemplateHeader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn send_mail(
|
||||||
receiver: &String,
|
receiver: &String,
|
||||||
subject: &String,
|
subject: &String,
|
||||||
body: &String,
|
body: &String,
|
||||||
|
|
|
@ -2,6 +2,7 @@ pub mod comment;
|
||||||
pub mod comment_interaction;
|
pub mod comment_interaction;
|
||||||
pub mod contact;
|
pub mod contact;
|
||||||
pub mod interaction;
|
pub mod interaction;
|
||||||
|
pub mod login;
|
||||||
pub mod permission;
|
pub mod permission;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod post_interaction;
|
pub mod post_interaction;
|
||||||
|
@ -18,7 +19,7 @@ use tower_http::cors::CorsLayer;
|
||||||
|
|
||||||
use crate::{database, AppState};
|
use crate::{database, AppState};
|
||||||
|
|
||||||
pub async fn route(State(app_state): State<AppState>) -> Router {
|
pub async fn route(concurrency_limit: &usize, State(app_state): State<AppState>) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(alive))
|
.route("/", get(alive))
|
||||||
.nest(
|
.nest(
|
||||||
|
@ -70,7 +71,7 @@ pub async fn route(State(app_state): State<AppState>) -> Router {
|
||||||
routing_permission::route(axum::extract::State(app_state.clone())),
|
routing_permission::route(axum::extract::State(app_state.clone())),
|
||||||
)
|
)
|
||||||
.layer(CorsLayer::permissive())
|
.layer(CorsLayer::permissive())
|
||||||
.layer(ConcurrencyLimitLayer::new(100))
|
.layer(ConcurrencyLimitLayer::new(*concurrency_limit))
|
||||||
.with_state(app_state)
|
.with_state(app_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
148
src/routing/login.rs
Normal file
148
src/routing/login.rs
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
use axum::{
|
||||||
|
extract::{Path, State},
|
||||||
|
http::StatusCode,
|
||||||
|
response::IntoResponse,
|
||||||
|
routing::{delete, get, patch, post},
|
||||||
|
Json, Router,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
feature::{auth::OneTimePassword, login::Login},
|
||||||
|
AppState,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct CreateLogin {
|
||||||
|
pub one_time_password: OneTimePassword,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct UpdateLogin {
|
||||||
|
pub user_id: i64,
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn route(State(app_state): State<AppState>) -> Router<AppState> {
|
||||||
|
Router::new()
|
||||||
|
.route("/", post(create))
|
||||||
|
.route("/users/:user_id/token/:token", get(read))
|
||||||
|
.route("/", patch(update))
|
||||||
|
.route("/users/:user_id/token/:token", delete(delete_))
|
||||||
|
.route("/users/:user_id", get(read_all_for_user))
|
||||||
|
.route("/users/:user_id", delete(delete_all_for_user))
|
||||||
|
.route("/count/users/:user_id", get(count_all_for_user))
|
||||||
|
.with_state(app_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create(
|
||||||
|
State(app_state): State<AppState>,
|
||||||
|
Json(create_login): Json<CreateLogin>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
match OneTimePassword::verify(&create_login.one_time_password).await {
|
||||||
|
true => {
|
||||||
|
match Login::create(
|
||||||
|
&create_login.one_time_password.user_id,
|
||||||
|
&app_state.database_connection,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(login) => (StatusCode::CREATED, Json(serde_json::json!(login))),
|
||||||
|
Err(err_val) => (
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(serde_json::json!(err_val.to_string())),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false => (
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(serde_json::json!(
|
||||||
|
"One Time Password Authentication Failed".to_string()
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read(
|
||||||
|
State(app_state): State<AppState>,
|
||||||
|
Path((user_id, token)): Path<(i64, String)>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
match Login::read(&user_id, &token, &app_state.database_connection).await {
|
||||||
|
Ok(login) => (StatusCode::OK, Json(serde_json::json!(login))),
|
||||||
|
Err(err_val) => (
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(serde_json::json!(err_val.to_string())),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update(
|
||||||
|
State(app_state): State<AppState>,
|
||||||
|
Json(update_role): Json<UpdateLogin>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
match Login::update(
|
||||||
|
&update_role.user_id,
|
||||||
|
&update_role.token,
|
||||||
|
&app_state.database_connection,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(login) => (StatusCode::ACCEPTED, Json(serde_json::json!(login))),
|
||||||
|
Err(err_val) => (
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(serde_json::json!(err_val.to_string())),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_(
|
||||||
|
State(app_state): State<AppState>,
|
||||||
|
Path((user_id, token)): Path<(i64, String)>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
match Login::delete(&user_id, &token, &app_state.database_connection).await {
|
||||||
|
Ok(login) => (StatusCode::NO_CONTENT, Json(serde_json::json!(login))),
|
||||||
|
Err(err_val) => (
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(serde_json::json!(err_val.to_string())),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_all_for_user(
|
||||||
|
State(app_state): State<AppState>,
|
||||||
|
Path(user_id): Path<i64>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
match Login::read_all_for_user(&user_id, &app_state.database_connection).await {
|
||||||
|
Ok(logins) => (StatusCode::OK, Json(serde_json::json!(logins))),
|
||||||
|
Err(err_val) => (
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(serde_json::json!(err_val.to_string())),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_all_for_user(
|
||||||
|
State(app_state): State<AppState>,
|
||||||
|
Path(user_id): Path<i64>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
match Login::delete_all_for_user(&user_id, &app_state.database_connection).await {
|
||||||
|
Ok(logins) => (StatusCode::OK, Json(serde_json::json!(logins))),
|
||||||
|
Err(err_val) => (
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(serde_json::json!(err_val.to_string())),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn count_all_for_user(
|
||||||
|
State(app_state): State<AppState>,
|
||||||
|
Path(user_id): Path<i64>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
match Login::count_all_for_user(&user_id, &app_state.database_connection).await {
|
||||||
|
Ok(login_count) => (StatusCode::OK, Json(serde_json::json!(login_count))),
|
||||||
|
Err(err_val) => (
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(serde_json::json!(err_val.to_string())),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,15 @@
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
use crate::{AppState, ServerConfig};
|
use crate::{AppState, SERVER_CONFIG};
|
||||||
|
|
||||||
pub async fn start_server(app_state: AppState) {
|
pub async fn start_server(app_state: AppState) {
|
||||||
let server_config = ServerConfig::default();
|
let server_config = &SERVER_CONFIG;
|
||||||
|
|
||||||
let router = crate::routing::route(axum::extract::State(app_state)).await;
|
let router = crate::routing::route(
|
||||||
|
&server_config.concurrency_limit,
|
||||||
|
axum::extract::State(app_state),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
let listener = TcpListener::bind(&server_config.address).await.unwrap();
|
let listener = TcpListener::bind(&server_config.address).await.unwrap();
|
||||||
println!("\n\thttp://{}", server_config.address);
|
println!("\n\thttp://{}", server_config.address);
|
||||||
axum::serve(listener, router).await.unwrap()
|
axum::serve(listener, router).await.unwrap()
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue