feat: refresh token system

This commit is contained in:
Ahmet Kaan GÜMÜŞ 2025-01-14 00:59:21 +03:00
parent 87412ca828
commit cf1107d09c
7 changed files with 85 additions and 63 deletions

6
.env
View file

@ -1,3 +1,3 @@
# This is for sqlx to do compile time sql checking # This is for sqlx to do compile time SQL checking.
# Actual server and database configs are in config folder # Actual server and database configs are in "configs" folder which is in main directory by default.
DATABASE_URL=postgres://root:root@localhost:5432/rust_forum DATABASE_URL=postgres://root:root@localhost:5432/rust_forum

23
.gitignore vendored
View file

@ -1,27 +1,4 @@
.vscode/ .vscode/
# Generated by Cargo
# will have compiled files and executables
debug/ debug/
target/ target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk
# MSVC Windows builds of rustc generate these, which store debugging information
*.pdb
# RustRover
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Added by cargo
/target

View file

@ -1,5 +1,6 @@
[server_config] [server_config]
address = "localhost:2344" address = "localhost:2344"
otp_time_limit = 15 otp_time_limit = 15
login_token_time_limit = 15 login_token_expiration_time_limit = 15
concurrency_limit = -1 login_token_refresh_time_limit = 30
concurrency_limit = -1

View file

@ -1,6 +1,7 @@
-- Add up migration script here -- Add up migration script here
CREATE TABLE IF NOT EXISTS "login"( CREATE TABLE IF NOT EXISTS "login" (
user_id BIGSERIAL NOT NULL REFERENCES "user"(id), user_id BIGSERIAL NOT NULL REFERENCES "user" (id),
token VARCHAR(1024) NOT NULL, token VARCHAR(1024) NOT NULL,
token_creation_time TIMESTAMPTZ NOT NULL DEFAULT NOW (),
PRIMARY KEY (user_id, token) PRIMARY KEY (user_id, token)
); );

View file

@ -10,8 +10,8 @@ pub async fn create(
sqlx::query_as!( sqlx::query_as!(
Login, Login,
r#" r#"
INSERT INTO "login"(user_id, token) INSERT INTO "login"(user_id, token)
VALUES ($1, $2) VALUES ($1, $2)
RETURNING * RETURNING *
"#, "#,
user_id, user_id,
@ -38,24 +38,6 @@ pub async fn read(
.await .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( pub async fn delete(
user_id: &i64, user_id: &i64,
token: &String, token: &String,

View file

@ -1,3 +1,6 @@
use std::sync::LazyLock;
use chrono::{DateTime, Utc};
use jwt_simple::{ use jwt_simple::{
claims::Claims, claims::Claims,
common::VerificationOptions, common::VerificationOptions,
@ -8,10 +11,54 @@ use sqlx::{Pool, Postgres};
use crate::{database::login, SERVER_CONFIG}; use crate::{database::login, SERVER_CONFIG};
static TOKEN_META: LazyLock<TokenMeta> = LazyLock::new(TokenMeta::init);
struct TokenMeta {
token_key: HS256Key,
token_verification_options: Option<VerificationOptions>,
}
impl TokenMeta {
fn init() -> Self {
Self {
token_key: HS256Key::generate(),
token_verification_options: {
let mut verification_options = VerificationOptions::default();
verification_options.time_tolerance = Some(jwt_simple::prelude::Duration::from(0));
Some(verification_options)
},
}
}
async fn create_token() -> Option<String> {
let key = &TOKEN_META.token_key;
let claims = Claims::create(jwt_simple::prelude::Duration::from_mins(
SERVER_CONFIG.login_token_expiration_time_limit as u64,
));
let token = key.authenticate(claims).unwrap();
match TokenMeta::verify_token(&token).await {
true => Some(token),
false => None,
}
}
async fn verify_token(token: &String) -> bool {
let token_meta = &TOKEN_META;
token_meta
.token_key
.verify_token::<jwt_simple::prelude::NoCustomClaims>(
token,
token_meta.token_verification_options.clone(),
)
.is_ok()
}
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Login { pub struct Login {
pub user_id: i64, pub user_id: i64,
pub token: String, pub token: String,
pub token_creation_time: DateTime<Utc>,
} }
impl Login { impl Login {
@ -19,15 +66,9 @@ impl Login {
user_id: &i64, user_id: &i64,
database_connection: &Pool<Postgres>, database_connection: &Pool<Postgres>,
) -> Result<Login, sqlx::Error> { ) -> Result<Login, sqlx::Error> {
let key = HS256Key::generate(); let token = TokenMeta::create_token()
let claims = Claims::create(jwt_simple::prelude::Duration::from_mins( .await
SERVER_CONFIG.login_token_time_limit as u64, .expect("Should not panic if it isn't configured wrong");
));
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 login::create(user_id, &token, database_connection).await
} }
@ -44,7 +85,23 @@ impl Login {
token: &String, token: &String,
database_connection: &Pool<Postgres>, database_connection: &Pool<Postgres>,
) -> Result<Login, sqlx::Error> { ) -> Result<Login, sqlx::Error> {
login::update(user_id, token, database_connection).await let login = Login::read(user_id, token, database_connection).await?;
match TokenMeta::verify_token(token).await {
true => Ok(login),
false => {
if DateTime::<Utc>::default()
.signed_duration_since(&login.token_creation_time)
.num_minutes()
<= SERVER_CONFIG.login_token_refresh_time_limit as i64
{
Login::delete(user_id, token, database_connection).await?;
Login::create(user_id, database_connection).await
} else {
Ok(login)
}
}
}
} }
pub async fn delete( pub async fn delete(
user_id: &i64, user_id: &i64,

View file

@ -52,7 +52,8 @@ impl Default for DatabaseConfig {
pub struct ServerConfig { pub struct ServerConfig {
pub address: String, pub address: String,
pub otp_time_limit: usize, pub otp_time_limit: usize,
pub login_token_time_limit: usize, pub login_token_expiration_time_limit: usize,
pub login_token_refresh_time_limit: usize,
pub concurrency_limit: usize, pub concurrency_limit: usize,
} }
@ -65,7 +66,10 @@ impl Default for ServerConfig {
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()), otp_time_limit: value_or_max(server_configs.pop_front().unwrap()),
login_token_time_limit: value_or_max(server_configs.pop_front().unwrap()), login_token_expiration_time_limit: value_or_max(
server_configs.pop_front().unwrap(),
),
login_token_refresh_time_limit: value_or_max(server_configs.pop_front().unwrap()),
concurrency_limit: value_or_max(server_configs.pop_front().unwrap()), concurrency_limit: value_or_max(server_configs.pop_front().unwrap()),
} }
} else { } else {