Skip to main content

codlet_core/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4//! # codlet-core
5//!
6//! Runtime-neutral authentication primitives. This crate contains pure types,
7//! policy objects, cryptographic lookup-key derivation, lifecycle state
8//! machines, and storage *traits*. It deliberately contains no web framework,
9//! database, or async-executor dependencies (RFC-002).
10//!
11//! ## Boundary
12//!
13//! codlet authenticates a subject. The host application authorizes that
14//! subject (RFC-001). Nothing in this crate decides community membership,
15//! roles, permissions, or resource access.
16//!
17//! ## Status
18//!
19//! This release implements the first cryptographic primitives:
20//!
21//! - [`code`]    — code policy, generation, normalization, validation (RFC-003)
22//! - [`hashing`] — HMAC lookup-key derivation, key providers, domain
23//!                 separation, key versioning (RFC-004)
24//! - [`rng`]     — fail-closed randomness abstraction (RFC-020)
25//! - [`secret`]  — redacted secret newtypes and opaque IDs (RFC-019 foundation)
26//! - [`error`]   — internal error layer (RFC-021)
27//!
28//! Forthcoming modules (added with their RFCs):
29//!
30//! - `state` — pure lifecycle classifiers (RFC-005/006/007)
31//! - `store` — storage traits (RFC-005..008)
32//!
33//! ## Example
34//!
35//! Generate a code and derive the value that would be stored (never the
36//! plaintext). End-to-end redemption needs the storage traits, still to come.
37//!
38//! ```
39//! use codlet_core::{CodePolicy, SecretDomain, SecretHasher, StaticKeyProvider};
40//! use codlet_core::{generate_code, validate_code_input};
41//! use codlet_core::rng::SystemRandom;
42//! use std::time::Duration;
43//!
44//! let policy = CodePolicy::default_human(Duration::from_secs(24 * 3600)).unwrap();
45//!
46//! let mut rng = SystemRandom::new();
47//! let code = generate_code(&policy, &mut rng).unwrap();
48//!
49//! let hasher = SecretHasher::new(
50//!     StaticKeyProvider::single("v1", b"real-key-from-secret-manager".to_vec()).unwrap(),
51//! );
52//! let normalized = validate_code_input(code.expose(), &policy).unwrap();
53//! let (lookup_key, key_version) =
54//!     hasher.lookup_key(SecretDomain::Code, &normalized).unwrap();
55//! assert_eq!(key_version.as_str(), "v1");
56//! assert_eq!(lookup_key.as_str().len(), 64);
57//! // Persist `lookup_key` + `key_version`; never persist `code`.
58//! ```
59
60/// The codlet wire/format version embedded in domain-separated HMAC inputs.
61///
62/// Bumping this is a breaking change to every stored lookup key and MUST be
63/// accompanied by a key-version migration (RFC-004).
64pub const FORMAT_VERSION: &str = "codlet/v1";
65
66pub mod code;
67pub mod error;
68pub mod hashing;
69pub mod rng;
70pub mod secret;
71
72// Convenience re-exports for the most common types.
73pub use code::{Alphabet, CodePolicy, generate_code, normalize, validate_code_input};
74pub use error::{CodeInputError, KeyError, PolicyError, RandomError};
75pub use hashing::{
76    HmacKeyRef, KeyProvider, KeyVersion, LookupKey, SecretDomain, SecretHasher, StaticKeyProvider,
77};
78pub use rng::{RandomSource, SystemRandom};
79pub use secret::{
80    CodeId, FormTokenSecret, PlainCode, SecretString, SessionId, SessionSecret, SubjectId,
81};
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    #[test]
88    fn format_version_is_stable() {
89        // Guard against an accidental format bump. Changing this string is a
90        // breaking change requiring a key-version migration (RFC-004).
91        assert_eq!(FORMAT_VERSION, "codlet/v1");
92    }
93}