Working on access/refresh tokens
This commit is contained in:
@@ -6,7 +6,15 @@ DATABASE_NAME=siren
|
|||||||
DATABASE_HOST=localhost
|
DATABASE_HOST=localhost
|
||||||
DATABASE_PORT=5432
|
DATABASE_PORT=5432
|
||||||
|
|
||||||
JWT_SECRET=
|
ACCESS_TOKEN_PRIVATE_KEY=
|
||||||
|
ACCESS_TOKEN_PUBLIC_KEY=
|
||||||
|
ACCESS_TOKEN_EXPIRED_IN=15m
|
||||||
|
ACCESS_TOKEN_MAXAGE=15
|
||||||
|
|
||||||
|
REFRESH_TOKEN_PRIVATE_KEY=
|
||||||
|
REFRESH_TOKEN_PUBLIC_KEY=
|
||||||
|
REFRESH_TOKEN_EXPIRED_IN=60m
|
||||||
|
REFRESH_TOKEN_MAXAGE=60
|
||||||
|
|
||||||
REDIS_HOST=localhost
|
REDIS_HOST=localhost
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ lazy_static = "1.4.0"
|
|||||||
uuid = { version = "1.4.1", features = ["serde", "v4"] }
|
uuid = { version = "1.4.1", features = ["serde", "v4"] }
|
||||||
argon2 = "0.5.2"
|
argon2 = "0.5.2"
|
||||||
jsonwebtoken = "9.0.0"
|
jsonwebtoken = "9.0.0"
|
||||||
|
redis = { version = "0.23.3", features = ["tokio-comp", "connection-manager", "r2d2"] }
|
||||||
|
base64 = "0.21.4"
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
version = "1.32.0"
|
version = "1.32.0"
|
||||||
|
|||||||
@@ -1,71 +1,68 @@
|
|||||||
use std::env;
|
|
||||||
|
|
||||||
use actix_web::{dev::ServiceRequest, Error as ActixError};
|
|
||||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
|
||||||
use argon2::{password_hash::{rand_core::OsRng, PasswordHasher, PasswordVerifier, SaltString, Error as HashError}, Argon2, PasswordHash};
|
use argon2::{password_hash::{rand_core::OsRng, PasswordHasher, PasswordVerifier, SaltString, Error as HashError}, Argon2, PasswordHash};
|
||||||
|
use base64::{engine::general_purpose, Engine as _};
|
||||||
use jsonwebtoken::{DecodingKey, EncodingKey, Header, encode, decode, Validation, Algorithm};
|
use jsonwebtoken::{DecodingKey, EncodingKey, Header, encode, decode, Validation, Algorithm};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use siren::ServiceError;
|
|
||||||
|
|
||||||
mod model;
|
mod model;
|
||||||
mod routes;
|
mod routes;
|
||||||
|
|
||||||
pub use model::*;
|
pub use model::*;
|
||||||
pub use routes::init_routes;
|
pub use routes::init_routes;
|
||||||
|
use siren::ServiceError;
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
struct Claims {
|
struct TokenClaims {
|
||||||
sub: String,
|
sub: String, // Subject
|
||||||
exp: usize,
|
token_uuid: String, // Issuer
|
||||||
|
exp: i64, // Expiration time
|
||||||
|
iat: i64, // Issued At
|
||||||
|
nbf: i64 // Not Before
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/Sirneij/rust-auth/blob/main/backend/src/routes/users/login.rs
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
// https://dev.to/sirneij/authentication-system-using-rust-actix-web-and-sveltekit-user-registration-580h
|
pub struct TokenDetails {
|
||||||
// https://github.com/actix/actix-extras/blob/master/actix-session/examples/basic.rs
|
pub token: Option<String>,
|
||||||
// maybe https://github.com/actix/actix-extras/blob/master/actix-identity/examples/identity.rs
|
pub token_uuid: uuid::Uuid,
|
||||||
// https://www.lpalmieri.com/posts/session-based-authentication-in-rust/#3-3-1-postgres
|
pub email: String,
|
||||||
|
pub expires_in: Option<i64>
|
||||||
pub async fn validator(req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, (ActixError, ServiceRequest)> {
|
|
||||||
let token = credentials.token();
|
|
||||||
match validate_token(token) {
|
|
||||||
Ok(_) => Ok(req),
|
|
||||||
Err(err) => {
|
|
||||||
Err((ActixError::from(actix_web::error::ErrorUnauthorized(err)), req))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_token(token: &str) -> Result<(), ServiceError> {
|
// https://codevoweb.com/rust-actix-web-jwt-access-and-refresh-tokens/
|
||||||
let jwt_secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
|
// https://github.com/wpcodevo/rust-jwt-rs256/blob/master/src/main.rs
|
||||||
match decode::<Claims>(token, &DecodingKey::from_secret(jwt_secret.as_ref()), &Validation::new(Algorithm::HS256)) {
|
|
||||||
Ok(token_data) => {
|
pub fn verify_token(token: &str, public_key: &str) -> Result<TokenDetails, ServiceError> {
|
||||||
println!("{:?}", token_data.claims);
|
let bytes_public_key = general_purpose::STANDARD.decode(public_key).unwrap();
|
||||||
if token_data.claims.exp < chrono::Utc::now().timestamp() as usize {
|
let decoded_public_key = String::from_utf8(bytes_public_key).unwrap();
|
||||||
return Err(ServiceError {
|
let key = DecodingKey::from_rsa_pem(decoded_public_key.as_bytes())?;
|
||||||
status: 401,
|
let validation = Validation::new(Algorithm::RS256);
|
||||||
message: "Token expired".to_string()
|
let decoded = decode::<TokenClaims>(token, &key, &validation)?;
|
||||||
});
|
let email = decoded.claims.sub;
|
||||||
}
|
let token_uuid = uuid::Uuid::parse_str(decoded.claims.token_uuid.as_str()).unwrap();
|
||||||
Ok(())
|
Ok(TokenDetails { token: None, token_uuid, email, expires_in: None })
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
Err(ServiceError {
|
|
||||||
status: 401,
|
|
||||||
message: err.to_string()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_token(email: &str) -> String {
|
pub fn generate_token(email: &str, ttl: i64, private_key: &str) -> Result<TokenDetails, ServiceError> {
|
||||||
let jwt_secret = env::var("JWT_SECRET").expect("JWT_SECRET must be set");
|
let now = chrono::Utc::now();
|
||||||
let exp = chrono::Utc::now().checked_add_signed(chrono::Duration::seconds(3600)).expect("valid timestamp").timestamp();
|
let mut token_details = TokenDetails {
|
||||||
let claims = Claims {
|
token: None,
|
||||||
sub: email.to_owned(),
|
token_uuid: uuid::Uuid::new_v4(),
|
||||||
exp: exp as usize,
|
email: email.to_string(),
|
||||||
|
expires_in: Some((now + chrono::Duration::minutes(ttl)).timestamp())
|
||||||
};
|
};
|
||||||
let token = encode(&Header::default(), &claims, &EncodingKey::from_secret(jwt_secret.as_ref())).unwrap();
|
let claims = TokenClaims {
|
||||||
token
|
sub: token_details.email.clone(),
|
||||||
|
token_uuid: token_details.token_uuid.to_string(),
|
||||||
|
exp: token_details.expires_in.unwrap(),
|
||||||
|
iat: now.timestamp(),
|
||||||
|
nbf: now.timestamp()
|
||||||
|
};
|
||||||
|
let header = Header::new(Algorithm::RS256);
|
||||||
|
let bytes_private_key = general_purpose::STANDARD.decode(private_key).unwrap();
|
||||||
|
let decoded_private_key = String::from_utf8(bytes_private_key).unwrap();
|
||||||
|
let key = EncodingKey::from_rsa_pem(decoded_private_key.as_bytes())?;
|
||||||
|
let token = encode(&header, &claims, &key)?;
|
||||||
|
token_details.token = Some(token);
|
||||||
|
Ok(token_details)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hash_password(password: &[u8]) -> Result<String, HashError> {
|
pub fn hash_password(password: &[u8]) -> Result<String, HashError> {
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
|
use std::{future::{ready, Ready}, env};
|
||||||
|
use actix_web::{FromRequest, Error as ActixError, HttpRequest, dev::Payload, http};
|
||||||
use diesel::prelude::*;
|
use diesel::prelude::*;
|
||||||
|
use log::error;
|
||||||
|
use redis::Commands;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use siren::ServiceError;
|
use siren::ServiceError;
|
||||||
|
|
||||||
use crate::db::schema::users;
|
use crate::db::schema::users;
|
||||||
|
|
||||||
use super::hash_password;
|
use super::{hash_password, verify_token};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct RegisterUser {
|
pub struct RegisterUser {
|
||||||
@@ -33,11 +37,6 @@ pub struct LoginRequest {
|
|||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
pub struct LoginResponse {
|
|
||||||
pub token: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Queryable, QueryableByName, Serialize, Deserialize)]
|
#[derive(Debug, Queryable, QueryableByName, Serialize, Deserialize)]
|
||||||
#[diesel(table_name = users)]
|
#[diesel(table_name = users)]
|
||||||
pub struct QueryUser {
|
pub struct QueryUser {
|
||||||
@@ -79,3 +78,75 @@ impl InsertUser {
|
|||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct JwtAuth {
|
||||||
|
pub access_token_uuid: uuid::Uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromRequest for JwtAuth {
|
||||||
|
type Error = ActixError;
|
||||||
|
type Future = Ready<Result<Self, Self::Error>>;
|
||||||
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
|
let access_token = match req
|
||||||
|
.cookie("access_token")
|
||||||
|
.map(|c| c.value().to_string())
|
||||||
|
.or_else(|| {
|
||||||
|
req.headers().get(http::header::AUTHORIZATION)
|
||||||
|
.map(|h| h.to_str().unwrap().split_at(7).1.to_string())
|
||||||
|
}) {
|
||||||
|
Some(token) => token,
|
||||||
|
None => return ready(Err(ActixError::from(ServiceError {
|
||||||
|
status: 401,
|
||||||
|
message: "Unauthorized".to_string()
|
||||||
|
})))
|
||||||
|
};
|
||||||
|
|
||||||
|
let public_key = env::var("ACCESS_TOKEN_PUBLIC_KEY")
|
||||||
|
.expect("ACCESS_TOKEN_PUBLIC_KEY must be set");
|
||||||
|
|
||||||
|
let access_token_details = match verify_token(&access_token, &public_key) {
|
||||||
|
Ok(token_details) => token_details,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to verify access token: {}", err);
|
||||||
|
return ready(Err(ActixError::from(ServiceError {
|
||||||
|
status: 401,
|
||||||
|
message: format!("Failed to verify access token: {}", err)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let access_token_uuid = uuid::Uuid::parse_str(&access_token_details.token_uuid.to_string()).unwrap();
|
||||||
|
|
||||||
|
let mut conn = match crate::db::redis_connection() {
|
||||||
|
Ok(conn) => conn,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to get redis connection: {}", err);
|
||||||
|
return ready(Err(ActixError::from(ServiceError {
|
||||||
|
status: 500,
|
||||||
|
message: format!("Failed to get redis connection: {}", err)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let user_email = match conn.get::<_, String>(access_token_uuid.clone().to_string()) {
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to get access token from redis: {}", err);
|
||||||
|
return ready(Err(ActixError::from(ServiceError {
|
||||||
|
status: 500,
|
||||||
|
message: format!("Failed to get access token from redis: {}", err)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match QueryUser::get_by_email(&user_email) {
|
||||||
|
Ok(_) => {
|
||||||
|
ready(Ok(JwtAuth { access_token_uuid }))
|
||||||
|
}
|
||||||
|
Err(err) => return ready(Err(ActixError::from(ServiceError {
|
||||||
|
status: 500,
|
||||||
|
message: format!("Failed to get user from db: {}", err)
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use actix_web::{get, post, web, HttpResponse, ResponseError};
|
use std::env;
|
||||||
use actix_web_httpauth::middleware::HttpAuthentication;
|
|
||||||
|
use actix_web::{get, post, web, HttpResponse, ResponseError, cookie::{Cookie, time::Duration}, HttpRequest};
|
||||||
|
use log::error;
|
||||||
|
use redis::AsyncCommands;
|
||||||
use siren::ServiceError;
|
use siren::ServiceError;
|
||||||
|
|
||||||
use crate::auth::{LoginRequest, RegisterUser, InsertUser, QueryUser, verify_password, create_token, LoginResponse};
|
use crate::{auth::{LoginRequest, RegisterUser, InsertUser, QueryUser, verify_password, generate_token, JwtAuth}, db};
|
||||||
|
|
||||||
use super::validator;
|
|
||||||
|
|
||||||
#[post("/register")]
|
#[post("/register")]
|
||||||
async fn register(user: web::Json<RegisterUser>) -> HttpResponse {
|
async fn register(user: web::Json<RegisterUser>) -> HttpResponse {
|
||||||
@@ -40,8 +41,80 @@ async fn login(request: web::Json<LoginRequest>) -> HttpResponse {
|
|||||||
let password = request.password.as_bytes();
|
let password = request.password.as_bytes();
|
||||||
match verify_password(&hash, password) {
|
match verify_password(&hash, password) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
let token = create_token(&email);
|
let access_token_max_age = env::var("ACCESS_TOKEN_MAXAGE")
|
||||||
HttpResponse::Ok().json(LoginResponse { token })
|
.expect("ACCESS_TOKEN_MAXAGE must be set")
|
||||||
|
.parse::<i64>()
|
||||||
|
.expect("ACCESS_TOKEN_MAXAGE must be an integer");
|
||||||
|
let access_private_key = env::var("ACCESS_TOKEN_PRIVATE_KEY")
|
||||||
|
.expect("ACCESS_TOKEN_PRIVATE_KEY must be set");
|
||||||
|
let access_token_details = match generate_token(&email, access_token_max_age, &access_private_key) {
|
||||||
|
Ok(token_details) => token_details,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to generate access token: {}", err);
|
||||||
|
return ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let refresh_token_max_age = env::var("REFRESH_TOKEN_MAXAGE")
|
||||||
|
.expect("REFRESH_TOKEN_MAXAGE must be set")
|
||||||
|
.parse::<i64>()
|
||||||
|
.expect("REFRESH_TOKEN_MAXAGE must be an integer");
|
||||||
|
let refresh_private_key = env::var("REFRESH_TOKEN_PRIVATE_KEY")
|
||||||
|
.expect("REFRESH_TOKEN_PRIVATE_KEY must be set");
|
||||||
|
let refresh_token_details = match generate_token(&email, refresh_token_max_age, &refresh_private_key) {
|
||||||
|
Ok(token_details) => token_details,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to generate refresh token: {}", err);
|
||||||
|
return ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut conn = match db::redis_async_connection().await {
|
||||||
|
Ok(conn) => conn,
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to get redis connection: {}", err);
|
||||||
|
return ResponseError::error_response(&err)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let access_result: redis::RedisResult<()> = conn.set_ex(access_token_details.token_uuid.to_string(), &email, (access_token_max_age * 60) as usize).await;
|
||||||
|
if let Err(err) = access_result {
|
||||||
|
error!("Failed to set access token in redis: {}", err);
|
||||||
|
return ResponseError::error_response(&ServiceError {
|
||||||
|
status: 500,
|
||||||
|
message: format!("Failed to set access token in redis: {}", err)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let refresh_result: redis::RedisResult<()> = conn.set_ex(refresh_token_details.token_uuid.to_string(), &email, (refresh_token_max_age * 60) as usize).await;
|
||||||
|
if let Err(err) = refresh_result {
|
||||||
|
error!("Failed to set refresh token in redis: {}", err);
|
||||||
|
return ResponseError::error_response(&ServiceError {
|
||||||
|
status: 500,
|
||||||
|
message: format!("Failed to set refresh token in redis: {}", err)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let access_cookie = Cookie::build("access_token", access_token_details.token.clone().unwrap())
|
||||||
|
.path("/")
|
||||||
|
.max_age(Duration::new(access_token_max_age, 0))
|
||||||
|
.http_only(true)
|
||||||
|
.finish();
|
||||||
|
let refresh_cookie = Cookie::build("refresh_token", refresh_token_details.token.clone().unwrap())
|
||||||
|
.path("/")
|
||||||
|
.max_age(Duration::new(refresh_token_max_age, 0))
|
||||||
|
.http_only(true)
|
||||||
|
.finish();
|
||||||
|
let logged_in_cookie = Cookie::build("logged_in", "true")
|
||||||
|
.path("/")
|
||||||
|
.max_age(Duration::new(access_token_max_age, 0))
|
||||||
|
.http_only(false)
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.cookie(access_cookie)
|
||||||
|
.cookie(refresh_cookie)
|
||||||
|
.cookie(logged_in_cookie)
|
||||||
|
.json(access_token_details.token.unwrap())
|
||||||
},
|
},
|
||||||
Err(err) => ResponseError::error_response(&ServiceError {
|
Err(err) => ResponseError::error_response(&ServiceError {
|
||||||
status: 401,
|
status: 401,
|
||||||
@@ -50,24 +123,27 @@ async fn login(request: web::Json<LoginRequest>) -> HttpResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/logout")]
|
#[get("/refresh")]
|
||||||
async fn logout() -> HttpResponse {
|
async fn refresh(req: HttpRequest) -> HttpResponse {
|
||||||
HttpResponse::Ok().finish()
|
HttpResponse::Ok().finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/ping")]
|
#[post("/logout")]
|
||||||
async fn ping() -> HttpResponse {
|
async fn logout(req: HttpRequest, auth: JwtAuth) -> HttpResponse {
|
||||||
HttpResponse::Ok().finish()
|
HttpResponse::Ok().finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/me")]
|
||||||
|
async fn me(auth: JwtAuth) -> HttpResponse {
|
||||||
|
HttpResponse::Ok().json(auth)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init_routes(config: &mut web::ServiceConfig) {
|
pub fn init_routes(config: &mut web::ServiceConfig) {
|
||||||
let auth = HttpAuthentication::bearer(validator);
|
|
||||||
config.service(web::scope("auth")
|
config.service(web::scope("auth")
|
||||||
.service(register)
|
.service(register)
|
||||||
.service(login)
|
.service(login)
|
||||||
.service(web::scope("")
|
.service(refresh)
|
||||||
.wrap(auth)
|
|
||||||
.service(logout)
|
.service(logout)
|
||||||
.service(ping)
|
.service(me)
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
use diesel::{r2d2::ConnectionManager, PgConnection};
|
use diesel::{r2d2::ConnectionManager as DieselConnectionManager, PgConnection};
|
||||||
|
// use redis::{aio::{Connection as RedisConnection, ConnectionManager as RedisConnectionManager}, AsyncCommands};
|
||||||
|
use redis::aio::Connection as RedisConnection;
|
||||||
use siren::ServiceError;
|
use siren::ServiceError;
|
||||||
use crate::diesel_migrations::MigrationHarness;
|
use crate::diesel_migrations::MigrationHarness;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
@@ -19,22 +21,31 @@ pub mod races;
|
|||||||
pub mod spells;
|
pub mod spells;
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
|
|
||||||
type Pool = r2d2::Pool<ConnectionManager<PgConnection>>;
|
type DbPool = r2d2::Pool<DieselConnectionManager<PgConnection>>;
|
||||||
pub type DbConnection = r2d2::PooledConnection<ConnectionManager<PgConnection>>;
|
pub type DbConnection = r2d2::PooledConnection<DieselConnectionManager<PgConnection>>;
|
||||||
|
// type RedisPool = r2d2::Pool<redis::ConnectionManager>;
|
||||||
|
|
||||||
pub const MIGRATIONS: diesel_migrations::EmbeddedMigrations = embed_migrations!();
|
pub const MIGRATIONS: diesel_migrations::EmbeddedMigrations = embed_migrations!();
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref POOL: Pool = {
|
static ref POOL: DbPool = {
|
||||||
let username = env::var("DATABASE_USER").expect("DATABASE_USERNAME is not set");
|
let username = env::var("DATABASE_USER").expect("DATABASE_USERNAME is not set");
|
||||||
let password = env::var("DATABASE_PASSWORD").expect("DATABASE_PASSWORD is not set");
|
let password = env::var("DATABASE_PASSWORD").expect("DATABASE_PASSWORD is not set");
|
||||||
let host = env::var("DATABASE_HOST").unwrap_or("localhost".to_string());
|
let host = env::var("DATABASE_HOST").unwrap_or("localhost".to_string());
|
||||||
let name = env::var("DATABASE_NAME").expect("DATABASE_NAME is not set");
|
let name = env::var("DATABASE_NAME").expect("DATABASE_NAME is not set");
|
||||||
let port = env::var("DATABASE_PORT").unwrap_or("5432".to_string());
|
let port = env::var("DATABASE_PORT").unwrap_or("5432".to_string());
|
||||||
let url = format!("postgres://{}:{}@{}:{}/{}", username, password, host, port, name);
|
let url = format!("postgres://{}:{}@{}:{}/{}", username, password, host, port, name);
|
||||||
let manager = ConnectionManager::<PgConnection>::new(url);
|
let manager = DieselConnectionManager::<PgConnection>::new(url);
|
||||||
Pool::builder().test_on_check_out(true).build(manager).expect("Failed to create db pool")
|
DbPool::builder().test_on_check_out(true).build(manager).expect("Failed to create db pool")
|
||||||
};
|
};
|
||||||
|
// static ref REDIS_POOL: RedisPool = {
|
||||||
|
// let host = env::var("REDIS_HOST").unwrap_or("localhost".to_string());
|
||||||
|
// let port = env::var("REDIS_PORT").unwrap_or("6379".to_string());
|
||||||
|
// let url = format!("redis://{}:{}", host, port);
|
||||||
|
// let client = redis::Client::open(url).expect("Failed to create redis client");
|
||||||
|
// let manager = RedisConnectionManager::new(client);
|
||||||
|
// "".to_string()
|
||||||
|
// };
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
@@ -51,6 +62,26 @@ pub fn connection() -> Result<DbConnection, ServiceError> {
|
|||||||
.map_err(|e| ServiceError::new(500, format!("Failed getting db connection: {}", e)))
|
.map_err(|e| ServiceError::new(500, format!("Failed getting db connection: {}", e)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn redis_client() -> Result<redis::Client, ServiceError> {
|
||||||
|
let host = env::var("REDIS_HOST").unwrap_or("localhost".to_string());
|
||||||
|
let port = env::var("REDIS_PORT").unwrap_or("6379".to_string());
|
||||||
|
let url = format!("redis://{}:{}", host, port);
|
||||||
|
let client = redis::Client::open(url)?;
|
||||||
|
Ok(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn redis_connection() -> Result<redis::Connection, ServiceError> {
|
||||||
|
let client = redis_client()?;
|
||||||
|
let conn = client.get_connection()?;
|
||||||
|
Ok(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn redis_async_connection() -> Result<RedisConnection, ServiceError> {
|
||||||
|
let client = redis_client()?;
|
||||||
|
let conn = client.get_async_connection().await?;
|
||||||
|
Ok(conn)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_data(data_dir_path: &str) {
|
pub fn load_data(data_dir_path: &str) {
|
||||||
spells::load_data(data_dir_path);
|
spells::load_data(data_dir_path);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,6 +100,18 @@ impl From<argon2::password_hash::Error> for ServiceError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<jsonwebtoken::errors::Error> for ServiceError {
|
||||||
|
fn from(error: jsonwebtoken::errors::Error) -> ServiceError {
|
||||||
|
ServiceError::new(500, format!("Unknown jsonwebtoken error: {}", error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<redis::RedisError> for ServiceError {
|
||||||
|
fn from(error: redis::RedisError) -> ServiceError {
|
||||||
|
ServiceError::new(500, format!("Unknown redis error: {}", error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ResponseError for ServiceError {
|
impl ResponseError for ServiceError {
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
let status_code = match StatusCode::from_u16(self.status) {
|
let status_code = match StatusCode::from_u16(self.status) {
|
||||||
|
|||||||
Reference in New Issue
Block a user