Skip to main content

Session

Struct Session 

Source
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

Source

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
}
Source

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) }
}
Source

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}")))
}
Source

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}"))
}
Source

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")
}
Source

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())
}
Source

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("/"))
}
Source

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"))
}
Source

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())
}
Source

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"))
}

Trait Implementations§

Source§

impl Clone for Session

Source§

fn clone(&self) -> Session

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<S: Sync> FromRequestParts<S> for Session

Source§

type Rejection = Error

If the extractor fails it’ll use this “rejection” type. A rejection is a kind of error that can be converted into a response.
Source§

async fn from_request_parts( parts: &mut Parts, _state: &S, ) -> Result<Self, Error>

Perform the extraction.

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T> FromRef<T> for T
where T: Clone,

§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
§

impl<S, T> FromRequest<S, ViaParts> for T
where S: Send + Sync, T: FromRequestParts<S>,

§

type Rejection = <T as FromRequestParts<S>>::Rejection

If the extractor fails it’ll use this “rejection” type. A rejection is a kind of error that can be converted into a response.
§

fn from_request( req: Request<Body>, state: &S, ) -> impl Future<Output = Result<T, <T as FromRequest<S, ViaParts>>::Rejection>>

Perform the extraction.
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<S, T> Upcast<T> for S
where T: UpcastFrom<S> + ?Sized, S: ?Sized,

Source§

fn upcast(&self) -> &T
where Self: ErasableGeneric, T: Sized + ErasableGeneric<Repr = Self::Repr>,

Perform a zero-cost type-safe upcast to a wider ref type within the Wasm bindgen generics type system. Read more
Source§

fn upcast_into(self) -> T
where Self: Sized + ErasableGeneric, T: Sized + ErasableGeneric<Repr = Self::Repr>,

Perform a zero-cost type-safe upcast to a wider type within the Wasm bindgen generics type system. Read more
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V