Create session objects during login, also store auth token for users
This commit is contained in:
parent
e24cf5c0b8
commit
6057c8295d
14 changed files with 230 additions and 95 deletions
|
@ -35,7 +35,7 @@ serde_json = "1.0"
|
||||||
serde_with = "3.9"
|
serde_with = "3.9"
|
||||||
sqlx = { version = "0.8", features = [
|
sqlx = { version = "0.8", features = [
|
||||||
"default",
|
"default",
|
||||||
"chrono",
|
"time",
|
||||||
"postgres",
|
"postgres",
|
||||||
"runtime-tokio",
|
"runtime-tokio",
|
||||||
] }
|
] }
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
DROP INDEX IF EXISTS status_name_uniq_idx;
|
DROP INDEX IF EXISTS status_name_uniq_idx;
|
||||||
|
DROP INDEX IF EXISTS user_auth_token_hash_uniq_idx;
|
||||||
DROP INDEX IF EXISTS user_username_uniq_idx;
|
DROP INDEX IF EXISTS user_username_uniq_idx;
|
||||||
DROP INDEX IF EXISTS user_email_uniq_idx;
|
DROP INDEX IF EXISTS user_email_uniq_idx;
|
||||||
DROP INDEX IF EXISTS permission_name_uniq_idx;
|
DROP INDEX IF EXISTS permission_name_uniq_idx;
|
||||||
|
|
||||||
DROP TABLE IF EXISTS public.user_permission;
|
DROP TABLE IF EXISTS public.user_permission;
|
||||||
DROP TABLE IF EXISTS public.permission;
|
DROP TABLE IF EXISTS public.permission;
|
||||||
|
DROP TABLE IF EXISTS public.user_auth_token;
|
||||||
DROP TABLE IF EXISTS public.user;
|
DROP TABLE IF EXISTS public.user;
|
||||||
DROP TABLE IF EXISTS public.status CASCADE;
|
DROP TABLE IF EXISTS public.status CASCADE;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
CREATE TABLE IF NOT EXISTS
|
CREATE TABLE IF NOT EXISTS
|
||||||
public.status (
|
public.status (
|
||||||
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||||
name VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||||
updated_at TIMESTAMP WITH TIME ZONE NULL
|
updated_at TIMESTAMP WITH TIME ZONE NULL
|
||||||
|
@ -20,7 +20,7 @@ VALUES
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS
|
CREATE TABLE IF NOT EXISTS
|
||||||
public.user (
|
public.user (
|
||||||
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||||
username VARCHAR(255) NOT NULL,
|
username VARCHAR(255) NOT NULL,
|
||||||
password VARCHAR(255) NOT NULL,
|
password VARCHAR(255) NOT NULL,
|
||||||
email VARCHAR(255) NOT NULL,
|
email VARCHAR(255) NOT NULL,
|
||||||
|
@ -33,9 +33,20 @@ CREATE TABLE IF NOT EXISTS
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS user_username_uniq_idx ON public.user(username);
|
CREATE UNIQUE INDEX IF NOT EXISTS user_username_uniq_idx ON public.user(username);
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS user_email_uniq_idx ON public.user(email);
|
CREATE UNIQUE INDEX IF NOT EXISTS user_email_uniq_idx ON public.user(email);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS
|
||||||
|
public.user_auth_token (
|
||||||
|
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL REFERENCES public.user(id),
|
||||||
|
token_hash VARCHAR(256) NOT NULL,
|
||||||
|
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS user_auth_token_hash_uniq_idx ON public.user_auth_token(token_hash);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS
|
CREATE TABLE IF NOT EXISTS
|
||||||
public.permission (
|
public.permission (
|
||||||
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||||
name VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
status_id INT NOT NULL REFERENCES status(id) DEFAULT 1,
|
status_id INT NOT NULL REFERENCES status(id) DEFAULT 1,
|
||||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||||
|
@ -46,7 +57,7 @@ CREATE UNIQUE INDEX IF NOT EXISTS permission_name_uniq_idx ON public.permission(
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS
|
CREATE TABLE IF NOT EXISTS
|
||||||
public.user_permission (
|
public.user_permission (
|
||||||
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||||
permission_id INT NOT NULL REFERENCES permission(id),
|
permission_id INT NOT NULL REFERENCES permission(id),
|
||||||
user_id INT NOT NULL REFERENCES public.user(id),
|
user_id INT NOT NULL REFERENCES public.user(id),
|
||||||
status_id INT NOT NULL REFERENCES status(id)
|
status_id INT NOT NULL REFERENCES status(id)
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
use std::fmt::Debug;
|
use std::{
|
||||||
|
fmt::Debug,
|
||||||
|
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||||
|
};
|
||||||
|
|
||||||
|
use blake3::Hash;
|
||||||
|
use serde::Serialize;
|
||||||
use sqlx::prelude::FromRow;
|
use sqlx::prelude::FromRow;
|
||||||
|
use time::OffsetDateTime;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::models::AppError;
|
use crate::models::AppError;
|
||||||
|
@ -60,19 +66,17 @@ pub async fn insert_new_user(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, FromRow)]
|
#[derive(Debug, FromRow)]
|
||||||
pub struct UserAndHashedPassword {
|
pub struct UserIdAndHashedPasswordEntity {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub username: String,
|
|
||||||
pub name: String,
|
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_username_and_password_by_username(
|
pub async fn get_username_and_password_by_username(
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
username: String,
|
username: String,
|
||||||
) -> Result<UserAndHashedPassword, AppError> {
|
) -> Result<UserIdAndHashedPasswordEntity, AppError> {
|
||||||
sqlx::query_as::<_, UserAndHashedPassword>(
|
sqlx::query_as::<_, UserIdAndHashedPasswordEntity>(
|
||||||
"SELECT id, username, name, password FROM public.user WHERE username = $1;",
|
"SELECT id, password FROM public.user WHERE username = $1;",
|
||||||
)
|
)
|
||||||
.bind(username)
|
.bind(username)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
|
@ -94,3 +98,75 @@ pub async fn verify_user(pool: &DbPool, user_id: i32) -> Result<(), AppError> {
|
||||||
})
|
})
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct NewUserAuthTokenEntity {
|
||||||
|
pub user_id: i32,
|
||||||
|
pub token_hash: Hash,
|
||||||
|
pub expires_at: SystemTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromRow)]
|
||||||
|
pub struct TmpUserAuthTokenEntity {
|
||||||
|
pub id: i32,
|
||||||
|
pub user_id: i32,
|
||||||
|
pub token_hash: String,
|
||||||
|
pub expires_at: OffsetDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
pub struct UserAuthTokenEntity {
|
||||||
|
pub id: i32,
|
||||||
|
pub user_id: i32,
|
||||||
|
pub token_hash: String,
|
||||||
|
pub expires_at: SystemTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<TmpUserAuthTokenEntity> for UserAuthTokenEntity {
|
||||||
|
fn from(other: TmpUserAuthTokenEntity) -> Self {
|
||||||
|
let TmpUserAuthTokenEntity {
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
token_hash,
|
||||||
|
expires_at,
|
||||||
|
} = other;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
id,
|
||||||
|
user_id,
|
||||||
|
token_hash,
|
||||||
|
expires_at: UNIX_EPOCH
|
||||||
|
.checked_add(Duration::new(
|
||||||
|
expires_at.clone().unix_timestamp().try_into().unwrap(),
|
||||||
|
expires_at.nanosecond(),
|
||||||
|
))
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn insert_new_user_auth_token(
|
||||||
|
pool: &DbPool,
|
||||||
|
new_user_auth_token: NewUserAuthTokenEntity,
|
||||||
|
) -> Result<UserAuthTokenEntity, AppError> {
|
||||||
|
let NewUserAuthTokenEntity {
|
||||||
|
user_id,
|
||||||
|
token_hash,
|
||||||
|
expires_at,
|
||||||
|
} = new_user_auth_token.clone();
|
||||||
|
|
||||||
|
let token_hash = token_hash.to_string();
|
||||||
|
let expires_at = OffsetDateTime::from(expires_at);
|
||||||
|
|
||||||
|
sqlx::query_as::<_, TmpUserAuthTokenEntity>("INSERT INTO public.user_auth_token (user_id, token_hash, expires_at) VALUES ($1, $2, $3) RETURNING id, user_id, token_hash, expires_at;")
|
||||||
|
.bind(user_id)
|
||||||
|
.bind(token_hash)
|
||||||
|
.bind(expires_at)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
error!(?err, record = ?new_user_auth_token, "Cannot insert new user auth token record");
|
||||||
|
AppError::from(err)
|
||||||
|
})
|
||||||
|
.map(From::from)
|
||||||
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ impl AppError {
|
||||||
Self::new(ErrorKind::MissingEnvironmentVariables(missing_vars))
|
Self::new(ErrorKind::MissingEnvironmentVariables(missing_vars))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _missing_session_field(field: &'static str) -> Self {
|
pub fn missing_session_field(field: &'static str) -> Self {
|
||||||
Self::new(ErrorKind::MissingSessionField(field))
|
Self::new(ErrorKind::MissingSessionField(field))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ use redis::ToRedisArgs;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Session {
|
pub struct Session {
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
pub username: String,
|
|
||||||
pub created_at: SystemTime,
|
pub created_at: SystemTime,
|
||||||
pub expires_at: SystemTime,
|
pub expires_at: SystemTime,
|
||||||
}
|
}
|
||||||
|
@ -17,14 +16,12 @@ impl Session {
|
||||||
) -> Vec<(&'static str, impl ToRedisArgs + Send + Sync + 'a)> {
|
) -> Vec<(&'static str, impl ToRedisArgs + Send + Sync + 'a)> {
|
||||||
let Self {
|
let Self {
|
||||||
user_id,
|
user_id,
|
||||||
username,
|
|
||||||
created_at,
|
created_at,
|
||||||
expires_at,
|
expires_at,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
vec![
|
vec![
|
||||||
("userId", user_id.to_string()),
|
("userId", user_id.to_string()),
|
||||||
("username", username),
|
|
||||||
("createdAt", format_rfc3339(created_at).to_string()),
|
("createdAt", format_rfc3339(created_at).to_string()),
|
||||||
("expiresAt", format_rfc3339(expires_at).to_string()),
|
("expiresAt", format_rfc3339(expires_at).to_string()),
|
||||||
]
|
]
|
||||||
|
@ -35,7 +32,6 @@ impl Default for Session {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
user_id: Default::default(),
|
user_id: Default::default(),
|
||||||
username: Default::default(),
|
|
||||||
created_at: UNIX_EPOCH,
|
created_at: UNIX_EPOCH,
|
||||||
expires_at: UNIX_EPOCH,
|
expires_at: UNIX_EPOCH,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
debug_handler,
|
debug_handler,
|
||||||
extract::State,
|
extract::State,
|
||||||
|
@ -9,15 +11,18 @@ use pasetors::{keys::SymmetricKey, version4::V4};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{get_username_and_password_by_username, DbPool, UserAndHashedPassword},
|
db::{
|
||||||
models::{ApiResponse, AppError},
|
get_username_and_password_by_username, insert_new_user_auth_token, DbPool,
|
||||||
|
NewUserAuthTokenEntity, UserIdAndHashedPasswordEntity,
|
||||||
|
},
|
||||||
|
models::{ApiResponse, AppError, Session},
|
||||||
requests::{
|
requests::{
|
||||||
auth::login::models::{AuthLoginResponse, AuthLoginTokenData},
|
auth::login::models::{AuthLoginResponse, AuthLoginTokenData},
|
||||||
AppState,
|
AppState,
|
||||||
},
|
},
|
||||||
services::{
|
services::{
|
||||||
auth_token::{generate_access_token, generate_auth_token},
|
auth_token::{generate_auth_token, generate_session_token},
|
||||||
verify_password,
|
user_session, verify_password, CachePool,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,41 +33,62 @@ pub async fn auth_login_post_handler(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(body): Json<AuthLoginRequest>,
|
Json(body): Json<AuthLoginRequest>,
|
||||||
) -> Result<Response, AppError> {
|
) -> Result<Response, AppError> {
|
||||||
let pool = state.db_pool();
|
let db_pool = state.db_pool();
|
||||||
|
let cache_pool = state.cache_pool();
|
||||||
let token_key = state.env().token_key();
|
let token_key = state.env().token_key();
|
||||||
auth_login_request(pool, token_key, body).await
|
auth_login_request(db_pool, cache_pool, token_key, body).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn auth_login_request(
|
async fn auth_login_request(
|
||||||
pool: &DbPool,
|
db_pool: &DbPool,
|
||||||
|
cache_pool: &CachePool,
|
||||||
token_key: &SymmetricKey<V4>,
|
token_key: &SymmetricKey<V4>,
|
||||||
body: AuthLoginRequest,
|
body: AuthLoginRequest,
|
||||||
) -> Result<Response, AppError> {
|
) -> Result<Response, AppError> {
|
||||||
debug!(?body);
|
debug!(?body);
|
||||||
|
|
||||||
let AuthLoginRequest { username, password } = body;
|
let AuthLoginRequest { username, password } = body;
|
||||||
let UserAndHashedPassword {
|
let UserIdAndHashedPasswordEntity {
|
||||||
id: user_id,
|
id: user_id,
|
||||||
username,
|
|
||||||
name,
|
|
||||||
password: hashed_password,
|
password: hashed_password,
|
||||||
} = get_username_and_password_by_username(pool, username).await?;
|
} = get_username_and_password_by_username(db_pool, username).await?;
|
||||||
|
|
||||||
verify_password(password, hashed_password)?;
|
verify_password(password, hashed_password)?;
|
||||||
|
|
||||||
let (access_token, _access_token_id, access_token_expiration) =
|
let (session_token, session_token_id, session_token_expiration) =
|
||||||
generate_access_token(token_key, user_id);
|
generate_session_token(token_key, user_id);
|
||||||
|
|
||||||
let (auth_token, _auth_token_id, auth_token_expiration) =
|
let (auth_token, auth_token_id, auth_token_expiration) =
|
||||||
generate_auth_token(token_key, user_id);
|
generate_auth_token(token_key, user_id);
|
||||||
|
|
||||||
|
insert_new_user_auth_token(
|
||||||
|
db_pool,
|
||||||
|
NewUserAuthTokenEntity {
|
||||||
|
user_id,
|
||||||
|
token_hash: blake3::hash(auth_token_id.as_bytes()),
|
||||||
|
expires_at: auth_token_expiration,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let session = Session {
|
||||||
|
user_id,
|
||||||
|
created_at: SystemTime::now(),
|
||||||
|
expires_at: auth_token_expiration,
|
||||||
|
};
|
||||||
|
|
||||||
|
let expiration = session_token_expiration
|
||||||
|
.duration_since(SystemTime::now())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
user_session::store_user_session(cache_pool, session_token_id, session, Some(expiration))
|
||||||
|
.await?;
|
||||||
|
|
||||||
let response = AuthLoginResponse {
|
let response = AuthLoginResponse {
|
||||||
user_id,
|
user_id,
|
||||||
username,
|
session: AuthLoginTokenData {
|
||||||
name,
|
token: session_token,
|
||||||
access: AuthLoginTokenData {
|
expiration: session_token_expiration,
|
||||||
token: access_token,
|
|
||||||
expiration: access_token_expiration,
|
|
||||||
},
|
},
|
||||||
auth: AuthLoginTokenData {
|
auth: AuthLoginTokenData {
|
||||||
token: auth_token,
|
token: auth_token,
|
||||||
|
|
|
@ -6,9 +6,7 @@ use serde::Serialize;
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AuthLoginResponse {
|
pub struct AuthLoginResponse {
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
pub username: String,
|
pub session: AuthLoginTokenData,
|
||||||
pub name: String,
|
|
||||||
pub access: AuthLoginTokenData,
|
|
||||||
pub auth: AuthLoginTokenData,
|
pub auth: AuthLoginTokenData,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -84,7 +84,6 @@ async fn register_new_user_request(
|
||||||
|
|
||||||
let new_user_session = Session {
|
let new_user_session = Session {
|
||||||
user_id,
|
user_id,
|
||||||
username,
|
|
||||||
created_at: SystemTime::now(),
|
created_at: SystemTime::now(),
|
||||||
expires_at,
|
expires_at,
|
||||||
};
|
};
|
||||||
|
@ -114,13 +113,13 @@ async fn register_new_user_request(
|
||||||
UserRegistrationResponse {
|
UserRegistrationResponse {
|
||||||
id: user_id,
|
id: user_id,
|
||||||
expires_at,
|
expires_at,
|
||||||
verification_token: None,
|
session_token: None,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
UserRegistrationResponse {
|
UserRegistrationResponse {
|
||||||
id: user_id,
|
id: user_id,
|
||||||
expires_at,
|
expires_at,
|
||||||
verification_token: Some(verification_token),
|
session_token: Some(verification_token),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,5 +13,5 @@ pub struct UserRegistrationResponse {
|
||||||
#[serde(serialize_with = "humantime_serde::serialize")]
|
#[serde(serialize_with = "humantime_serde::serialize")]
|
||||||
pub expires_at: SystemTime,
|
pub expires_at: SystemTime,
|
||||||
|
|
||||||
pub verification_token: Option<String>,
|
pub session_token: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::str::FromStr;
|
use std::{str::FromStr, time::SystemTime};
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
debug_handler,
|
debug_handler,
|
||||||
|
@ -12,10 +12,13 @@ use tracing::{debug, error};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{verify_user, DbPool},
|
db::{insert_new_user_auth_token, verify_user, DbPool, NewUserAuthTokenEntity},
|
||||||
models::{ApiResponse, AppError},
|
models::{ApiResponse, AppError, Session},
|
||||||
requests::AppState,
|
requests::{user::verify::UserVerifyGetResponseTokenAndExpiration, AppState},
|
||||||
services::{auth_token::verify_token, user_session, CachePool},
|
services::{
|
||||||
|
auth_token::{generate_auth_token, generate_session_token, verify_token},
|
||||||
|
user_session, CachePool,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{UserVerifyGetParams, UserVerifyGetResponse};
|
use super::{UserVerifyGetParams, UserVerifyGetResponse};
|
||||||
|
@ -57,29 +60,77 @@ async fn verify_new_user_request(
|
||||||
)
|
)
|
||||||
.inspect_err(|err| error!(?err))?;
|
.inspect_err(|err| error!(?err))?;
|
||||||
|
|
||||||
let token_id = verified_token
|
let verification_token_id = verified_token
|
||||||
.payload_claims()
|
.payload_claims()
|
||||||
.map(|claims| claims.get_claim("jti"))
|
.map(|claims| claims.get_claim("jti"))
|
||||||
.flatten()
|
.flatten()
|
||||||
.map(|jti| Uuid::from_str(jti.as_str().unwrap()).unwrap())
|
.map(|jti| Uuid::from_str(jti.as_str().unwrap()).unwrap())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
user_session::exists(cache_pool, token_id)
|
user_session::exists(cache_pool, verification_token_id)
|
||||||
.await
|
.await
|
||||||
.and_then(|exists| {
|
.and_then(|exists| {
|
||||||
if exists {
|
if exists {
|
||||||
Ok(())
|
Ok(user_session::get_user_session(
|
||||||
|
cache_pool,
|
||||||
|
verification_token_id,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
Err(AppError::no_session_found())
|
Err(AppError::no_session_found())
|
||||||
}
|
}
|
||||||
})?;
|
})?
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// TODO: Add new auth token to database, cache session access token
|
||||||
|
let (session_token, session_token_id, session_token_expiration) =
|
||||||
|
generate_session_token(token_key, user_id);
|
||||||
|
let (auth_token, auth_token_id, auth_token_expiration) =
|
||||||
|
generate_auth_token(token_key, user_id);
|
||||||
|
|
||||||
|
let session = Session {
|
||||||
|
user_id,
|
||||||
|
created_at: SystemTime::now(),
|
||||||
|
expires_at: session_token_expiration,
|
||||||
|
};
|
||||||
|
|
||||||
|
user_session::store_user_session(
|
||||||
|
cache_pool,
|
||||||
|
session_token_id,
|
||||||
|
session,
|
||||||
|
Some(
|
||||||
|
session_token_expiration
|
||||||
|
.duration_since(SystemTime::now())
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
insert_new_user_auth_token(
|
||||||
|
db_pool,
|
||||||
|
NewUserAuthTokenEntity {
|
||||||
|
user_id,
|
||||||
|
token_hash: blake3::hash(auth_token_id.as_bytes()),
|
||||||
|
expires_at: auth_token_expiration,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let response = verify_token(
|
let response = verify_token(
|
||||||
token_key,
|
token_key,
|
||||||
verification_token.as_str(),
|
verification_token.as_str(),
|
||||||
Some(validation_rules),
|
Some(validation_rules),
|
||||||
)
|
)
|
||||||
.map(|_| UserVerifyGetResponse::new(token_key, user_id))?;
|
.map(|_| UserVerifyGetResponse {
|
||||||
|
user_id,
|
||||||
|
session: UserVerifyGetResponseTokenAndExpiration {
|
||||||
|
token: session_token,
|
||||||
|
expires_at: session_token_expiration,
|
||||||
|
},
|
||||||
|
auth: UserVerifyGetResponseTokenAndExpiration {
|
||||||
|
token: auth_token,
|
||||||
|
expires_at: auth_token_expiration,
|
||||||
|
},
|
||||||
|
})?;
|
||||||
|
|
||||||
verify_user(db_pool, user_id)
|
verify_user(db_pool, user_id)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,35 +1,20 @@
|
||||||
use humantime::format_rfc3339_seconds;
|
use std::time::SystemTime;
|
||||||
use pasetors::{keys::SymmetricKey, version4::V4};
|
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::services::auth_token::{generate_access_token, generate_auth_token};
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct UserVerifyGetResponse {
|
pub struct UserVerifyGetResponse {
|
||||||
access: UserVerifyGetResponseTokenAndExpiration,
|
pub user_id: i32,
|
||||||
auth: UserVerifyGetResponseTokenAndExpiration,
|
pub session: UserVerifyGetResponseTokenAndExpiration,
|
||||||
}
|
pub auth: UserVerifyGetResponseTokenAndExpiration,
|
||||||
|
|
||||||
impl UserVerifyGetResponse {
|
|
||||||
pub fn new(key: &SymmetricKey<V4>, user_id: i32) -> Self {
|
|
||||||
let (access_token, _, access_token_expiration) = generate_access_token(key, user_id);
|
|
||||||
let (auth_token, _, auth_token_expiration) = generate_auth_token(key, user_id);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
access: UserVerifyGetResponseTokenAndExpiration {
|
|
||||||
token: access_token,
|
|
||||||
expiration: format_rfc3339_seconds(access_token_expiration).to_string(),
|
|
||||||
},
|
|
||||||
auth: UserVerifyGetResponseTokenAndExpiration {
|
|
||||||
token: auth_token,
|
|
||||||
expiration: format_rfc3339_seconds(auth_token_expiration).to_string(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct UserVerifyGetResponseTokenAndExpiration {
|
pub struct UserVerifyGetResponseTokenAndExpiration {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub expiration: String,
|
|
||||||
|
#[serde(serialize_with = "humantime_serde::serialize")]
|
||||||
|
pub expires_at: SystemTime,
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,7 +51,7 @@ pub fn verify_token(
|
||||||
Ok(token)
|
Ok(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_access_token(key: &SymmetricKey<V4>, user_id: i32) -> (String, Uuid, SystemTime) {
|
pub fn generate_session_token(key: &SymmetricKey<V4>, user_id: i32) -> (String, Uuid, SystemTime) {
|
||||||
generate_token(key, user_id, ONE_HOUR, None)
|
generate_token(key, user_id, ONE_HOUR, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ pub async fn store_user_session(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn _get_user_session(
|
pub async fn get_user_session(
|
||||||
cache_pool: &CachePool,
|
cache_pool: &CachePool,
|
||||||
token_id: Uuid,
|
token_id: Uuid,
|
||||||
) -> Result<Option<Session>, AppError> {
|
) -> Result<Option<Session>, AppError> {
|
||||||
|
@ -51,41 +51,32 @@ pub async fn _get_user_session(
|
||||||
if let Some(object) = object {
|
if let Some(object) = object {
|
||||||
let user_id: i32 = object
|
let user_id: i32 = object
|
||||||
.get("userId")
|
.get("userId")
|
||||||
.ok_or(AppError::_missing_session_field("userId"))
|
.ok_or(AppError::missing_session_field("userId"))
|
||||||
.and_then(|value| {
|
.and_then(|value| {
|
||||||
FromRedisValue::from_redis_value(value)
|
FromRedisValue::from_redis_value(value)
|
||||||
.map_err(|_| AppError::_missing_session_field("userId"))
|
.map_err(|_| AppError::missing_session_field("userId"))
|
||||||
})?;
|
|
||||||
|
|
||||||
let username: String = object
|
|
||||||
.get("username")
|
|
||||||
.ok_or(AppError::_missing_session_field("username"))
|
|
||||||
.and_then(|value| {
|
|
||||||
FromRedisValue::from_redis_value(value)
|
|
||||||
.map_err(|_| AppError::_missing_session_field("username"))
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let created_at: SystemTime = object
|
let created_at: SystemTime = object
|
||||||
.get("createdAt")
|
.get("createdAt")
|
||||||
.ok_or(AppError::_missing_session_field("createdAt"))
|
.ok_or(AppError::missing_session_field("createdAt"))
|
||||||
.and_then(|value| {
|
.and_then(|value| {
|
||||||
FromRedisValue::from_redis_value(value)
|
FromRedisValue::from_redis_value(value)
|
||||||
.map_err(|_| AppError::_missing_session_field("createdAt"))
|
.map_err(|_| AppError::missing_session_field("createdAt"))
|
||||||
})
|
})
|
||||||
.map(|serialized: String| parse_rfc3339(serialized.as_str()).unwrap())?;
|
.map(|serialized: String| parse_rfc3339(serialized.as_str()).unwrap())?;
|
||||||
|
|
||||||
let expires_at: SystemTime = object
|
let expires_at: SystemTime = object
|
||||||
.get("createdAt")
|
.get("createdAt")
|
||||||
.ok_or(AppError::_missing_session_field("expiresAt"))
|
.ok_or(AppError::missing_session_field("expiresAt"))
|
||||||
.and_then(|value| {
|
.and_then(|value| {
|
||||||
FromRedisValue::from_redis_value(value)
|
FromRedisValue::from_redis_value(value)
|
||||||
.map_err(|_| AppError::_missing_session_field("expiresAt"))
|
.map_err(|_| AppError::missing_session_field("expiresAt"))
|
||||||
})
|
})
|
||||||
.map(|serialized: String| parse_rfc3339(serialized.as_str()).unwrap())?;
|
.map(|serialized: String| parse_rfc3339(serialized.as_str()).unwrap())?;
|
||||||
|
|
||||||
Ok(Some(Session {
|
Ok(Some(Session {
|
||||||
user_id,
|
user_id,
|
||||||
username,
|
|
||||||
created_at,
|
created_at,
|
||||||
expires_at,
|
expires_at,
|
||||||
}))
|
}))
|
||||||
|
|
Loading…
Add table
Reference in a new issue