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
|
||||
|
||||
```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.
|
||||
|
|
16
schema.sql
16
schema.sql
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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")?;
|
||||
|
|
|
@ -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;
|
112
src/main.rs
112
src/main.rs
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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