use rocket::{fairing::AdHoc, futures::FutureExt, http::Status, routes, serde::json::Json};
use rocket_flex_session::{
storage::redis::{RedisFormat, RedisFredStorage, RedisValue, SessionRedis},
RocketFlexSession, Session, SessionIdentifier,
};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct BasicSession {
user_id: u32,
name: String,
}
impl SessionIdentifier for BasicSession {
type Id = String;
fn identifier(&self) -> Option<Self::Id> {
Some(self.user_id.to_string())
}
}
impl SessionRedis for BasicSession {
const REDIS_FORMAT: RedisFormat = RedisFormat::String;
type Error = serde_json::Error;
fn into_redis(self) -> Result<RedisValue, Self::Error> {
let value = serde_json::to_string(&self)?;
Ok(RedisValue::String(value))
}
fn from_redis(value: RedisValue) -> Result<Self, Self::Error> {
let value = value.into_string().expect("Should be a string type");
serde_json::from_str(&value)
}
}
#[rocket::launch]
async fn basic() -> _ {
let setup_session = AdHoc::on_ignite("Sessions", |rocket| async {
use fred::prelude::*;
let config = Config::from_url("redis://my-redis-server").expect("should parse Redis URL");
let pool = Builder::from_config(config)
.build_pool(4)
.expect("should build Redis pool");
pool.init().await.expect("should initialize Redis pool");
let storage = RedisFredStorage::builder()
.pool(pool.clone())
.prefix("sess:")
.index_prefix("sess:user:")
.build();
let session_fairing = RocketFlexSession::<BasicSession>::builder()
.storage(storage)
.with_options(|opt| {
opt.cookie_name = "my-cookie-name".to_string();
})
.build();
rocket.attach(session_fairing).manage(pool)
});
let shutdown_session = AdHoc::on_shutdown("Shutdown", |rocket| {
async {
use fred::prelude::{ClientLike, Pool};
let pool = rocket.state::<Pool>().expect("should be in Rocket state");
if let Err(e) = pool.quit().await {
eprintln!("Failed to quit Redis connection: {e}");
}
}
.boxed()
});
rocket::build()
.attach(setup_session)
.attach(shutdown_session)
.mount("/", routes![login, logout, user, logout_everywhere])
}
#[derive(Deserialize)]
struct LoginData {
username: String,
password: String,
}
#[rocket::post("/login", data = "<data>")]
async fn login(
mut session: Session<'_, BasicSession>,
data: Json<LoginData>,
) -> Result<&'static str, (Status, &'static str)> {
if session.tap(|data| data.is_some()) {
return Err((Status::BadRequest, "Already logged in"));
}
if data.username == "rossg" && data.password == "dinosaurs" {
session.set(BasicSession {
user_id: 1,
name: "Ross".to_string(),
});
Ok("Logged in")
} else {
Err((Status::Unauthorized, "Invalid credentials"))
}
}
#[rocket::get("/user")]
async fn user(session: Session<'_, BasicSession>) -> Result<String, (Status, &'static str)> {
match session.tap(|data| data.map(|d| d.user_id)) {
Some(user_id) => Ok(format!("User ID: {}", user_id)),
None => Err((Status::Unauthorized, "Not logged in")),
}
}
#[rocket::post("/logout")]
async fn logout(mut session: Session<'_, BasicSession>) -> &'static str {
session.delete();
"Logged out"
}
#[rocket::post("/logout-everywhere")]
async fn logout_everywhere(session: Session<'_, BasicSession>) -> Result<String, (Status, String)> {
let _ = session.get_session_ids_by_identifier(&"foo".into()).await;
match session.invalidate_all_sessions(false).await {
Ok(Some(n)) => Ok(format!("Logged out from {} sessions", n)),
Ok(None) => Err((Status::Unauthorized, "Not logged in".to_string())),
Err(err) => Err((Status::InternalServerError, err.to_string())),
}
}