Skip to main content

basileus/
lib.rs

1pub mod err;
2pub mod pass;
3pub mod perm;
4pub mod pkce;
5pub mod prelude;
6pub mod token;
7pub mod user;
8
9use std::path::PathBuf;
10
11use sqlx::{SqlitePool, query, sqlite::SqliteConnectOptions};
12
13use token::TokenModule;
14use tracing::{info, trace};
15
16pub use prelude::*;
17
18use crate::pkce::{PkceConfig, PkceModule};
19
20fn rand_buf<const N: usize>() -> [u8; N] {
21    let mut buf = [0u8; N];
22    getrandom::fill(&mut buf).unwrap();
23    buf
24}
25
26/// Configuration for [`Basileus`].
27#[derive(Clone)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub struct Config {
30    /// Path to the SQLite storage.
31    #[cfg_attr(feature = "serde", serde(rename = "database-path"))]
32    pub db: PathBuf,
33    /// PKCE configuration.
34    #[cfg_attr(feature = "serde", serde(rename = "pkce"))]
35    #[cfg_attr(feature = "serde", serde(default))]
36    pub pkce: PkceConfig,
37}
38
39impl Default for Config {
40    fn default() -> Self {
41        Self {
42            db: "./basileus.db".into(),
43            pkce: Default::default(),
44        }
45    }
46}
47
48/// Entry point for the library.
49pub struct Basileus {
50    /// Configurations.
51    pub config: Config,
52    /// Database connection.
53    db: SqlitePool,
54    /// Token management module.
55    token: TokenModule,
56    pkce: PkceModule,
57}
58
59/// Initialize the database.
60pub const DB_INIT: &str = r#"
61CREATE TABLE IF NOT EXISTS pubkey (
62    user TEXT NOT NULL PRIMARY KEY,
63    key BLOB NOT NULL,
64    FOREIGN KEY (user) REFERENCES user(user) ON DELETE CASCADE
65);
66CREATE INDEX IF NOT EXISTS idx_pubkey_user ON pubkey (user);
67CREATE TABLE IF NOT EXISTS token (
68    user TEXT NOT NULL PRIMARY KEY,
69    token TEXT,
70    FOREIGN KEY (user) REFERENCES user(user) ON DELETE CASCADE
71);
72CREATE INDEX IF NOT EXISTS idx_token_user ON token (user);
73"#;
74
75impl Basileus {
76    /// Initialize the library, creating the database if missing.
77    pub async fn new(config: Config) -> Result<Self, sqlx::error::Error> {
78        let opt = SqliteConnectOptions::default()
79            .filename(&config.db)
80            .create_if_missing(true);
81        let db = SqlitePool::connect_with(opt).await?;
82        info!("connected to {:?}", config.db);
83        query(user::DB_INIT).execute(&db).await?;
84        query(pass::DB_INIT).execute(&db).await?;
85        query(perm::DB_INIT).execute(&db).await?;
86        query(DB_INIT).execute(&db).await?;
87        trace!("database initialized");
88        let pkce = PkceModule::new(config.pkce.clone());
89        Ok(Self {
90            config,
91            db,
92            token: TokenModule::new(),
93            pkce,
94        })
95    }
96
97    /// Count the number of users.
98    pub async fn user_cnt(&self) -> Result<i64, sqlx::error::Error> {
99        let (cnt,): (i64,) = sqlx::query_as("SELECT COUNT(*) FROM user")
100            .fetch_one(&self.db)
101            .await?;
102        Ok(cnt)
103    }
104}