feat: auth

This commit is contained in:
Maulana Sodiqin
2025-03-13 23:40:16 +07:00
parent 298144bd36
commit acfb8259bc
17 changed files with 226 additions and 179 deletions
+6 -15
View File
@@ -1,31 +1,22 @@
use crate::{redisdb_init, Env, RedisClient};
use crate::{surrealdb_init, SurrealClient};
use axum::{serve, Router};
use std::env;
use std::{future::Future, net::SocketAddr};
use tokio::net::TcpListener;
use crate::{redisdb_init, RedisClient};
use crate::{surrealdb_init, SurrealClient};
pub async fn axum_init<F, Fut>(router_fn: F)
where
F: FnOnce(SurrealClient, RedisClient) -> Fut,
Fut: Future<Output = Router>,
{
let env = Env::new();
let surrealdb = surrealdb_init().await.expect("Failed surrealdb");
let redisdb = redisdb_init().await.expect("Failed redisdb");
let redisdb = redisdb_init().await.expect("Failed redisdb");
let router = router_fn(surrealdb, redisdb).await;
let addr = SocketAddr::from((
[0, 0, 0, 0],
env::var("PORT")
.unwrap_or_else(|_| "3000".to_string())
.parse()
.unwrap(),
));
let port = env.port;
let addr = SocketAddr::from(([0, 0, 0, 0], port));
let listener = TcpListener::bind(&addr).await.unwrap();
println!("Listening on http://{}", addr);
match serve(listener, router).await {
Ok(_) => println!("Server stopped gracefully."),
Err(err) => println!("Server encountered an error: {}", err),
+57
View File
@@ -0,0 +1,57 @@
use std::env;
pub struct Env {
pub port: u16,
pub access_token_secret: String,
pub refresh_token_secret: String,
pub database_url: String,
pub database_schema: String,
pub smtp_email: String,
pub smtp_password: String,
pub smtp_name: String,
pub smpt_host: String,
pub redis_hostname: String,
pub fe_url: String,
pub rust_env: String,
pub minio_endpoint: String,
pub minio_bucket_name: String,
pub minio_access_key: String,
pub minio_secret_key: String,
}
impl Env {
pub fn new() -> Self {
Self {
port: env::var("PORT")
.unwrap_or("3000".to_string())
.parse()
.unwrap_or(3000),
access_token_secret: env::var("ACCESS_TOKEN_SECRET")
.unwrap_or("default_access_secret".to_string()),
refresh_token_secret: env::var("REFRESH_TOKEN_SECRET")
.unwrap_or("default_refresh_secret".to_string()),
database_url: env::var("DATABASE_URL")
.unwrap_or("postgres://localhost".to_string()),
database_schema: env::var("DATABASE_SCHEMA")
.unwrap_or("public".to_string()),
smtp_email: env::var("SMTP_EMAIL")
.unwrap_or("no-reply@example.com".to_string()),
smtp_password: env::var("SMTP_PASSWORD")
.unwrap_or("default_smtp_password".to_string()),
smtp_name: env::var("SMTP_NAME").unwrap_or("MyApp SMTP".to_string()),
smpt_host: env::var("SMPT_HOST").unwrap_or("smpt.gmail.com".to_string()),
redis_hostname: env::var("REDIS_HOSTNAME")
.unwrap_or("localhost".to_string()),
fe_url: env::var("FE_URL").unwrap_or("http://localhost".to_string()),
rust_env: env::var("RUST_ENV").unwrap_or("development".to_string()),
minio_endpoint: env::var("MINIO_ENDPOINT")
.unwrap_or("http://localhost:9000".to_string()),
minio_bucket_name: env::var("MINIO_BUCKET_NAME")
.unwrap_or("default_bucket".to_string()),
minio_access_key: env::var("MINIO_ACCESS_KEY")
.unwrap_or("minio_access".to_string()),
minio_secret_key: env::var("MINIO_SECRET_KEY")
.unwrap_or("minio_secret".to_string()),
}
}
}
+20 -26
View File
@@ -1,31 +1,32 @@
use super::Env;
use axum::http::StatusCode;
use chrono::{Duration, TimeDelta, Utc};
use jsonwebtoken::{
decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation,
};
use serde::{Deserialize, Serialize};
use std::env;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenSub {
pub email: String,
pub role_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Claims {
pub exp: usize,
pub iat: usize,
pub email: String,
pub sub: TokenSub,
}
pub fn encode_access_token(email: &str) -> Result<String, StatusCode> {
let secret: String = env::var("ACCESS_TOKEN_SECRET")
.unwrap_or("this-is-secret".to_string())
.to_string();
pub fn encode_access_token(sub: TokenSub) -> Result<String, StatusCode> {
let env = Env::new();
let secret: String = env.access_token_secret;
let now = Utc::now();
let expire: TimeDelta = Duration::minutes(15);
let exp: usize = (now + expire).timestamp() as usize;
let iat: usize = now.timestamp() as usize;
let claim = Claims {
iat,
exp,
email: email.to_string(),
};
let claim = Claims { iat, exp, sub };
encode(
&Header::default(),
&claim,
@@ -37,9 +38,8 @@ pub fn encode_access_token(email: &str) -> Result<String, StatusCode> {
pub fn decode_access_token(
jwt_token: &str,
) -> Result<TokenData<Claims>, StatusCode> {
let secret = env::var("ACCESS_TOKEN_SECRET")
.unwrap_or("this-is-secret".to_string())
.to_string();
let env = Env::new();
let secret: String = env.access_token_secret;
let result: Result<TokenData<Claims>, StatusCode> = decode(
&jwt_token,
&DecodingKey::from_secret(secret.as_ref()),
@@ -49,19 +49,14 @@ pub fn decode_access_token(
result
}
pub fn encode_refresh_token(email: &str) -> Result<String, StatusCode> {
let secret: String = env::var("REFRESH_TOKEN_SECRET")
.unwrap_or("this-is-secret".to_string())
.to_string();
pub fn encode_refresh_token(sub: TokenSub) -> Result<String, StatusCode> {
let env = Env::new();
let secret: String = env.refresh_token_secret;
let now = Utc::now();
let expire: TimeDelta = Duration::days(1);
let exp: usize = (now + expire).timestamp() as usize;
let iat: usize = now.timestamp() as usize;
let claim = Claims {
iat,
exp,
email: email.to_string(),
};
let claim = Claims { iat, exp, sub };
encode(
&Header::default(),
&claim,
@@ -73,9 +68,8 @@ pub fn encode_refresh_token(email: &str) -> Result<String, StatusCode> {
pub fn decode_refresh_token(
jwt_token: &str,
) -> Result<TokenData<Claims>, StatusCode> {
let secret = env::var("REFRESH_TOKEN_SECRET")
.unwrap_or("this-is-secret".to_string())
.to_string();
let env = Env::new();
let secret: String = env.refresh_token_secret;
let result: Result<TokenData<Claims>, StatusCode> = decode(
&jwt_token,
&DecodingKey::from_secret(secret.as_ref()),
+9 -10
View File
@@ -1,18 +1,20 @@
use super::Env;
use lettre::message::Mailbox;
use lettre::transport::smtp::authentication::Credentials;
use lettre::{Message, SmtpTransport, Transport};
use std::env;
use std::error::Error;
pub fn send_email(
to: &str,
subject: &str,
body: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let sender_email = env::var("SMTP_EMAIL")?.to_string();
let sender_name = env::var("SMTP_NAME")?.to_string();
let sender_password = env::var("SMTP_PASSWORD")?.to_string();
) -> Result<(), Box<dyn Error>> {
let env = Env::new();
let host = env.smpt_host;
let sender_email = env.smtp_email;
let sender_name = env.smtp_name;
let sender_password = env.smtp_password;
let recipient_email = to;
let email = Message::builder()
.from(Mailbox::new(
Some(sender_name.replace("-", " ")),
@@ -21,14 +23,11 @@ pub fn send_email(
.to(recipient_email.parse()?)
.subject(subject)
.body(body.to_string())?;
let smtp_credentials =
Credentials::new(sender_email, sender_password.replace("-", " "));
let mailer = SmtpTransport::relay("smtp.gmail.com")?
let mailer = SmtpTransport::relay(&host)?
.credentials(smtp_credentials)
.build();
match mailer.send(&email) {
Ok(_) => {
println!("Email sent successfully to {}", to);
+4 -2
View File
@@ -1,13 +1,15 @@
pub mod argon;
pub mod axum;
pub mod enviroment;
pub mod jsonwebtoken;
pub mod lettre;
pub mod redis;
pub mod redisdb;
pub mod surrealdb;
pub use argon::*;
pub use axum::*;
pub use enviroment::*;
pub use jsonwebtoken::*;
pub use lettre::*;
pub use redis::*;
pub use redisdb::*;
pub use surrealdb::*;
-16
View File
@@ -1,16 +0,0 @@
use redis::Client;
use redis::RedisResult;
use std::env;
pub async fn redisdb_init() -> RedisResult<Client> {
let host_name =
env::var("REDIS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
let uri_scheme = if env::var("IS_TLS").is_ok() {
"rediss"
} else {
"redis"
};
let url = format!("{}://{}", uri_scheme, host_name);
let client = Client::open(url)?;
Ok(client)
}
+11
View File
@@ -0,0 +1,11 @@
use super::Env;
use redis::Client;
use redis::RedisResult;
pub async fn redisdb_init() -> RedisResult<Client> {
let env = Env::new();
let host_name = env.redis_hostname;
let url = format!("redis://{}", host_name);
let client = Client::open(url)?;
Ok(client)
}
+8 -4
View File
@@ -1,11 +1,15 @@
use surrealdb::engine::remote::ws::{Client, Ws};
use surrealdb::{Result, Surreal};
use crate::SurrealClient;
use surrealdb::engine::remote::http::{Client, Http};
use surrealdb::{Result, Surreal};
pub async fn surrealdb_init() -> Result<SurrealClient> {
let db = Surreal::<Client>::init();
db.connect::<Ws>("ws://localhost:8000").await?;
db.connect::<Http>("localhost:8000").await?;
db.signin(surrealdb::opt::auth::Root {
username: "root",
password: "root",
})
.await?;
db.use_ns("test").use_db("test").await?;
Ok(db)
}