#![forbid(unsafe_code, future_incompatible, rust_2018_idioms)]
#![deny(missing_debug_implementations, nonstandard_style)]
#![warn(missing_docs, rustdoc::missing_doc_code_examples, unreachable_pub)]
use async_session::chrono::{Duration, Utc};
use async_session::{async_trait, Result, Session, SessionStore};
use mongodb::bson::{self, doc, Bson, Document};
use mongodb::options::{ReplaceOptions, SelectionCriteria};
use mongodb::Client;
#[derive(Debug, Clone)]
pub struct MongodbSessionStore {
collection: mongodb::Collection<Document>,
database: mongodb::Database,
}
impl MongodbSessionStore {
pub async fn new(uri: &str, db_name: &str, coll_name: &str) -> mongodb::error::Result<Self> {
let client = Client::with_uri_str(uri).await?;
let middleware = Self::from_client(client, db_name, coll_name);
middleware.create_expire_index("expireAt", 0).await?;
Ok(middleware)
}
pub fn from_client(client: Client, db_name: &str, coll_name: &str) -> Self {
let database = client.database(db_name);
let collection = database.collection(coll_name);
Self {
database,
collection,
}
}
pub async fn initialize(&self) -> Result {
self.index_on_expiry_at().await
}
async fn create_expire_index(
&self,
field_name: &str,
expire_after_seconds: u32,
) -> mongodb::error::Result<()> {
let create_index = doc! {
"createIndexes": self.collection.name(),
"indexes": [
{
"key" : { field_name: 1 },
"name": format!("session_expire_index_{}", field_name),
"expireAfterSeconds": expire_after_seconds,
}
]
};
self.database
.run_command(
create_index,
SelectionCriteria::ReadPreference(mongodb::options::ReadPreference::Primary),
)
.await?;
Ok(())
}
pub async fn index_on_expiry_at(&self) -> Result {
self.create_expire_index("expireAt", 0).await?;
Ok(())
}
}
#[async_trait]
impl SessionStore for MongodbSessionStore {
async fn store_session(&self, session: Session) -> Result<Option<String>> {
let coll = &self.collection;
let value = bson::to_bson(&session)?;
let id = session.id();
let query = doc! { "session_id": id };
let expire_at = match session.expiry() {
None => Utc::now() + Duration::from_std(std::time::Duration::from_secs(1200)).unwrap(),
Some(expiry) => *{ expiry },
};
let replacement = doc! { "session_id": id, "session": value, "expireAt": expire_at, "created": Utc::now() };
let opts = ReplaceOptions::builder().upsert(true).build();
coll.replace_one(query, replacement, Some(opts)).await?;
Ok(session.into_cookie_value())
}
async fn load_session(&self, cookie_value: String) -> Result<Option<Session>> {
let id = Session::id_from_cookie_value(&cookie_value)?;
let coll = &self.collection;
let filter = doc! { "session_id": id };
match coll.find_one(filter, None).await? {
None => Ok(None),
Some(doc) => {
let bsession = match doc.get("session") {
Some(v) => v.clone(),
None => return Ok(None),
};
if let Some(expiry_at) = doc.get("expireAt").and_then(Bson::as_datetime) {
if expiry_at.to_chrono() < Utc::now() {
return Ok(None);
}
}
Ok(Some(bson::from_bson::<Session>(bsession)?))
}
}
}
async fn destroy_session(&self, session: Session) -> Result {
let coll = &self.collection;
coll.delete_one(doc! { "session_id": session.id() }, None)
.await?;
Ok(())
}
async fn clear_store(&self) -> Result {
let coll = &self.collection;
coll.drop(None).await?;
self.initialize().await?;
Ok(())
}
}