feat: ✨ refresh token system
This commit is contained in:
parent
87412ca828
commit
cf1107d09c
7 changed files with 85 additions and 63 deletions
6
.env
6
.env
|
@ -1,3 +1,3 @@
|
|||
# This is for sqlx to do compile time sql checking
|
||||
# Actual server and database configs are in config folder
|
||||
DATABASE_URL=postgres://root:root@localhost:5432/rust_forum
|
||||
# This is for sqlx to do compile time SQL checking.
|
||||
# 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
|
||||
|
|
23
.gitignore
vendored
23
.gitignore
vendored
|
@ -1,27 +1,4 @@
|
|||
.vscode/
|
||||
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
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
|
||||
|
||||
# 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
|
|
@ -1,5 +1,6 @@
|
|||
[server_config]
|
||||
address = "localhost:2344"
|
||||
otp_time_limit = 15
|
||||
login_token_time_limit = 15
|
||||
concurrency_limit = -1
|
||||
login_token_expiration_time_limit = 15
|
||||
login_token_refresh_time_limit = 30
|
||||
concurrency_limit = -1
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
-- Add up migration script here
|
||||
CREATE TABLE IF NOT EXISTS "login"(
|
||||
user_id BIGSERIAL NOT NULL REFERENCES "user"(id),
|
||||
CREATE TABLE IF NOT EXISTS "login" (
|
||||
user_id BIGSERIAL NOT NULL REFERENCES "user" (id),
|
||||
token VARCHAR(1024) NOT NULL,
|
||||
token_creation_time TIMESTAMPTZ NOT NULL DEFAULT NOW (),
|
||||
PRIMARY KEY (user_id, token)
|
||||
);
|
||||
);
|
||||
|
|
|
@ -10,8 +10,8 @@ pub async fn create(
|
|||
sqlx::query_as!(
|
||||
Login,
|
||||
r#"
|
||||
INSERT INTO "login"(user_id, token)
|
||||
VALUES ($1, $2)
|
||||
INSERT INTO "login"(user_id, token)
|
||||
VALUES ($1, $2)
|
||||
RETURNING *
|
||||
"#,
|
||||
user_id,
|
||||
|
@ -38,24 +38,6 @@ pub async fn read(
|
|||
.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,
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
use std::sync::LazyLock;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use jwt_simple::{
|
||||
claims::Claims,
|
||||
common::VerificationOptions,
|
||||
|
@ -8,10 +11,54 @@ use sqlx::{Pool, Postgres};
|
|||
|
||||
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)]
|
||||
pub struct Login {
|
||||
pub user_id: i64,
|
||||
pub token: String,
|
||||
pub token_creation_time: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Login {
|
||||
|
@ -19,15 +66,9 @@ impl Login {
|
|||
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();
|
||||
|
||||
let token = TokenMeta::create_token()
|
||||
.await
|
||||
.expect("Should not panic if it isn't configured wrong");
|
||||
login::create(user_id, &token, database_connection).await
|
||||
}
|
||||
|
||||
|
@ -44,7 +85,23 @@ impl Login {
|
|||
token: &String,
|
||||
database_connection: &Pool<Postgres>,
|
||||
) -> 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(
|
||||
user_id: &i64,
|
||||
|
|
|
@ -52,7 +52,8 @@ impl Default for DatabaseConfig {
|
|||
pub struct ServerConfig {
|
||||
pub address: String,
|
||||
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,
|
||||
}
|
||||
|
||||
|
@ -65,7 +66,10 @@ impl Default for ServerConfig {
|
|||
Self {
|
||||
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()),
|
||||
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()),
|
||||
}
|
||||
} else {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue