pub struct Session(/* private fields */);Expand description
The current request’s session, stored in an encrypted cookie, as an extractor.
Like Rails’ default cookie store: values are serialized as JSON and the
cookie is encrypted and authenticated with AES-256-GCM using a key derived
from SECRET_KEY_BASE, so clients can neither read nor change it. A
cookie that does not decrypt (tampered, or encrypted with a key that is
neither SECRET_KEY_BASE nor listed in SECRET_KEY_BASE_PREVIOUS)
starts an empty session; one encrypted with a previous key is read and
re-encrypted with the current key. The cookie is decrypted on first use;
changes are sent back as one Set-Cookie header when the handler returns,
and only if something changed. Clones share the same session.
Browsers drop cookies over 4 KB: store ids, not records. A response whose session cookie would be larger becomes a 500 (logged, naming that fix).
By default the cookie lasts until the browser session ends and the
session never expires on its own. expire_in sets an
expiry time stored inside the encrypted cookie (checked on every request,
so an old copy of the cookie cannot be replayed after it), and
remember_for also keeps the cookie across browser
restarts (“remember me”). Keys starting with _ are reserved for Ocre.
Server-side session tracking (list and revoke a user’s sessions) is
generated by ocre g auth --db-sessions, which stores a session id here
and the session row in D1.
Added by serve. Outside serve the extractor rejects
with Error::Internal: as an HTML page with the html feature, as
ApiError JSON without it.
§Free plan
Nothing is stored on the server: sessions cost no D1 rows and no KV operations, only a little CPU for AES-GCM.
§Examples
use axum::response::Redirect;
use ocre::{Result, Session};
async fn login(session: Session) -> Result<Redirect> {
session.insert("user_id", 42)?;
session.flash("notice", "Signed in.")?;
Ok(Redirect::to("/"))
}
async fn current_user_id(session: Session) -> Result<String> {
let id: Option<i64> = session.get("user_id")?;
Ok(id.map_or("guest".to_owned(), |id| id.to_string()))
}Implementations§
Source§impl Session
impl Session
Sourcepub fn expire_in(&self, seconds: i64) -> Result<()>
pub fn expire_in(&self, seconds: i64) -> Result<()>
Makes the session expire seconds from now; the cookie still ends with the browser session.
The expiry time is stored inside the encrypted cookie and checked on
every request, so a copied or replayed cookie stops working after it
(Rails’ “session expiry” countermeasure): the session then starts
empty. Calling it again moves the expiry. clear
removes it with everything else. Generated sign-in (ocre g auth)
calls it or remember_for.
§Errors
Error::Internal when SECRET_KEY_BASE is missing or shorter
than 64 characters.
§Examples
use ocre::{Result, Session};
async fn sign_in(session: Session) -> Result<()> {
session.insert("user_id", 42)?;
session.expire_in(24 * 3600) // signed out after a day, even if the tab stays open
}Sourcepub fn remember_for(&self, seconds: i64) -> Result<()>
pub fn remember_for(&self, seconds: i64) -> Result<()>
Keeps the session for seconds, across browser restarts (“remember me”).
Like expire_in, plus a persistent cookie
(Max-Age set to the time left), so closing the browser does not sign
the user out. Every later Set-Cookie keeps the same end time.
§Errors
Error::Internal when SECRET_KEY_BASE is missing or shorter
than 64 characters.
§Examples
use ocre::{Result, Session};
async fn sign_in(session: Session, remember_me: bool) -> Result<()> {
session.insert("user_id", 42)?;
if remember_me { session.remember_for(30 * 24 * 3600) } else { session.expire_in(24 * 3600) }
}Sourcepub fn expires_at(&self) -> Result<Option<i64>>
pub fn expires_at(&self) -> Result<Option<i64>>
When the session expires, in Unix seconds, if expire_in
or remember_for set it.
§Errors
Error::Internal when the request carries a session cookie but
SECRET_KEY_BASE is missing or shorter than 64 characters.
§Examples
use ocre::{Result, Session};
async fn expires(session: Session) -> Result<String> {
Ok(session.expires_at()?.map_or("with the browser".to_owned(), |at| format!("at {at}")))
}Sourcepub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>>
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>>
Returns the value stored under key, or None if absent or not deserializable as T.
§Errors
Error::Internal when the request carries a session cookie but
SECRET_KEY_BASE is missing or shorter than 64 characters.
§Examples
use ocre::{Error, Result, Session};
async fn dashboard(session: Session) -> Result<String> {
let user_id: i64 = session.get("user_id")?.ok_or(Error::Unauthorized)?;
Ok(format!("user {user_id}"))
}Sourcepub fn insert(&self, key: &str, value: impl Serialize) -> Result<()>
pub fn insert(&self, key: &str, value: impl Serialize) -> Result<()>
Stores value under key, replacing any previous value.
§Errors
Error::Internal when value does not serialize to JSON, or when
SECRET_KEY_BASE is missing or shorter than 64 characters (nothing is
changed then).
§Examples
use ocre::{Result, Session};
async fn set_theme(session: Session) -> Result<()> {
session.insert("theme", "dark")
}Sourcepub fn remove(&self, key: &str) -> Result<bool>
pub fn remove(&self, key: &str) -> Result<bool>
Removes key and returns whether it was there.
§Errors
Error::Internal when SECRET_KEY_BASE is missing or shorter than
64 characters.
§Examples
use ocre::{Result, Session};
async fn reset_theme(session: Session) -> Result<String> {
Ok(if session.remove("theme")? { "reset" } else { "already default" }.to_owned())
}Sourcepub fn clear(&self) -> Result<()>
pub fn clear(&self) -> Result<()>
Empties the session (sign out); flash messages set during this request are kept.
The response then deletes the cookie if nothing is left.
§Errors
Error::Internal when SECRET_KEY_BASE is missing or shorter than
64 characters.
§Examples
use axum::response::Redirect;
use ocre::{Result, Session};
async fn logout(session: Session) -> Result<Redirect> {
session.clear()?;
session.flash("notice", "Signed out.")?;
Ok(Redirect::to("/"))
}Sourcepub fn flash(&self, kind: &str, message: impl Into<String>) -> Result<()>
pub fn flash(&self, kind: &str, message: impl Into<String>) -> Result<()>
Stores message under kind to show on the next request, usually after a redirect.
Kinds are free-form; generated code uses notice and alert. A second
message of the same kind replaces the first. The next request reads it
with Flash (or flashes), after which it is gone.
§Errors
Error::Internal when SECRET_KEY_BASE is missing or shorter than
64 characters.
§Examples
use axum::response::Redirect;
use ocre::{Result, Session};
async fn create(session: Session) -> Result<Redirect> {
session.flash("notice", "Post was successfully created.")?;
Ok(Redirect::to("/posts"))
}Sourcepub fn flashes(&self) -> Result<Flash>
pub fn flashes(&self) -> Result<Flash>
Returns the flash messages set by the previous request and removes them from the session.
The Flash extractor calls it.
§Errors
Error::Internal when the request carries a session cookie but
SECRET_KEY_BASE is missing or shorter than 64 characters.
§Examples
use ocre::{Result, Session};
async fn index(session: Session) -> Result<String> {
Ok(session.flashes()?.notice().unwrap_or_default().to_owned())
}Sourcepub fn keep_flash(&self, flash: &Flash) -> Result<()>
pub fn keep_flash(&self, flash: &Flash) -> Result<()>
Keeps flash for the next request too (Rails’ flash.keep), e.g.
when a page read the messages but redirects again before showing them.
§Errors
Error::Internal when SECRET_KEY_BASE is missing or shorter
than 64 characters.
§Examples
use axum::response::Redirect;
use ocre::{Flash, Result, Session};
async fn old_dashboard(session: Session, flash: Flash) -> Result<Redirect> {
session.keep_flash(&flash)?;
Ok(Redirect::to("/dashboard"))
}