Skip to main content

laterite_auth/
lib.rs

1//! Laterite auth: backend user authentication and authorization.
2//!
3//! Provides the operator-facing security primitives the admin surface is
4//! built on: Argon2id password hashing, opaque server-side sessions, a
5//! role-based permission model over dotted permission strings, brute-force
6//! throttling, and an append-only access log. "Backend users" are the
7//! operators of the admin, kept distinct from any application's end users.
8//!
9//! This crate is HTTP-agnostic on purpose. It exposes an [`AuthService`] with
10//! plain async methods (`authenticate`, `verify_session`, `logout`) plus an
11//! [`AuthenticatedUser`] identity; the admin crate wraps these in Axum
12//! extractors, cookie handling, and the rendered login screen.
13
14pub mod audit_listener;
15pub mod error;
16pub mod events;
17pub mod migrations;
18pub mod password;
19pub mod permission;
20pub mod service;
21pub mod store;
22
23/// The built-in role every administrative permission belongs to. Held by the
24/// first operator, and by anyone who should administer the panel itself.
25pub const ROLE_ADMIN: &str = "admin";
26
27/// The built-in role for working with content rather than the panel. Empty on a
28/// bare framework: a permission joins it by naming it.
29pub const ROLE_EDITOR: &str = "editor";
30
31mod models;
32mod schema;
33
34pub use audit_listener::AuditListener;
35pub use error::AuthError;
36pub use migrations::{migrations, MODULE_ID};
37pub use models::{AccessEvent, BackendUser, BackendUserSummary};
38pub use permission::PermissionSet;
39pub use service::{
40    ActiveSession, AuditEntry, AuthConfig, AuthService, AuthenticatedUser, IssuedSession,
41    NewOperator, PasswordPolicy, Purged, RecalledSession, RememberCredential, RequestContext,
42    ResolvedSession, MIN_PASSWORD_LENGTH,
43};
44pub use store::{AccessRecord, AuditRecord};
45pub use store::{RevokeReason, SessionEnd};
46
47/// The `laterite.auth` module: the framework's backend-user auth tables.
48pub struct AuthModule;
49
50#[laterite_core::strata::async_trait]
51impl laterite_core::Module for AuthModule {
52    fn id(&self) -> laterite_core::ModuleId {
53        laterite_core::ModuleId::new(MODULE_ID)
54    }
55    /// Sweeps expired sessions and stay-signed-in credentials, so a boot
56    /// starts from tables holding only what a request could still use.
57    async fn boot(&self, cx: &laterite_core::BootCx<'_>) -> laterite_core::CoreResult<()> {
58        let now = chrono::Utc::now();
59        let sessions = store::delete_expired_sessions(cx.db, now)
60            .await
61            .map_err(|e| laterite_core::CoreError::Internal(e.to_string()))?;
62        let tokens = store::delete_expired_remember_tokens(cx.db, now)
63            .await
64            .map_err(|e| laterite_core::CoreError::Internal(e.to_string()))?;
65        if sessions + tokens > 0 {
66            tracing::info!(
67                sessions,
68                remember_tokens = tokens,
69                "purged expired credentials"
70            );
71        }
72        Ok(())
73    }
74    fn migrations(&self) -> laterite_core::MigrationSet {
75        migrations()
76    }
77    fn register(&self, registry: &mut laterite_core::Registry) {
78        registry.add(laterite_core::ModelListenerReg::all(std::sync::Arc::new(
79            audit_listener::AuditListener,
80        )));
81    }
82}