feat(auth): simplify auth model and implement bootstrap flow
Remove key_identity indirection table, storing public keys and nonces directly on client tables. Replace AuthResponse with AuthOk, add a BootstrapActor to manage token lifecycle, and move user agent stream handling into the actor module.
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
use arbiter_proto::{BOOTSTRAP_TOKEN_PATH, home_path};
|
||||
use diesel::{QueryDsl, dsl::exists, select};
|
||||
use diesel::{
|
||||
ExpressionMethods, QueryDsl,
|
||||
dsl::{count, exists},
|
||||
select,
|
||||
};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use kameo::{Actor, messages};
|
||||
use memsafe::MemSafe;
|
||||
use miette::Diagnostic;
|
||||
use rand::{RngExt, distr::StandardUniform, make_rng, rngs::StdRng};
|
||||
@@ -9,7 +14,10 @@ use thiserror::Error;
|
||||
use tracing::info;
|
||||
use zeroize::{Zeroize, Zeroizing};
|
||||
|
||||
use crate::db::{self, schema};
|
||||
use crate::{
|
||||
context::{self, ServerContext},
|
||||
db::{self, DatabasePool, schema},
|
||||
};
|
||||
|
||||
const TOKEN_LENGTH: usize = 64;
|
||||
|
||||
@@ -28,3 +36,70 @@ pub async fn generate_token() -> Result<String, std::io::Error> {
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, Diagnostic)]
|
||||
pub enum BootstrapError {
|
||||
#[error("Database error: {0}")]
|
||||
#[diagnostic(code(arbiter_server::bootstrap::database))]
|
||||
Database(#[from] db::PoolError),
|
||||
|
||||
#[error("Database query error: {0}")]
|
||||
#[diagnostic(code(arbiter_server::bootstrap::database_query))]
|
||||
Query(#[from] diesel::result::Error),
|
||||
|
||||
#[error("I/O error: {0}")]
|
||||
#[diagnostic(code(arbiter_server::bootstrap::io))]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
#[derive(Actor)]
|
||||
pub struct BootstrapActor {
|
||||
token: Option<String>,
|
||||
}
|
||||
|
||||
impl BootstrapActor {
|
||||
pub async fn new(db: &DatabasePool) -> Result<Self, BootstrapError> {
|
||||
let mut conn = db.get().await?;
|
||||
|
||||
let needs_token: bool = select(exists(
|
||||
schema::useragent_client::table
|
||||
.filter(schema::useragent_client::id.eq(schema::useragent_client::id)), // Just check if the table is empty
|
||||
))
|
||||
.first(&mut conn)
|
||||
.await?;
|
||||
|
||||
drop(conn);
|
||||
|
||||
let token = if needs_token {
|
||||
let token = generate_token().await?;
|
||||
info!(%token, "Generated bootstrap token");
|
||||
tokio::fs::write(home_path()?.join(BOOTSTRAP_TOKEN_PATH), token.as_str()).await?;
|
||||
Some(token)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Self { token })
|
||||
}
|
||||
}
|
||||
|
||||
#[messages]
|
||||
impl BootstrapActor {
|
||||
#[message]
|
||||
pub fn is_correct_token(&self, token: String) -> bool {
|
||||
match &self.token {
|
||||
Some(expected) => *expected == token,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[message]
|
||||
pub fn consume_token(&mut self, token: String) -> bool {
|
||||
if self.is_correct_token(token) {
|
||||
self.token = None;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user