feat(proto): add separate client/user-agent gRPC services
This commit is contained in:
@@ -2,6 +2,14 @@
|
|||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
backend = "cargo:cargo-audit"
|
backend = "cargo:cargo-audit"
|
||||||
|
|
||||||
|
[[tools."cargo:cargo-features"]]
|
||||||
|
version = "1.0.0"
|
||||||
|
backend = "cargo:cargo-features"
|
||||||
|
|
||||||
|
[[tools."cargo:cargo-features-manager"]]
|
||||||
|
version = "0.11.1"
|
||||||
|
backend = "cargo:cargo-features-manager"
|
||||||
|
|
||||||
[[tools."cargo:cargo-vet"]]
|
[[tools."cargo:cargo-vet"]]
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
backend = "cargo:cargo-vet"
|
backend = "cargo:cargo-vet"
|
||||||
|
|||||||
@@ -6,3 +6,4 @@
|
|||||||
flutter = "3.38.9-stable"
|
flutter = "3.38.9-stable"
|
||||||
protoc = "29.6"
|
protoc = "29.6"
|
||||||
rust = "1.93.0"
|
rust = "1.93.0"
|
||||||
|
"cargo:cargo-features-manager" = "0.11.1"
|
||||||
|
|||||||
@@ -4,20 +4,35 @@ package arbiter;
|
|||||||
|
|
||||||
import "auth.proto";
|
import "auth.proto";
|
||||||
|
|
||||||
message ClientMessage {
|
message ClientRequest {
|
||||||
oneof payload {
|
oneof payload {
|
||||||
arbiter.auth.AuthChallengeRequest auth_challenge_request = 1;
|
arbiter.auth.ClientMessage auth_message = 1;
|
||||||
arbiter.auth.AuthChallengeSolution auth_challenge_solution = 2;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
message ServerMessage {
|
message ClientResponse {
|
||||||
oneof payload {
|
oneof payload {
|
||||||
arbiter.auth.AuthChallenge auth_challenge = 1;
|
arbiter.auth.ServerMessage auth_message = 1;
|
||||||
arbiter.auth.AuthResponse auth_response = 2;
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
service Server {
|
message UserAgentRequest {
|
||||||
rpc Communicate(stream ClientMessage) returns (stream ServerMessage);
|
oneof payload {
|
||||||
|
arbiter.auth.ClientMessage auth_message = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message UserAgentResponse {
|
||||||
|
oneof payload {
|
||||||
|
arbiter.auth.ServerMessage auth_message = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ServerInfo {
|
||||||
|
string version = 1;
|
||||||
|
bytes cert_public_key = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
service ArbiterService {
|
||||||
|
rpc Client(stream ClientRequest) returns (stream ClientResponse);
|
||||||
|
rpc UserAgent(stream UserAgentRequest) returns (stream UserAgentResponse);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,23 +5,37 @@ package arbiter.auth;
|
|||||||
import "google/protobuf/timestamp.proto";
|
import "google/protobuf/timestamp.proto";
|
||||||
|
|
||||||
message AuthChallengeRequest {
|
message AuthChallengeRequest {
|
||||||
bytes pubkey = 1;
|
bytes pubkey = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AuthChallenge {
|
message AuthChallenge {
|
||||||
bytes pubkey = 1;
|
bytes pubkey = 1;
|
||||||
bytes nonce = 2;
|
bytes nonce = 2;
|
||||||
google.protobuf.Timestamp minted = 3 ;
|
google.protobuf.Timestamp minted = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AuthChallengeSolution {
|
message AuthChallengeSolution {
|
||||||
AuthChallenge challenge = 1 ;
|
AuthChallenge challenge = 1;
|
||||||
bytes signature = 2;
|
bytes signature = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AuthResponse {
|
message AuthResponse {
|
||||||
string token = 1;
|
string token = 1;
|
||||||
string refresh_token = 2;
|
string refresh_token = 2;
|
||||||
google.protobuf.Timestamp expires_at = 3 ;
|
google.protobuf.Timestamp expires_at = 3;
|
||||||
google.protobuf.Timestamp refresh_expires_at = 4 ;
|
google.protobuf.Timestamp refresh_expires_at = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message ClientMessage {
|
||||||
|
oneof payload {
|
||||||
|
AuthChallengeRequest auth_challenge_request = 1;
|
||||||
|
AuthChallengeSolution auth_challenge_solution = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ServerMessage {
|
||||||
|
oneof payload {
|
||||||
|
AuthChallenge auth_challenge = 1;
|
||||||
|
AuthResponse auth_response = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
14
protobufs/unseal.proto
Normal file
14
protobufs/unseal.proto
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package arbiter.auth;
|
||||||
|
|
||||||
|
message UserAgentKeyRequest {}
|
||||||
|
|
||||||
|
message ServerKeyResponse {
|
||||||
|
bytes pubkey = 1;
|
||||||
|
}
|
||||||
|
message UserAgentSealedKey {
|
||||||
|
bytes sealed_key = 1;
|
||||||
|
bytes pubkey = 2;
|
||||||
|
bytes nonce = 3;
|
||||||
|
}
|
||||||
826
server/Cargo.lock
generated
826
server/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -3,12 +3,14 @@ members = [
|
|||||||
"crates/arbiter-client",
|
"crates/arbiter-client",
|
||||||
"crates/arbiter-proto",
|
"crates/arbiter-proto",
|
||||||
"crates/arbiter-server",
|
"crates/arbiter-server",
|
||||||
|
"crates/arbiter-useragent",
|
||||||
]
|
]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
|
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
prost = "0.14.3"
|
prost = "0.14.3"
|
||||||
tonic = "0.14.3"
|
tonic = { version = "0.14.3", features = ["tls-connect-info"] }
|
||||||
tracing = "0.1.44"
|
tracing = "0.1.44"
|
||||||
tokio = { version = "1.49.0", features = ["full"] }
|
tokio = { version = "1.49.0", features = ["full"] }
|
||||||
ed25519 = "3.0.0-rc.4"
|
ed25519 = "3.0.0-rc.4"
|
||||||
@@ -16,18 +18,7 @@ ed25519-dalek = "3.0.0-pre.6"
|
|||||||
chrono = { version = "0.4.43", features = ["serde"] }
|
chrono = { version = "0.4.43", features = ["serde"] }
|
||||||
rand = "0.10.0"
|
rand = "0.10.0"
|
||||||
uuid = "1.20.0"
|
uuid = "1.20.0"
|
||||||
|
rustls = "0.23.36"
|
||||||
[package]
|
smlang = "0.8.0"
|
||||||
name = "arbiter"
|
miette = { version = "7.6.0", features = ["fancy", "serde"] }
|
||||||
version = "0.1.0"
|
thiserror = "2.0.18"
|
||||||
edition = "2024"
|
|
||||||
repository = "https://git.markettakers.org/MarketTakers/arbiter"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,5 +2,6 @@
|
|||||||
name = "arbiter-client"
|
name = "arbiter-client"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
repository = "https://git.markettakers.org/MarketTakers/arbiter"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
name = "arbiter-proto"
|
name = "arbiter-proto"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
repository = "https://git.markettakers.org/MarketTakers/arbiter"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tonic.workspace = true
|
tonic.workspace = true
|
||||||
@@ -10,6 +11,8 @@ bytes = "1.11.1"
|
|||||||
prost-derive = "0.14.3"
|
prost-derive = "0.14.3"
|
||||||
prost-types = { version = "0.14.3", features = ["chrono"] }
|
prost-types = { version = "0.14.3", features = ["chrono"] }
|
||||||
tonic-prost = "0.14.3"
|
tonic-prost = "0.14.3"
|
||||||
|
rkyv = "0.8.15"
|
||||||
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
prost-build = "0.14.3"
|
prost-build = "0.14.3"
|
||||||
|
|||||||
@@ -4,4 +4,4 @@ pub mod proto {
|
|||||||
pub mod auth {
|
pub mod auth {
|
||||||
tonic::include_proto!("arbiter.auth");
|
tonic::include_proto!("arbiter.auth");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,10 +2,19 @@
|
|||||||
name = "arbiter-server"
|
name = "arbiter-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
repository = "https://git.markettakers.org/MarketTakers/arbiter"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
diesel = { version = "2.3.6", features = ["sqlite", "uuid", "time", "chrono", "serde_json"] }
|
diesel = { version = "2.3.6", features = ["sqlite", "uuid", "time", "chrono", "serde_json"] }
|
||||||
diesel-async = { version = "0.7.4", features = ["sqlite", "tokio", "migrations", "pool", "deadpool"] }
|
diesel-async = { version = "0.7.4", features = ["sqlite", "tokio", "migrations", "pool", "deadpool"] }
|
||||||
|
ed25519.workspace = true
|
||||||
|
ed25519-dalek.workspace = true
|
||||||
|
arbiter-proto.path = "../arbiter-proto"
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
tonic.workspace = true
|
tonic.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
|
rustls.workspace = true
|
||||||
|
smlang.workspace = true
|
||||||
|
miette.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
diesel_migrations = { version = "2.3.1", features = ["sqlite"] }
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# see https://diesel.rs/guides/configuring-diesel-cli
|
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
[print_schema]
|
[print_schema]
|
||||||
file = "src/schema.rs"
|
file = "src/db/schema.rs"
|
||||||
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
|
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
|
||||||
|
|
||||||
[migrations_directory]
|
[migrations_directory]
|
||||||
|
|||||||
@@ -1 +1,29 @@
|
|||||||
-- Your SQL goes here
|
-- This is a singleton
|
||||||
|
create table if not exists arbiter_settings (
|
||||||
|
root_key_enc blob, -- if null, means wasn't bootstrapped yet
|
||||||
|
cert_key blob not null,
|
||||||
|
cert blob not null
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
create table if not exists key_identity(
|
||||||
|
id integer primary key,
|
||||||
|
name text not null,
|
||||||
|
public_key text not null,
|
||||||
|
created_at integer not null default (unixepoch('now')),
|
||||||
|
updated_at integer not null default (unixepoch('now'))
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
create table if not exists useragent_client (
|
||||||
|
id integer primary key,
|
||||||
|
key_identity_id integer not null references key_identity(id) on delete cascade,
|
||||||
|
created_at integer not null default (unixepoch('now')),
|
||||||
|
updated_at integer not null default (unixepoch('now'))
|
||||||
|
) STRICT;
|
||||||
|
|
||||||
|
|
||||||
|
create table if not exists program_client(
|
||||||
|
id integer primary key,
|
||||||
|
key_identity_id integer not null references key_identity(id) on delete cascade,
|
||||||
|
created_at integer not null default (unixepoch('now')),
|
||||||
|
updated_at integer not null default (unixepoch('now'))
|
||||||
|
) STRICT;
|
||||||
83
server/crates/arbiter-server/src/db.rs
Normal file
83
server/crates/arbiter-server/src/db.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
use diesel::{Connection as _, SqliteConnection, connection::SimpleConnection as _};
|
||||||
|
use diesel_async::sync_connection_wrapper::SyncConnectionWrapper;
|
||||||
|
use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
|
||||||
|
use miette::Diagnostic;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub mod models;
|
||||||
|
pub mod schema;
|
||||||
|
|
||||||
|
pub type Database = SyncConnectionWrapper<SqliteConnection>;
|
||||||
|
|
||||||
|
static ARBITER_HOME: &'static str = ".arbiter";
|
||||||
|
static DB_FILE: &'static str = "db.sqlite";
|
||||||
|
|
||||||
|
const MIGRATIONS: EmbeddedMigrations = embed_migrations!();
|
||||||
|
|
||||||
|
#[derive(Error, Diagnostic, Debug)]
|
||||||
|
pub enum DatabaseSetupError {
|
||||||
|
#[error("Failed to determine home directory")]
|
||||||
|
#[diagnostic(code(arbiter::db::home_dir_error))]
|
||||||
|
HomeDir(Option<std::io::Error>),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
#[diagnostic(code(arbiter::db::connection_error))]
|
||||||
|
Connection(diesel::ConnectionError),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
#[diagnostic(code(arbiter::db::concurrency_error))]
|
||||||
|
ConcurrencySetup(diesel::result::Error),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
#[diagnostic(code(arbiter::db::migration_error))]
|
||||||
|
Migration(Box<dyn std::error::Error + Send + Sync>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn database_path() -> Result<std::path::PathBuf, DatabaseSetupError> {
|
||||||
|
let home_dir = std::env::home_dir().ok_or_else(|| DatabaseSetupError::HomeDir(None))?;
|
||||||
|
|
||||||
|
let arbiter_home = home_dir.join(ARBITER_HOME);
|
||||||
|
|
||||||
|
let db_path = arbiter_home.join(DB_FILE);
|
||||||
|
|
||||||
|
std::fs::create_dir_all(arbiter_home)
|
||||||
|
.map_err(|err| DatabaseSetupError::HomeDir(Some(err)))?;
|
||||||
|
|
||||||
|
Ok(db_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_concurrency(conn: &mut SqliteConnection) -> Result<(), diesel::result::Error> {
|
||||||
|
// see https://fractaledmind.github.io/2023/09/07/enhancing-rails-sqlite-fine-tuning/
|
||||||
|
// sleep if the database is busy, this corresponds to up to 2 seconds sleeping time.
|
||||||
|
conn.batch_execute("PRAGMA busy_timeout = 2000;")?;
|
||||||
|
// better write-concurrency
|
||||||
|
conn.batch_execute("PRAGMA journal_mode = WAL;")?;
|
||||||
|
// fsync only in critical moments
|
||||||
|
conn.batch_execute("PRAGMA synchronous = NORMAL;")?;
|
||||||
|
// write WAL changes back every 1000 pages, for an in average 1MB WAL file.
|
||||||
|
// May affect readers if number is increased
|
||||||
|
conn.batch_execute("PRAGMA wal_autocheckpoint = 1000;")?;
|
||||||
|
// free some space by truncating possibly massive WAL files from the last run
|
||||||
|
conn.batch_execute("PRAGMA wal_checkpoint(TRUNCATE);")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tracing::instrument]
|
||||||
|
pub fn connect() -> Result<Database, DatabaseSetupError> {
|
||||||
|
let database_url = format!(
|
||||||
|
"{}?mode=rwc",
|
||||||
|
database_path()?
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| DatabaseSetupError::HomeDir(None))?
|
||||||
|
);
|
||||||
|
let mut conn =
|
||||||
|
SqliteConnection::establish(&database_url).map_err(DatabaseSetupError::Connection)?;
|
||||||
|
|
||||||
|
setup_concurrency(&mut conn).map_err(DatabaseSetupError::ConcurrencySetup)?;
|
||||||
|
|
||||||
|
conn.run_pending_migrations(MIGRATIONS)
|
||||||
|
.map_err(DatabaseSetupError::Migration)?;
|
||||||
|
|
||||||
|
Ok(SyncConnectionWrapper::new(conn))
|
||||||
|
}
|
||||||
0
server/crates/arbiter-server/src/db/models.rs
Normal file
0
server/crates/arbiter-server/src/db/models.rs
Normal file
48
server/crates/arbiter-server/src/db/schema.rs
Normal file
48
server/crates/arbiter-server/src/db/schema.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
// @generated automatically by Diesel CLI.
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
arbiter_settings (rowid) {
|
||||||
|
rowid -> Integer,
|
||||||
|
root_key_enc -> Nullable<Binary>,
|
||||||
|
cert_key -> Binary,
|
||||||
|
cert -> Binary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
key_identity (id) {
|
||||||
|
id -> Nullable<Integer>,
|
||||||
|
name -> Text,
|
||||||
|
public_key -> Text,
|
||||||
|
created_at -> Integer,
|
||||||
|
updated_at -> Integer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
program_client (id) {
|
||||||
|
id -> Nullable<Integer>,
|
||||||
|
key_identity_id -> Integer,
|
||||||
|
created_at -> Integer,
|
||||||
|
updated_at -> Integer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
useragent_client (id) {
|
||||||
|
id -> Nullable<Integer>,
|
||||||
|
key_identity_id -> Integer,
|
||||||
|
created_at -> Integer,
|
||||||
|
updated_at -> Integer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::joinable!(program_client -> key_identity (key_identity_id));
|
||||||
|
diesel::joinable!(useragent_client -> key_identity (key_identity_id));
|
||||||
|
|
||||||
|
diesel::allow_tables_to_appear_in_same_query!(
|
||||||
|
arbiter_settings,
|
||||||
|
key_identity,
|
||||||
|
program_client,
|
||||||
|
useragent_client,
|
||||||
|
);
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
mod db;
|
||||||
|
|
||||||
#[tokio::main]
|
pub struct Server {
|
||||||
pub async fn main() {
|
pub db: db::Database,
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
6
server/crates/arbiter-useragent/Cargo.toml
Normal file
6
server/crates/arbiter-useragent/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[package]
|
||||||
|
name = "arbiter-useragent"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
14
server/crates/arbiter-useragent/src/lib.rs
Normal file
14
server/crates/arbiter-useragent/src/lib.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
pub fn add(left: u64, right: u64) -> u64 {
|
||||||
|
left + right
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
let result = add(2, 2);
|
||||||
|
assert_eq!(result, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
# For documentation on how to configure this file,
|
|
||||||
# see https://diesel.rs/guides/configuring-diesel-cli
|
|
||||||
|
|
||||||
[print_schema]
|
|
||||||
file = "src/schema.rs"
|
|
||||||
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
|
|
||||||
|
|
||||||
[migrations_directory]
|
|
||||||
dir = "migrations"
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
println!("Hello, world!");
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user