main: draft layout for subcommands & identity
I think that API key generation and revocation should be done by calling the binary. Also, nuking all keys should be accessible. Public identity regeneration can use `init` again for now. I am thinking about seperating between confidential and public clients in the structure that keeps track of them. The map keeping track of the confidential should use the client base url as well. Expecting to overhaul how its done after checking RFC 8725 and other JOSE damage reduction strategies more throughly. (ugh)
parent
d9699ecea0
commit
e0173496c6
|
@ -11,7 +11,7 @@ For administration and usage, a reference handbook will be provided in the futur
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
chibiauth --database=/path/to/database/of/chibiauth.db --port 9909
|
chibiauth run --database=/path/to/database/of/chibiauth.db --port 9909 --base-url 'https://example.com'
|
||||||
```
|
```
|
||||||
|
|
||||||
* ChibiAuth will bind to localhost, bring your own TLS terminator.
|
* ChibiAuth will bind to localhost, bring your own TLS terminator.
|
||||||
|
|
16
schema.sql
16
schema.sql
|
@ -28,6 +28,18 @@ CREATE TABLE IF NOT EXISTS oidc_claims (
|
||||||
updated_at INTEGER
|
updated_at INTEGER
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS public_jwk (
|
||||||
|
key_id TEXT PRIMARY KEY NOT NULL,
|
||||||
|
algorithm TEXT NOT NULL,
|
||||||
|
pem TEXT NOT NULL
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS secret_jwk (
|
||||||
|
key_id TEXT PRIMARY KEY NOT NULL,
|
||||||
|
algorithm TEXT NOT NULL,
|
||||||
|
secret TEXT NOT NULL
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS sessions (
|
CREATE TABLE IF NOT EXISTS sessions (
|
||||||
cookie BLOB PRIMARY KEY NOT NULL,
|
cookie BLOB PRIMARY KEY NOT NULL,
|
||||||
uuid BLOB NOT NULL REFERENCES users(uuid) ON DELETE CASCADE
|
uuid BLOB NOT NULL REFERENCES users(uuid) ON DELETE CASCADE
|
||||||
|
@ -36,6 +48,6 @@ CREATE TABLE IF NOT EXISTS sessions (
|
||||||
CREATE TABLE IF NOT EXISTS reset_token (
|
CREATE TABLE IF NOT EXISTS reset_token (
|
||||||
selector TEXT PRIMARY KEY NOT NULL,
|
selector TEXT PRIMARY KEY NOT NULL,
|
||||||
uuid BLOB NOT NULL REFERENCES users(uuid) ON DELETE CASCADE,
|
uuid BLOB NOT NULL REFERENCES users(uuid) ON DELETE CASCADE,
|
||||||
verifier TEXT NOT NULL
|
verifier TEXT NOT NULL,
|
||||||
expires_at TEXT NOT NULL,
|
expires_at TEXT NOT NULL
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::{AddExtensionLayer, Router};
|
use axum::Router;
|
||||||
|
|
||||||
use crate::CsrfMap;
|
use crate::CsrfMap;
|
||||||
|
|
||||||
pub fn setup_router(csrf_map: Arc<CsrfMap>) -> Router {
|
pub fn setup_router(csrf_map: Arc<CsrfMap>) -> Router {
|
||||||
Router::new().layer(AddExtensionLayer::new(csrf_map))
|
Router::new()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
|
use tracing::{debug, error, info, warn};
|
||||||
|
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
|
|
||||||
pub fn setup_connection_and_for_replication(path: String) -> Result<Connection> {
|
pub fn setup_connection_and_for_replication(path: String) -> Result<Connection> {
|
||||||
|
debug!(target = ?path, "opening database");
|
||||||
|
|
||||||
let db = Connection::open(path)?;
|
let db = Connection::open(path)?;
|
||||||
|
|
||||||
let mode: String = db.pragma_update_and_check(None, "journal_mode", "WAL", |row| row.get(0))?;
|
let mode: String = db.pragma_update_and_check(None, "journal_mode", "WAL", |row| row.get(0))?;
|
||||||
|
@ -11,6 +14,12 @@ pub fn setup_connection_and_for_replication(path: String) -> Result<Connection>
|
||||||
bail!("couldn't set journaling to WAL");
|
bail!("couldn't set journaling to WAL");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let secure_delete: u64 = db.pragma_query_value(None, "secure_delete", |row| row.get(0))?;
|
||||||
|
|
||||||
|
if secure_delete == 0 {
|
||||||
|
warn!("secure delete is off, beware of lingering secrets in the database");
|
||||||
|
}
|
||||||
|
|
||||||
db.pragma_update(None, "busy_timeout", "5000")?;
|
db.pragma_update(None, "busy_timeout", "5000")?;
|
||||||
db.pragma_update(None, "synchronous", "NORMAL")?;
|
db.pragma_update(None, "synchronous", "NORMAL")?;
|
||||||
db.pragma_update(None, "foreign_keys", "ON")?;
|
db.pragma_update(None, "foreign_keys", "ON")?;
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub struct JwkEngine {
|
||||||
|
/// For publicly listed keys.
|
||||||
|
public: HashMap<String, String>,
|
||||||
|
|
||||||
|
/// For confidential clients using HS256/384/512.
|
||||||
|
secret: HashMap<(String, String), String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl JwkEngine {}
|
|
@ -0,0 +1,3 @@
|
||||||
|
mod jwk;
|
||||||
|
|
||||||
|
pub use jwk::JwkEngine;
|
104
src/main.rs
104
src/main.rs
|
@ -1,43 +1,113 @@
|
||||||
mod controller;
|
mod controller;
|
||||||
mod database;
|
mod database;
|
||||||
|
mod engine;
|
||||||
|
mod server;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::option::Option;
|
||||||
|
use std::process::{ExitCode, Termination};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::{Parser, Subcommand};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
|
|
||||||
use crate::controller::setup_router;
|
|
||||||
|
|
||||||
pub type CsrfMap = RwLock<HashMap<String, String>>;
|
pub type CsrfMap = RwLock<HashMap<String, String>>;
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
enum ServerResult {
|
||||||
|
Success = 0,
|
||||||
|
Failure = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Termination for ServerResult {
|
||||||
|
fn report(self) -> ExitCode {
|
||||||
|
ExitCode::from(self as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
#[clap(version)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
#[clap(subcommand)]
|
||||||
|
command: Command,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
enum Command {
|
||||||
|
/// Start the server
|
||||||
|
Run {
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub database: String,
|
database: String,
|
||||||
|
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
pub port: u16,
|
url: String,
|
||||||
|
|
||||||
|
#[clap(short, long)]
|
||||||
|
port: u16,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Initialize the database and/or regenerate public identities
|
||||||
|
Init {
|
||||||
|
#[clap(short, long)]
|
||||||
|
database: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Control API keys
|
||||||
|
Api {
|
||||||
|
#[clap(short, long)]
|
||||||
|
database: String,
|
||||||
|
|
||||||
|
#[clap(short, long)]
|
||||||
|
generate: bool,
|
||||||
|
|
||||||
|
#[clap(short, long)]
|
||||||
|
revoke: Option<String>,
|
||||||
|
|
||||||
|
#[clap(short = 'R', long)]
|
||||||
|
revoke_all: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_default_subscriber() -> Result<()> {
|
fn setup_default_subscriber() -> Result<()> {
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt().pretty().init();
|
||||||
|
|
||||||
|
std::panic::set_hook(Box::new(|panic| {
|
||||||
|
if let Some(location) = panic.location() {
|
||||||
|
tracing::error!(
|
||||||
|
message = %panic,
|
||||||
|
panic.file = location.file(),
|
||||||
|
panic.line = location.line(),
|
||||||
|
panic.column = location.column(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
tracing::error!(message = %panic);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
fn main() -> ServerResult {
|
||||||
async fn main() -> Result<()> {
|
if setup_default_subscriber().is_err() {
|
||||||
|
return ServerResult::Failure;
|
||||||
|
}
|
||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
setup_default_subscriber()?;
|
match args.command {
|
||||||
|
Command::Init { database } => {}
|
||||||
|
Command::Run {
|
||||||
|
database,
|
||||||
|
port,
|
||||||
|
url,
|
||||||
|
} => {}
|
||||||
|
Command::Api {
|
||||||
|
database,
|
||||||
|
generate,
|
||||||
|
revoke,
|
||||||
|
revoke_all,
|
||||||
|
} => {}
|
||||||
|
}
|
||||||
|
|
||||||
let csrf_map = Arc::new(RwLock::new(HashMap::<String, String>::new()));
|
ServerResult::Success
|
||||||
|
|
||||||
let db = crate::database::setup_connection_and_for_replication(args.database)?;
|
|
||||||
|
|
||||||
let router = setup_router(csrf_map);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn run() {
|
||||||
|
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
if let Err(err) = runtime {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue