origin_auth/lib.rs
1//! OAuth 2.0 for native applications (ADR-0015).
2//!
3//! Implements the authorization code flow with PKCE, a loopback redirect, token
4//! storage in the OS credential store, and transparent refresh.
5//!
6//! ```text
7//! LoopbackRedirect::bind() → redirect_uri
8//! AuthorizationFlow::begin() → authorization url + state + verifier
9//! Opener::open_url() → the user consents in their browser
10//! RedirectListener::wait() → code (state verified)
11//! AuthorizationFlow::exchange() → TokenSet
12//! TokenStore::save() → OS credential store
13//! ```
14//!
15//! Afterwards nothing calls the flow again: [`AccessTokenProvider`] hands out a valid
16//! access token and refreshes it when it is about to expire.
17
18mod config;
19mod flow;
20mod pkce;
21mod provider;
22mod redirect;
23mod store;
24mod token;
25
26#[cfg(feature = "testing")]
27pub mod testing;
28
29pub use config::OAuthConfig;
30pub use flow::{AuthorizationFlow, PendingAuthorization};
31pub use pkce::Pkce;
32pub use provider::AccessTokenProvider;
33pub use redirect::{AuthorizationCode, RedirectListener};
34pub use store::TokenStore;
35pub use token::TokenSet;
36
37/// Random bytes, base64url-encoded without padding.
38///
39/// Used for the PKCE verifier and the `state` parameter — both must be
40/// unguessable, and both travel in URLs.
41pub(crate) fn random_token(bytes: usize) -> origin_domain::Result<String> {
42 use base64::Engine as _;
43
44 let mut buffer = vec![0u8; bytes];
45 getrandom::fill(&mut buffer).map_err(|error| {
46 origin_domain::AppError::internal(format!("no secure randomness available: {error}"))
47 })?;
48
49 Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(buffer))
50}