glacier-web-cli 0.3.0

Command-line tool for creating and running Glacier applications.
//! Signing in and out, and who's signed in. Written by `glacier generate
//! authentication`; it's your code now, so change it as you need.
//!
//! A session is a row in `sessions`. The client gets its token when it
//! signs in and sends it as `Authorization: Bearer <token>`. The database
//! stores only the token's SHA-256 digest.

use glacier::handler::BoxFuture;
use glacier::prelude::*;

use crate::models::{Session, User};

/// The signed-in user and their session, found by [`Authentication`].
#[derive(Clone)]
pub struct Current {
    pub user: User,
    pub session: Session,
}

/// The signed-in user, if there is one.
pub fn current_user(ctx: &Context) -> Option<&User> {
    ctx.extensions().get::<Current>().map(|current| &current.user)
}

/// Finds each request's session from its bearer token. Register it on the
/// app, so every request knows who's signed in.
pub struct Authentication;

impl Middleware for Authentication {
    fn handle(&self, mut ctx: Context, next: Next) -> BoxFuture<Response> {
        Box::pin(async move {
            let token = glacier::auth::bearer_token(&ctx).map(str::to_owned);
            if let Some(token) = token
                && let Ok(Some(session)) = Session::find_by_token(&token).await
                && let Some(user) = session.user.get().cloned()
            {
                ctx.extensions_mut().insert(Current { user, session });
            }
            next.run(ctx).await
        })
    }
}

/// Lets only signed-in users through; others get `401 Unauthorized`.
pub struct RequireAuthentication;

impl Middleware for RequireAuthentication {
    fn handle(&self, ctx: Context, next: Next) -> BoxFuture<Response> {
        Box::pin(async move {
            if ctx.extensions().get::<Current>().is_some() {
                return next.run(ctx).await;
            }
            Error::new(StatusCode::UNAUTHORIZED, "sign in first").into_response()
        })
    }
}

/// Signs `user` in: a new session, whose token the client sends from now on.
pub async fn start_session(ctx: &Context, user: &User) -> glacier::Result<String> {
    let user_agent = ctx.header("user-agent").map(str::to_owned);
    Session::start(user.id, user_agent).await
}

/// Signs out: ends the current session.
pub async fn end_session(ctx: &Context) -> glacier::Result<()> {
    if let Some(current) = ctx.extensions().get::<Current>() {
        current.session.clone().destroy().await?;
    }
    Ok(())
}