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"
|
||||
sqlx = { version = "0.8", features = [
|
||||
"default",
|
||||
"chrono",
|
||||
"time",
|
||||
"postgres",
|
||||
"runtime-tokio",
|
||||
] }
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
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_email_uniq_idx;
|
||||
DROP INDEX IF EXISTS permission_name_uniq_idx;
|
||||
|
||||
DROP TABLE IF EXISTS public.user_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.status CASCADE;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
CREATE TABLE IF NOT EXISTS
|
||||
public.status (
|
||||
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NULL
|
||||
|
@ -20,7 +20,7 @@ VALUES
|
|||
|
||||
CREATE TABLE IF NOT EXISTS
|
||||
public.user (
|
||||
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
username VARCHAR(255) NOT NULL,
|
||||
password 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_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
|
||||
public.permission (
|
||||
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
status_id INT NOT NULL REFERENCES status(id) DEFAULT 1,
|
||||
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
|
||||
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),
|
||||
user_id INT NOT NULL REFERENCES public.user(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 time::OffsetDateTime;
|
||||
use tracing::error;
|
||||
|
||||
use crate::models::AppError;
|
||||
|
@ -60,19 +66,17 @@ pub async fn insert_new_user(
|
|||
}
|
||||
|
||||
#[derive(Debug, FromRow)]
|
||||
pub struct UserAndHashedPassword {
|
||||
pub struct UserIdAndHashedPasswordEntity {
|
||||
pub id: i32,
|
||||
pub username: String,
|
||||
pub name: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
pub async fn get_username_and_password_by_username(
|
||||
pool: &DbPool,
|
||||
username: String,
|
||||
) -> Result<UserAndHashedPassword, AppError> {
|
||||
sqlx::query_as::<_, UserAndHashedPassword>(
|
||||
"SELECT id, username, name, password FROM public.user WHERE username = $1;",
|
||||
) -> Result<UserIdAndHashedPasswordEntity, AppError> {
|
||||
sqlx::query_as::<_, UserIdAndHashedPasswordEntity>(
|
||||
"SELECT id, password FROM public.user WHERE username = $1;",
|
||||
)
|
||||
.bind(username)
|
||||
.fetch_one(pool)
|
||||
|
@ -94,3 +98,75 @@ pub async fn verify_user(pool: &DbPool, user_id: i32) -> Result<(), AppError> {
|
|||
})
|
||||
.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))
|
||||
}
|
||||
|
||||
pub fn _missing_session_field(field: &'static str) -> Self {
|
||||
pub fn missing_session_field(field: &'static str) -> Self {
|
||||
Self::new(ErrorKind::MissingSessionField(field))
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ use redis::ToRedisArgs;
|
|||
#[derive(Debug)]
|
||||
pub struct Session {
|
||||
pub user_id: i32,
|
||||
pub username: String,
|
||||
pub created_at: SystemTime,
|
||||
pub expires_at: SystemTime,
|
||||
}
|
||||
|
@ -17,14 +16,12 @@ impl Session {
|
|||
) -> Vec<(&'static str, impl ToRedisArgs + Send + Sync + 'a)> {
|
||||
let Self {
|
||||
user_id,
|
||||
username,
|
||||
created_at,
|
||||
expires_at,
|
||||
} = self;
|
||||
|
||||
vec![
|
||||
("userId", user_id.to_string()),
|
||||
("username", username),
|
||||
("createdAt", format_rfc3339(created_at).to_string()),
|
||||
("expiresAt", format_rfc3339(expires_at).to_string()),
|
||||
]
|
||||
|
@ -35,7 +32,6 @@ impl Default for Session {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
user_id: Default::default(),
|
||||
username: Default::default(),
|
||||
created_at: UNIX_EPOCH,
|
||||
expires_at: UNIX_EPOCH,
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use std::time::SystemTime;
|
||||
|
||||
use axum::{
|
||||
debug_handler,
|
||||
extract::State,
|
||||
|
@ -9,15 +11,18 @@ use pasetors::{keys::SymmetricKey, version4::V4};
|
|||
use tracing::debug;
|
||||
|
||||
use crate::{
|
||||
db::{get_username_and_password_by_username, DbPool, UserAndHashedPassword},
|
||||
models::{ApiResponse, AppError},
|
||||
db::{
|
||||
get_username_and_password_by_username, insert_new_user_auth_token, DbPool,
|
||||
NewUserAuthTokenEntity, UserIdAndHashedPasswordEntity,
|
||||
},
|
||||
models::{ApiResponse, AppError, Session},
|
||||
requests::{
|
||||
auth::login::models::{AuthLoginResponse, AuthLoginTokenData},
|
||||
AppState,
|
||||
},
|
||||
services::{
|
||||
auth_token::{generate_access_token, generate_auth_token},
|
||||
verify_password,
|
||||
auth_token::{generate_auth_token, generate_session_token},
|
||||
user_session, verify_password, CachePool,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -28,41 +33,62 @@ pub async fn auth_login_post_handler(
|
|||
State(state): State<AppState>,
|
||||
Json(body): Json<AuthLoginRequest>,
|
||||
) -> 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();
|
||||
auth_login_request(pool, token_key, body).await
|
||||
auth_login_request(db_pool, cache_pool, token_key, body).await
|
||||
}
|
||||
|
||||
async fn auth_login_request(
|
||||
pool: &DbPool,
|
||||
db_pool: &DbPool,
|
||||
cache_pool: &CachePool,
|
||||
token_key: &SymmetricKey<V4>,
|
||||
body: AuthLoginRequest,
|
||||
) -> Result<Response, AppError> {
|
||||
debug!(?body);
|
||||
|
||||
let AuthLoginRequest { username, password } = body;
|
||||
let UserAndHashedPassword {
|
||||
let UserIdAndHashedPasswordEntity {
|
||||
id: user_id,
|
||||
username,
|
||||
name,
|
||||
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)?;
|
||||
|
||||
let (access_token, _access_token_id, access_token_expiration) =
|
||||
generate_access_token(token_key, user_id);
|
||||
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) =
|
||||
let (auth_token, auth_token_id, auth_token_expiration) =
|
||||
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 {
|
||||
user_id,
|
||||
username,
|
||||
name,
|
||||
access: AuthLoginTokenData {
|
||||
token: access_token,
|
||||
expiration: access_token_expiration,
|
||||
session: AuthLoginTokenData {
|
||||
token: session_token,
|
||||
expiration: session_token_expiration,
|
||||
},
|
||||
auth: AuthLoginTokenData {
|
||||
token: auth_token,
|
||||
|
|
|
@ -6,9 +6,7 @@ use serde::Serialize;
|
|||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AuthLoginResponse {
|
||||
pub user_id: i32,
|
||||
pub username: String,
|
||||
pub name: String,
|
||||
pub access: AuthLoginTokenData,
|
||||
pub session: AuthLoginTokenData,
|
||||
pub auth: AuthLoginTokenData,
|
||||
}
|
||||
|
||||
|
|
|
@ -84,7 +84,6 @@ async fn register_new_user_request(
|
|||
|
||||
let new_user_session = Session {
|
||||
user_id,
|
||||
username,
|
||||
created_at: SystemTime::now(),
|
||||
expires_at,
|
||||
};
|
||||
|
@ -114,13 +113,13 @@ async fn register_new_user_request(
|
|||
UserRegistrationResponse {
|
||||
id: user_id,
|
||||
expires_at,
|
||||
verification_token: None,
|
||||
session_token: None,
|
||||
}
|
||||
} else {
|
||||
UserRegistrationResponse {
|
||||
id: user_id,
|
||||
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")]
|
||||
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::{
|
||||
debug_handler,
|
||||
|
@ -12,10 +12,13 @@ use tracing::{debug, error};
|
|||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
db::{verify_user, DbPool},
|
||||
models::{ApiResponse, AppError},
|
||||
requests::AppState,
|
||||
services::{auth_token::verify_token, user_session, CachePool},
|
||||
db::{insert_new_user_auth_token, verify_user, DbPool, NewUserAuthTokenEntity},
|
||||
models::{ApiResponse, AppError, Session},
|
||||
requests::{user::verify::UserVerifyGetResponseTokenAndExpiration, AppState},
|
||||
services::{
|
||||
auth_token::{generate_auth_token, generate_session_token, verify_token},
|
||||
user_session, CachePool,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{UserVerifyGetParams, UserVerifyGetResponse};
|
||||
|
@ -57,29 +60,77 @@ async fn verify_new_user_request(
|
|||
)
|
||||
.inspect_err(|err| error!(?err))?;
|
||||
|
||||
let token_id = verified_token
|
||||
let verification_token_id = verified_token
|
||||
.payload_claims()
|
||||
.map(|claims| claims.get_claim("jti"))
|
||||
.flatten()
|
||||
.map(|jti| Uuid::from_str(jti.as_str().unwrap()).unwrap())
|
||||
.unwrap();
|
||||
|
||||
user_session::exists(cache_pool, token_id)
|
||||
user_session::exists(cache_pool, verification_token_id)
|
||||
.await
|
||||
.and_then(|exists| {
|
||||
if exists {
|
||||
Ok(())
|
||||
Ok(user_session::get_user_session(
|
||||
cache_pool,
|
||||
verification_token_id,
|
||||
))
|
||||
} else {
|
||||
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(
|
||||
token_key,
|
||||
verification_token.as_str(),
|
||||
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)
|
||||
.await
|
||||
|
|
|
@ -1,35 +1,20 @@
|
|||
use humantime::format_rfc3339_seconds;
|
||||
use pasetors::{keys::SymmetricKey, version4::V4};
|
||||
use std::time::SystemTime;
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::services::auth_token::{generate_access_token, generate_auth_token};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserVerifyGetResponse {
|
||||
access: UserVerifyGetResponseTokenAndExpiration,
|
||||
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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
pub user_id: i32,
|
||||
pub session: UserVerifyGetResponseTokenAndExpiration,
|
||||
pub auth: UserVerifyGetResponseTokenAndExpiration,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UserVerifyGetResponseTokenAndExpiration {
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ pub async fn store_user_session(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn _get_user_session(
|
||||
pub async fn get_user_session(
|
||||
cache_pool: &CachePool,
|
||||
token_id: Uuid,
|
||||
) -> Result<Option<Session>, AppError> {
|
||||
|
@ -51,41 +51,32 @@ pub async fn _get_user_session(
|
|||
if let Some(object) = object {
|
||||
let user_id: i32 = object
|
||||
.get("userId")
|
||||
.ok_or(AppError::_missing_session_field("userId"))
|
||||
.ok_or(AppError::missing_session_field("userId"))
|
||||
.and_then(|value| {
|
||||
FromRedisValue::from_redis_value(value)
|
||||
.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"))
|
||||
.map_err(|_| AppError::missing_session_field("userId"))
|
||||
})?;
|
||||
|
||||
let created_at: SystemTime = object
|
||||
.get("createdAt")
|
||||
.ok_or(AppError::_missing_session_field("createdAt"))
|
||||
.ok_or(AppError::missing_session_field("createdAt"))
|
||||
.and_then(|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())?;
|
||||
|
||||
let expires_at: SystemTime = object
|
||||
.get("createdAt")
|
||||
.ok_or(AppError::_missing_session_field("expiresAt"))
|
||||
.ok_or(AppError::missing_session_field("expiresAt"))
|
||||
.and_then(|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())?;
|
||||
|
||||
Ok(Some(Session {
|
||||
user_id,
|
||||
username,
|
||||
created_at,
|
||||
expires_at,
|
||||
}))
|
||||
|
|
Loading…
Add table
Reference in a new issue