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)
Aydin Mercan 2022-05-27 10:48:32 +03:00
parent d9699ecea0
commit e0173496c6
No known key found for this signature in database
8 changed files with 142 additions and 26 deletions

View File

@ -11,7 +11,7 @@ For administration and usage, a reference handbook will be provided in the futur
## Usage
```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.

View File

@ -28,6 +28,18 @@ CREATE TABLE IF NOT EXISTS oidc_claims (
updated_at INTEGER
) 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 (
cookie BLOB PRIMARY KEY NOT NULL,
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 (
selector TEXT PRIMARY KEY NOT NULL,
uuid BLOB NOT NULL REFERENCES users(uuid) ON DELETE CASCADE,
verifier TEXT NOT NULL
expires_at TEXT NOT NULL,
verifier TEXT NOT NULL,
expires_at TEXT NOT NULL
) STRICT;

View File

@ -1,9 +1,9 @@
use std::sync::Arc;
use axum::{AddExtensionLayer, Router};
use axum::Router;
use crate::CsrfMap;
pub fn setup_router(csrf_map: Arc<CsrfMap>) -> Router {
Router::new().layer(AddExtensionLayer::new(csrf_map))
Router::new()
}

View File

@ -1,8 +1,11 @@
use anyhow::{bail, Result};
use tracing::{debug, error, info, warn};
use rusqlite::Connection;
pub fn setup_connection_and_for_replication(path: String) -> Result<Connection> {
debug!(target = ?path, "opening database");
let db = Connection::open(path)?;
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");
}
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, "synchronous", "NORMAL")?;
db.pragma_update(None, "foreign_keys", "ON")?;

11
src/engine/jwk.rs Normal file
View File

@ -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 {}

3
src/engine/mod.rs Normal file
View File

@ -0,0 +1,3 @@
mod jwk;
pub use jwk::JwkEngine;

View File

@ -1,43 +1,113 @@
mod controller;
mod database;
mod engine;
mod server;
use std::collections::HashMap;
use std::sync::Arc;
use std::option::Option;
use std::process::{ExitCode, Termination};
use anyhow::Result;
use clap::Parser;
use clap::{Parser, Subcommand};
use parking_lot::RwLock;
use crate::controller::setup_router;
pub type CsrfMap = RwLock<HashMap<String, String>>;
#[derive(Parser)]
struct Args {
#[clap(short, long)]
pub database: String,
#[repr(u8)]
enum ServerResult {
Success = 0,
Failure = 1,
}
#[clap(short, long)]
pub port: u16,
impl Termination for ServerResult {
fn report(self) -> ExitCode {
ExitCode::from(self as u8)
}
}
#[derive(Parser)]
#[clap(version)]
struct Args {
#[clap(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
/// Start the server
Run {
#[clap(short, long)]
database: String,
#[clap(short, long)]
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<()> {
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(())
}
#[tokio::main]
async fn main() -> Result<()> {
fn main() -> ServerResult {
if setup_default_subscriber().is_err() {
return ServerResult::Failure;
}
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()));
let db = crate::database::setup_connection_and_for_replication(args.database)?;
let router = setup_router(csrf_map);
Ok(())
ServerResult::Success
}

11
src/server.rs Normal file
View File

@ -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;
}
}