refactor: rename to to better reflect meaning
This commit is contained in:
20
AGENTS.md
20
AGENTS.md
@@ -6,7 +6,7 @@ This file provides guidance to Codex (Codex.ai/code) when working with code in t
|
|||||||
|
|
||||||
Arbiter is a **permissioned signing service** for cryptocurrency wallets. It consists of:
|
Arbiter is a **permissioned signing service** for cryptocurrency wallets. It consists of:
|
||||||
- **`server/`** — Rust gRPC daemon that holds encrypted keys and enforces policies
|
- **`server/`** — Rust gRPC daemon that holds encrypted keys and enforces policies
|
||||||
- **`useragent/`** — Flutter desktop app (macOS/Windows) with a Rust backend via Rinf
|
- **`operator/`** — Flutter desktop app (macOS/Windows) with a Rust backend via Rinf
|
||||||
- **`protobufs/`** — Protocol Buffer definitions shared between server and client
|
- **`protobufs/`** — Protocol Buffer definitions shared between server and client
|
||||||
|
|
||||||
The vault never exposes key material; it only produces signatures when requests satisfy configured policies.
|
The vault never exposes key material; it only produces signatures when requests satisfy configured policies.
|
||||||
@@ -28,7 +28,7 @@ Key versions: Rust 1.93.0 (with clippy), Flutter 3.38.9-stable, protoc 29.6, die
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `arbiter-proto` | Generated gRPC stubs + protobuf types; compiled from `protobufs/*.proto` via `tonic-prost-build` |
|
| `arbiter-proto` | Generated gRPC stubs + protobuf types; compiled from `protobufs/*.proto` via `tonic-prost-build` |
|
||||||
| `arbiter-server` | Main daemon — actors, DB, EVM policy engine, gRPC service implementation |
|
| `arbiter-server` | Main daemon — actors, DB, EVM policy engine, gRPC service implementation |
|
||||||
| `arbiter-useragent` | Rust client library for the user agent side of the gRPC protocol |
|
| `arbiter-operator` | Rust client library for the operator side of the gRPC protocol |
|
||||||
| `arbiter-client` | Rust client library for SDK clients |
|
| `arbiter-client` | Rust client library for SDK clients |
|
||||||
|
|
||||||
### Common Commands
|
### Common Commands
|
||||||
@@ -67,10 +67,10 @@ The server is actor-based using the **kameo** crate. All long-lived state lives
|
|||||||
|
|
||||||
- **`Bootstrapper`** — Manages the one-time bootstrap token written to `~/.arbiter/bootstrap_token` on first run.
|
- **`Bootstrapper`** — Manages the one-time bootstrap token written to `~/.arbiter/bootstrap_token` on first run.
|
||||||
- **`Vault`** — Holds the encrypted root key and manages the Sealed/Unsealed vault state machine. On unseal, decrypts the root key into a `memsafe` hardened memory cell.
|
- **`Vault`** — Holds the encrypted root key and manages the Sealed/Unsealed vault state machine. On unseal, decrypts the root key into a `memsafe` hardened memory cell.
|
||||||
- **`FlowCoordinator`** — Coordinates cross-connection flow between user agents and SDK clients.
|
- **`FlowCoordinator`** — Coordinates cross-connection flow between operators and SDK clients.
|
||||||
- **`EvmActor`** — Handles EVM transaction policy enforcement and signing.
|
- **`EvmActor`** — Handles EVM transaction policy enforcement and signing.
|
||||||
|
|
||||||
Per-connection actors live under `actors/user_agent/` and `actors/client/`, each with `auth` (challenge-response authentication) and `session` (post-auth operations) sub-modules.
|
Per-connection actors live under `actors/operator/` and `actors/client/`, each with `auth` (challenge-response authentication) and `session` (post-auth operations) sub-modules.
|
||||||
|
|
||||||
**Database:** SQLite via `diesel-async` + `bb8` connection pool. Schema managed by embedded Diesel migrations in `crates/arbiter-server/migrations/`. DB file lives at `~/.arbiter/arbiter.sqlite`. Tests use a temp-file DB via `db::create_test_pool()`.
|
**Database:** SQLite via `diesel-async` + `bb8` connection pool. Schema managed by embedded Diesel migrations in `crates/arbiter-server/migrations/`. DB file lives at `~/.arbiter/arbiter.sqlite`. Tests use a temp-file DB via `db::create_test_pool()`.
|
||||||
|
|
||||||
@@ -121,20 +121,20 @@ pub fn verify(&self, nonce: i32, context: &[u8], signature: &Signature) -> bool
|
|||||||
|
|
||||||
This forces callers to either use the return value or explicitly ignore it with `let _ = ...;`, preventing silent failures.
|
This forces callers to either use the return value or explicitly ignore it with `let _ = ...;`, preventing silent failures.
|
||||||
|
|
||||||
## User Agent (Flutter + Rinf at `useragent/`)
|
## Operator (Flutter + Rinf at `operator/`)
|
||||||
|
|
||||||
The Flutter app uses [Rinf](https://rinf.cunarist.org) to call Rust code. The Rust logic lives in `useragent/native/hub/` as a separate crate that uses `arbiter-useragent` for the gRPC client.
|
The Flutter app uses [Rinf](https://rinf.cunarist.org) to call Rust code. The Rust logic lives in `operator/native/hub/` as a separate crate that uses `arbiter-operator` for the gRPC client.
|
||||||
|
|
||||||
Communication between Dart and Rust uses typed **signals** defined in `useragent/native/hub/src/signals/`. After modifying signal structs, regenerate Dart bindings:
|
Communication between Dart and Rust uses typed **signals** defined in `operator/native/hub/src/signals/`. After modifying signal structs, regenerate Dart bindings:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && rinf gen
|
cd operator && rinf gen
|
||||||
```
|
```
|
||||||
|
|
||||||
### Common Commands
|
### Common Commands
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent
|
cd operator
|
||||||
|
|
||||||
# Run the app (macOS or Windows)
|
# Run the app (macOS or Windows)
|
||||||
flutter run
|
flutter run
|
||||||
@@ -146,4 +146,4 @@ rinf gen
|
|||||||
flutter analyze
|
flutter analyze
|
||||||
```
|
```
|
||||||
|
|
||||||
The Rinf Rust entry point is `useragent/native/hub/src/lib.rs`. It spawns actors defined in `useragent/native/hub/src/actors/` which handle Dart↔server communication via signals.
|
The Rinf Rust entry point is `operator/native/hub/src/lib.rs`. It spawns actors defined in `operator/native/hub/src/actors/` which handle Dart↔server communication via signals.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
## Security warning
|
## Security warning
|
||||||
Arbiter can't meaningfully protect against host compromise. Potential attack flow:
|
Arbiter can't meaningfully protect against host compromise. Potential attack flow:
|
||||||
- Attacker steals TLS keys from database
|
- Attacker steals TLS keys from database
|
||||||
- Pretends to be server; just accepts user agent challenge solutions
|
- Pretends to be server; just accepts operator challenge solutions
|
||||||
- Pretend to be in sealed state and performing DH with client
|
- Pretend to be in sealed state and performing DH with client
|
||||||
- Steals user password and derives seal key
|
- Steals user password and derives seal key
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Arbiter is a permissioned signing service for cryptocurrency wallets. It runs as
|
|||||||
|
|
||||||
Arbiter distinguishes two kinds of peers:
|
Arbiter distinguishes two kinds of peers:
|
||||||
|
|
||||||
- **User Agent** — A client application used by the owner to manage the vault (create wallets, approve SDK clients, configure policies).
|
- **Operator** — A client application used by the owner to manage the vault (create wallets, approve SDK clients, configure policies).
|
||||||
- **SDK Client** — A consumer of signing capabilities, typically an automation tool. In the future, this could include a browser-based wallet.
|
- **SDK Client** — A consumer of signing capabilities, typically an automation tool. In the future, this could include a browser-based wallet.
|
||||||
- **Recovery Operator** — A dormant recovery participant with narrowly scoped authority used only for custody recovery and operator replacement.
|
- **Recovery Operator** — A dormant recovery participant with narrowly scoped authority used only for custody recovery and operator replacement.
|
||||||
|
|
||||||
@@ -30,24 +30,24 @@ All peers authenticate via public-key cryptography using a challenge-response pr
|
|||||||
|
|
||||||
Authentication challenges are per-connection, ephemeral values. They are not persisted in the peer tables, and peer records store no challenge state.
|
Authentication challenges are per-connection, ephemeral values. They are not persisted in the peer tables, and peer records store no challenge state.
|
||||||
|
|
||||||
### 2.2 User Agent Bootstrap
|
### 2.2 Operator Bootstrap
|
||||||
|
|
||||||
On first run — when no User Agents are registered — the server generates a one-time bootstrap token. It is made available in two ways:
|
On first run — when no Operators are registered — the server generates a one-time bootstrap token. It is made available in two ways:
|
||||||
|
|
||||||
- **Local setup:** Written to `~/.arbiter/bootstrap_token` for automatic discovery by a co-located User Agent.
|
- **Local setup:** Written to `~/.arbiter/bootstrap_token` for automatic discovery by a co-located Operator.
|
||||||
- **Remote setup:** Printed to the server's console output.
|
- **Remote setup:** Printed to the server's console output.
|
||||||
|
|
||||||
The first User Agent must present this token alongside the standard challenge-response to complete registration.
|
The first Operator must present this token alongside the standard challenge-response to complete registration.
|
||||||
|
|
||||||
### 2.3 SDK Client Registration
|
### 2.3 SDK Client Registration
|
||||||
|
|
||||||
There is no bootstrap mechanism for SDK clients. They must be explicitly approved by an already-registered User Agent.
|
There is no bootstrap mechanism for SDK clients. They must be explicitly approved by an already-registered Operator.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Multi-Operator Governance
|
## 3. Multi-Operator Governance
|
||||||
|
|
||||||
When more than one User Agent is registered, the vault is treated as having multiple operators. In that mode, sensitive actions are governed by voting rather than by a single operator decision.
|
When more than one Operator is registered, the vault is treated as having multiple operators. In that mode, sensitive actions are governed by voting rather than by a single operator decision.
|
||||||
|
|
||||||
### 3.1 Voting Rules
|
### 3.1 Voting Rules
|
||||||
|
|
||||||
@@ -165,13 +165,13 @@ In both cases, committee formation is a coordinated process. Arbiter does not al
|
|||||||
|
|
||||||
When an unbootstrapped vault is initialized as a multi-operator vault, the setup proceeds as follows:
|
When an unbootstrapped vault is initialized as a multi-operator vault, the setup proceeds as follows:
|
||||||
|
|
||||||
1. An operator connects to the unbootstrapped vault using a User Agent and the bootstrap token.
|
1. An operator connects to the unbootstrapped vault using an Operator and the bootstrap token.
|
||||||
2. During bootstrap setup, that operator declares:
|
2. During bootstrap setup, that operator declares:
|
||||||
- the total number of ordinary operators
|
- the total number of ordinary operators
|
||||||
- the total number of Recovery Operators
|
- the total number of Recovery Operators
|
||||||
3. The vault enters **multi-bootstrap mode**.
|
3. The vault enters **multi-bootstrap mode**.
|
||||||
4. While in multi-bootstrap mode:
|
4. While in multi-bootstrap mode:
|
||||||
- every ordinary operator must connect with a User Agent using the bootstrap token
|
- every ordinary operator must connect with an Operator using the bootstrap token
|
||||||
- every Recovery Operator must also connect using the bootstrap token
|
- every Recovery Operator must also connect using the bootstrap token
|
||||||
- each participant is registered individually
|
- each participant is registered individually
|
||||||
- each participant's share is created and protected with that participant's credentials
|
- each participant's share is created and protected with that participant's credentials
|
||||||
@@ -193,8 +193,8 @@ The server proves its identity using TLS with a self-signed certificate. The TLS
|
|||||||
|
|
||||||
Peers verify the server by its **public key fingerprint**:
|
Peers verify the server by its **public key fingerprint**:
|
||||||
|
|
||||||
- **User Agent (local):** Receives the fingerprint automatically through the bootstrap token.
|
- **Operator (local):** Receives the fingerprint automatically through the bootstrap token.
|
||||||
- **User Agent (remote) / SDK Client:** Must receive the fingerprint out-of-band.
|
- **Operator (remote) / SDK Client:** Must receive the fingerprint out-of-band.
|
||||||
|
|
||||||
> A streamlined setup mechanism using a single connection string is planned but not yet implemented.
|
> A streamlined setup mechanism using a single connection string is planned but not yet implemented.
|
||||||
|
|
||||||
@@ -231,11 +231,11 @@ On boot, the root key is encrypted and the server cannot perform any signing ope
|
|||||||
|
|
||||||
### 6.2 Unseal Flow
|
### 6.2 Unseal Flow
|
||||||
|
|
||||||
To transition to the **Unsealed** state, a User Agent must provide the password:
|
To transition to the **Unsealed** state, an Operator must provide the password:
|
||||||
|
|
||||||
1. The User Agent initiates an unseal request.
|
1. The Operator initiates an unseal request.
|
||||||
2. The server generates a one-time key pair and returns the public key.
|
2. The server generates a one-time key pair and returns the public key.
|
||||||
3. The User Agent encrypts the user's password with this one-time public key and sends the ciphertext to the server.
|
3. The Operator encrypts the user's password with this one-time public key and sends the ciphertext to the server.
|
||||||
4. The server decrypts and verifies the password:
|
4. The server decrypts and verifies the password:
|
||||||
- **Success:** The root key is decrypted and placed into a hardened memory cell. The server transitions to `Unsealed`. Any entries pending encryption scheme migration are re-encrypted.
|
- **Success:** The root key is decrypted and placed into a hardened memory cell. The server transitions to `Unsealed`. Any entries pending encryption scheme migration are re-encrypted.
|
||||||
- **Failure:** The server returns an error indicating the password is incorrect.
|
- **Failure:** The server returns an error indicating the password is incorrect.
|
||||||
@@ -257,7 +257,7 @@ See [IMPLEMENTATION.md](IMPLEMENTATION.md) for the current and planned memory pr
|
|||||||
### 7.1 Fundamental Rules
|
### 7.1 Fundamental Rules
|
||||||
|
|
||||||
- SDK clients have **no access by default**.
|
- SDK clients have **no access by default**.
|
||||||
- Access is granted **explicitly** by a User Agent.
|
- Access is granted **explicitly** by an Operator.
|
||||||
- Grants are scoped to **specific wallets** and governed by **policies**.
|
- Grants are scoped to **specific wallets** and governed by **policies**.
|
||||||
|
|
||||||
Each blockchain requires its own policy system due to differences in static transaction analysis. Currently, only EVM is supported; Solana support is planned.
|
Each blockchain requires its own policy system due to differences in static transaction analysis. Currently, only EVM is supported; Solana support is planned.
|
||||||
@@ -277,19 +277,19 @@ sequenceDiagram
|
|||||||
autonumber
|
autonumber
|
||||||
actor SDK as SDK Client
|
actor SDK as SDK Client
|
||||||
participant Server
|
participant Server
|
||||||
participant UA as User Agent
|
participant operator as Operator
|
||||||
|
|
||||||
SDK->>Server: SignTransactionRequest
|
SDK->>Server: SignTransactionRequest
|
||||||
Server->>Server: Resolve wallet and wallet visibility
|
Server->>Server: Resolve wallet and wallet visibility
|
||||||
alt Visibility approval required
|
alt Visibility approval required
|
||||||
Server->>UA: Ask for wallet visibility approval
|
Server->>operator: Ask for wallet visibility approval
|
||||||
UA-->>Server: Vote result
|
operator-->>Server: Vote result
|
||||||
end
|
end
|
||||||
Server->>Server: Evaluate transaction
|
Server->>Server: Evaluate transaction
|
||||||
Server->>Server: Load grant and limits context
|
Server->>Server: Load grant and limits context
|
||||||
alt Grant approval required
|
alt Grant approval required
|
||||||
Server->>UA: Ask for execution / grant approval
|
Server->>operator: Ask for execution / grant approval
|
||||||
UA-->>Server: Vote result
|
operator-->>Server: Vote result
|
||||||
opt Create persistent grant
|
opt Create persistent grant
|
||||||
Server->>Server: Create and store grant
|
Server->>Server: Create and store grant
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ This document covers concrete technology choices and dependencies. For the archi
|
|||||||
|
|
||||||
### Authentication Result Semantics
|
### Authentication Result Semantics
|
||||||
|
|
||||||
Authentication no longer uses an implicit success-only response shape. Both `client` and `user-agent` return explicit auth status enums over the wire.
|
Authentication no longer uses an implicit success-only response shape. Both `client` and `operator` return explicit auth status enums over the wire.
|
||||||
|
|
||||||
- **Client:** `AuthResult` may return `SUCCESS`, `INVALID_KEY`, `INVALID_SIGNATURE`, `APPROVAL_DENIED`, `NO_USER_AGENTS_ONLINE`, or `INTERNAL`
|
- **Client:** `AuthResult` may return `SUCCESS`, `INVALID_KEY`, `INVALID_SIGNATURE`, `APPROVAL_DENIED`, `NO_OPERATORS_ONLINE`, or `INTERNAL`
|
||||||
- **User-agent:** `AuthResult` may return `SUCCESS`, `INVALID_KEY`, `INVALID_SIGNATURE`, `BOOTSTRAP_REQUIRED`, `TOKEN_INVALID`, or `INTERNAL`
|
- **Operator:** `AuthResult` may return `SUCCESS`, `INVALID_KEY`, `INVALID_SIGNATURE`, `BOOTSTRAP_REQUIRED`, `TOKEN_INVALID`, or `INTERNAL`
|
||||||
|
|
||||||
This makes transport-level failures and actor/domain-level auth failures distinct:
|
This makes transport-level failures and actor/domain-level auth failures distinct:
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ Clients are expected to handle these status codes directly and present the concr
|
|||||||
|
|
||||||
### New Client Approval
|
### New Client Approval
|
||||||
|
|
||||||
When a client whose public key is not yet in the database connects, all connected user agents are asked to approve the connection. The first agent to respond determines the outcome; remaining requests are cancelled via a watch channel.
|
When a client whose public key is not yet in the database connects, all connected operators are asked to approve the connection. The first operator to respond determines the outcome; remaining requests are cancelled via a watch channel.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
@@ -31,10 +31,10 @@ flowchart TD
|
|||||||
|
|
||||||
C -- yes --> G[Generate AuthChallenge]
|
C -- yes --> G[Generate AuthChallenge]
|
||||||
|
|
||||||
C -- no --> E[Ask all UserAgents:\nClientConnectionRequest]
|
C -- no --> E[Ask all Operators:\nClientConnectionRequest]
|
||||||
E --> F{First response}
|
E --> F{First response}
|
||||||
F -- denied --> Z([Reject connection])
|
F -- denied --> Z([Reject connection])
|
||||||
F -- approved --> F2[Cancel remaining\nUserAgent requests]
|
F -- approved --> F2[Cancel remaining\nOperator requests]
|
||||||
F2 --> F3[INSERT client]
|
F2 --> F3[INSERT client]
|
||||||
F3 --> G
|
F3 --> G
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ Auth challenges are generated from fresh random bytes plus a nanosecond timestam
|
|||||||
The authentication schema stores peer identity, not replay counters:
|
The authentication schema stores peer identity, not replay counters:
|
||||||
|
|
||||||
- `program_client` stores the SDK client's public key, metadata binding, and timestamps.
|
- `program_client` stores the SDK client's public key, metadata binding, and timestamps.
|
||||||
- `useragent_client` stores the User Agent public key and timestamps.
|
- `operator_client` stores the Operator public key and timestamps.
|
||||||
- Neither table stores an authentication nonce, and challenge generation does not update either table.
|
- Neither table stores an authentication nonce, and challenge generation does not update either table.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -62,7 +62,7 @@ The authentication schema stores peer identity, not replay counters:
|
|||||||
|
|
||||||
### User-Agent Authentication
|
### User-Agent Authentication
|
||||||
|
|
||||||
User-agent authentication supports multiple signature schemes because platform-provided "hardware-bound" keys do not expose a uniform algorithm across operating systems and hardware.
|
Operator authentication supports multiple signature schemes because platform-provided "hardware-bound" keys do not expose a uniform algorithm across operating systems and hardware.
|
||||||
|
|
||||||
- **Supported schemes:** ML-DSA
|
- **Supported schemes:** ML-DSA
|
||||||
- **Why:** Secure Enclave (MacOS) support them natively, on other platforms we could emulate while they roll-out
|
- **Why:** Secure Enclave (MacOS) support them natively, on other platforms we could emulate while they roll-out
|
||||||
@@ -86,7 +86,7 @@ User-agent authentication supports multiple signature schemes because platform-p
|
|||||||
|
|
||||||
### Request Multiplexing
|
### Request Multiplexing
|
||||||
|
|
||||||
Both `client` and `user-agent` connections support multiple in-flight requests over one gRPC bidi stream.
|
Both `client` and `operator` connections support multiple in-flight requests over one gRPC bidi stream.
|
||||||
|
|
||||||
- Every request carries a monotonically increasing request ID
|
- Every request carries a monotonically increasing request ID
|
||||||
- Every normal response echoes the request ID it corresponds to
|
- Every normal response echoes the request ID it corresponds to
|
||||||
@@ -141,7 +141,7 @@ flowchart TD
|
|||||||
L -- Yes --> M[Check grant limits]
|
L -- Yes --> M[Check grant limits]
|
||||||
L -- No --> N[Start execution or grant voting flow]
|
L -- No --> N[Start execution or grant voting flow]
|
||||||
|
|
||||||
N --> O{User-agent decision}
|
N --> O{Operator decision}
|
||||||
O -- Reject --> Z4[Return no matching grant error]
|
O -- Reject --> Z4[Return no matching grant error]
|
||||||
O -- Allow once --> M
|
O -- Allow once --> M
|
||||||
O -- Create grant --> P[Create grant with user-selected limits]
|
O -- Create grant --> P[Create grant with user-selected limits]
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ String shortAddress(List<int> bytes) {
|
|||||||
- [ ] **Step 2: Verify**
|
- [ ] **Step 2: Verify**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && dart analyze lib/screens/dashboard/evm/grants/create/utils.dart
|
cd operator && dart analyze lib/screens/dashboard/evm/grants/create/utils.dart
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: no errors.
|
Expected: no errors.
|
||||||
@@ -168,7 +168,7 @@ class GrantCreation extends _$GrantCreation {
|
|||||||
- [ ] **Step 2: Run code generator**
|
- [ ] **Step 2: Run code generator**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && dart run build_runner build --delete-conflicting-outputs
|
cd operator && dart run build_runner build --delete-conflicting-outputs
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: generates `provider.freezed.dart` and `provider.g.dart`, no errors.
|
Expected: generates `provider.freezed.dart` and `provider.g.dart`, no errors.
|
||||||
@@ -176,7 +176,7 @@ Expected: generates `provider.freezed.dart` and `provider.g.dart`, no errors.
|
|||||||
- [ ] **Step 3: Verify**
|
- [ ] **Step 3: Verify**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && dart analyze lib/screens/dashboard/evm/grants/create/provider.dart
|
cd operator && dart analyze lib/screens/dashboard/evm/grants/create/provider.dart
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: no errors.
|
Expected: no errors.
|
||||||
@@ -204,7 +204,7 @@ jj describe -m "feat(grants): add GrantCreation provider (client selection + gra
|
|||||||
|
|
||||||
```dart
|
```dart
|
||||||
// lib/screens/dashboard/evm/grants/create/fields/client_picker_field.dart
|
// lib/screens/dashboard/evm/grants/create/fields/client_picker_field.dart
|
||||||
import 'package:arbiter/proto/user_agent.pb.dart';
|
import 'package:arbiter/proto/operator.pb.dart';
|
||||||
import 'package:arbiter/providers/sdk_clients/list.dart';
|
import 'package:arbiter/providers/sdk_clients/list.dart';
|
||||||
import 'package:arbiter/screens/dashboard/evm/grants/create/provider.dart';
|
import 'package:arbiter/screens/dashboard/evm/grants/create/provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -246,7 +246,7 @@ class ClientPickerField extends ConsumerWidget {
|
|||||||
```dart
|
```dart
|
||||||
// lib/screens/dashboard/evm/grants/create/fields/wallet_access_picker_field.dart
|
// lib/screens/dashboard/evm/grants/create/fields/wallet_access_picker_field.dart
|
||||||
import 'package:arbiter/proto/evm.pb.dart';
|
import 'package:arbiter/proto/evm.pb.dart';
|
||||||
import 'package:arbiter/proto/user_agent.pb.dart';
|
import 'package:arbiter/proto/operator.pb.dart';
|
||||||
import 'package:arbiter/providers/evm/evm.dart';
|
import 'package:arbiter/providers/evm/evm.dart';
|
||||||
import 'package:arbiter/providers/sdk_clients/wallet_access_list.dart';
|
import 'package:arbiter/providers/sdk_clients/wallet_access_list.dart';
|
||||||
import 'package:arbiter/screens/dashboard/evm/grants/create/provider.dart';
|
import 'package:arbiter/screens/dashboard/evm/grants/create/provider.dart';
|
||||||
@@ -522,7 +522,7 @@ class TransactionRateLimitField extends StatelessWidget {
|
|||||||
- [ ] **Step 8: Verify all field widgets**
|
- [ ] **Step 8: Verify all field widgets**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && dart analyze lib/screens/dashboard/evm/grants/create/fields/
|
cd operator && dart analyze lib/screens/dashboard/evm/grants/create/fields/
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: no errors.
|
Expected: no errors.
|
||||||
@@ -585,7 +585,7 @@ class SharedGrantFields extends StatelessWidget {
|
|||||||
- [ ] **Step 2: Verify**
|
- [ ] **Step 2: Verify**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && dart analyze lib/screens/dashboard/evm/grants/create/shared_grant_fields.dart
|
cd operator && dart analyze lib/screens/dashboard/evm/grants/create/shared_grant_fields.dart
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: no errors.
|
Expected: no errors.
|
||||||
@@ -978,7 +978,7 @@ class _TokenVolumeLimitRow extends HookWidget {
|
|||||||
- [ ] **Step 4: Run code generator for token_transfer_grant.g.dart**
|
- [ ] **Step 4: Run code generator for token_transfer_grant.g.dart**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && dart run build_runner build --delete-conflicting-outputs
|
cd operator && dart run build_runner build --delete-conflicting-outputs
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: generates `token_transfer_grant.g.dart`, no errors.
|
Expected: generates `token_transfer_grant.g.dart`, no errors.
|
||||||
@@ -986,7 +986,7 @@ Expected: generates `token_transfer_grant.g.dart`, no errors.
|
|||||||
- [ ] **Step 5: Verify**
|
- [ ] **Step 5: Verify**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && dart analyze lib/screens/dashboard/evm/grants/create/grants/
|
cd operator && dart analyze lib/screens/dashboard/evm/grants/create/grants/
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: no errors.
|
Expected: no errors.
|
||||||
@@ -1265,7 +1265,7 @@ String _formatError(Object error) {
|
|||||||
- [ ] **Step 2: Verify the full create/ directory**
|
- [ ] **Step 2: Verify the full create/ directory**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && dart analyze lib/screens/dashboard/evm/grants/create/
|
cd operator && dart analyze lib/screens/dashboard/evm/grants/create/
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: no errors.
|
Expected: no errors.
|
||||||
|
|||||||
@@ -14,24 +14,24 @@
|
|||||||
|
|
||||||
| File | Action | Responsibility |
|
| File | Action | Responsibility |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `useragent/lib/theme/palette.dart` | Modify | Add `Palette.token` (indigo accent for token-transfer cards) |
|
| `operator/lib/theme/palette.dart` | Modify | Add `Palette.token` (indigo accent for token-transfer cards) |
|
||||||
| `useragent/lib/features/connection/evm/wallet_access.dart` | Modify | Add `listAllWalletAccesses()` function |
|
| `operator/lib/features/connection/evm/wallet_access.dart` | Modify | Add `listAllWalletAccesses()` function |
|
||||||
| `useragent/lib/providers/sdk_clients/wallet_access_list.dart` | Create | `WalletAccessListProvider` — fetches full wallet access list with IDs |
|
| `operator/lib/providers/sdk_clients/wallet_access_list.dart` | Create | `WalletAccessListProvider` — fetches full wallet access list with IDs |
|
||||||
| `useragent/lib/screens/dashboard/evm/grants/widgets/grant_card.dart` | Create | `GrantCard` widget — watches enrichment providers + revoke mutation; one card per grant |
|
| `operator/lib/screens/dashboard/evm/grants/widgets/grant_card.dart` | Create | `GrantCard` widget — watches enrichment providers + revoke mutation; one card per grant |
|
||||||
| `useragent/lib/screens/dashboard/evm/grants/grants.dart` | Create | `EvmGrantsScreen` — watches `evmGrantsProvider`; handles loading/error/empty/data states; renders `GrantCard` list |
|
| `operator/lib/screens/dashboard/evm/grants/grants.dart` | Create | `EvmGrantsScreen` — watches `evmGrantsProvider`; handles loading/error/empty/data states; renders `GrantCard` list |
|
||||||
| `useragent/lib/router.dart` | Modify | Register `EvmGrantsRoute` in dashboard children |
|
| `operator/lib/router.dart` | Modify | Register `EvmGrantsRoute` in dashboard children |
|
||||||
| `useragent/lib/screens/dashboard.dart` | Modify | Add Grants entry to `routes` list and `NavigationDestination` list |
|
| `operator/lib/screens/dashboard.dart` | Modify | Add Grants entry to `routes` list and `NavigationDestination` list |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Task 1: Add `Palette.token`
|
## Task 1: Add `Palette.token`
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `useragent/lib/theme/palette.dart`
|
- Modify: `operator/lib/theme/palette.dart`
|
||||||
|
|
||||||
- [ ] **Step 1: Add the color**
|
- [ ] **Step 1: Add the color**
|
||||||
|
|
||||||
Replace the contents of `useragent/lib/theme/palette.dart` with:
|
Replace the contents of `operator/lib/theme/palette.dart` with:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -48,7 +48,7 @@ class Palette {
|
|||||||
- [ ] **Step 2: Verify**
|
- [ ] **Step 2: Verify**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && flutter analyze lib/theme/palette.dart
|
cd operator && flutter analyze lib/theme/palette.dart
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: no issues.
|
Expected: no issues.
|
||||||
@@ -65,20 +65,20 @@ jj new
|
|||||||
## Task 2: Add `listAllWalletAccesses` feature function
|
## Task 2: Add `listAllWalletAccesses` feature function
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `useragent/lib/features/connection/evm/wallet_access.dart`
|
- Modify: `operator/lib/features/connection/evm/wallet_access.dart`
|
||||||
|
|
||||||
`readClientWalletAccess` (existing) filters the list to one client's wallet IDs and returns `Set<int>`. This new function returns the complete unfiltered list with row IDs so the grant cards can resolve wallet_access_id → wallet + client.
|
`readClientWalletAccess` (existing) filters the list to one client's wallet IDs and returns `Set<int>`. This new function returns the complete unfiltered list with row IDs so the grant cards can resolve wallet_access_id → wallet + client.
|
||||||
|
|
||||||
- [ ] **Step 1: Append function**
|
- [ ] **Step 1: Append function**
|
||||||
|
|
||||||
Add at the bottom of `useragent/lib/features/connection/evm/wallet_access.dart`:
|
Add at the bottom of `operator/lib/features/connection/evm/wallet_access.dart`:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
Future<List<SdkClientWalletAccess>> listAllWalletAccesses(
|
Future<List<SdkClientWalletAccess>> listAllWalletAccesses(
|
||||||
Connection connection,
|
Connection connection,
|
||||||
) async {
|
) async {
|
||||||
final response = await connection.ask(
|
final response = await connection.ask(
|
||||||
UserAgentRequest(listWalletAccess: Empty()),
|
OperatorRequest(listWalletAccess: Empty()),
|
||||||
);
|
);
|
||||||
if (!response.hasListWalletAccessResponse()) {
|
if (!response.hasListWalletAccessResponse()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
@@ -97,7 +97,7 @@ Each returned `SdkClientWalletAccess` has:
|
|||||||
- [ ] **Step 2: Verify**
|
- [ ] **Step 2: Verify**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && flutter analyze lib/features/connection/evm/wallet_access.dart
|
cd operator && flutter analyze lib/features/connection/evm/wallet_access.dart
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: no issues.
|
Expected: no issues.
|
||||||
@@ -114,18 +114,18 @@ jj new
|
|||||||
## Task 3: Create `WalletAccessListProvider`
|
## Task 3: Create `WalletAccessListProvider`
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `useragent/lib/providers/sdk_clients/wallet_access_list.dart`
|
- Create: `operator/lib/providers/sdk_clients/wallet_access_list.dart`
|
||||||
- Generated: `useragent/lib/providers/sdk_clients/wallet_access_list.g.dart`
|
- Generated: `operator/lib/providers/sdk_clients/wallet_access_list.g.dart`
|
||||||
|
|
||||||
Mirrors the structure of `EvmGrants` in `providers/evm/evm_grants.dart` — class-based `@riverpod` with a `refresh()` method.
|
Mirrors the structure of `EvmGrants` in `providers/evm/evm_grants.dart` — class-based `@riverpod` with a `refresh()` method.
|
||||||
|
|
||||||
- [ ] **Step 1: Write the provider**
|
- [ ] **Step 1: Write the provider**
|
||||||
|
|
||||||
Create `useragent/lib/providers/sdk_clients/wallet_access_list.dart`:
|
Create `operator/lib/providers/sdk_clients/wallet_access_list.dart`:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
import 'package:arbiter/features/connection/evm/wallet_access.dart';
|
import 'package:arbiter/features/connection/evm/wallet_access.dart';
|
||||||
import 'package:arbiter/proto/user_agent.pb.dart';
|
import 'package:arbiter/proto/operator.pb.dart';
|
||||||
import 'package:arbiter/providers/connection/connection_manager.dart';
|
import 'package:arbiter/providers/connection/connection_manager.dart';
|
||||||
import 'package:mtcore/markettakers.dart';
|
import 'package:mtcore/markettakers.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
@@ -165,15 +165,15 @@ class WalletAccessList extends _$WalletAccessList {
|
|||||||
- [ ] **Step 2: Run code generation**
|
- [ ] **Step 2: Run code generation**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && dart run build_runner build --delete-conflicting-outputs
|
cd operator && dart run build_runner build --delete-conflicting-outputs
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: `useragent/lib/providers/sdk_clients/wallet_access_list.g.dart` created. No errors.
|
Expected: `operator/lib/providers/sdk_clients/wallet_access_list.g.dart` created. No errors.
|
||||||
|
|
||||||
- [ ] **Step 3: Verify**
|
- [ ] **Step 3: Verify**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && flutter analyze lib/providers/sdk_clients/
|
cd operator && flutter analyze lib/providers/sdk_clients/
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: no issues.
|
Expected: no issues.
|
||||||
@@ -190,26 +190,26 @@ jj new
|
|||||||
## Task 4: Create `GrantCard` widget
|
## Task 4: Create `GrantCard` widget
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `useragent/lib/screens/dashboard/evm/grants/widgets/grant_card.dart`
|
- Create: `operator/lib/screens/dashboard/evm/grants/widgets/grant_card.dart`
|
||||||
|
|
||||||
This widget owns all per-card logic: enrichment lookups, revoke action, and rebuild scope. The screen only passes it a `GrantEntry` — the card fetches everything else itself.
|
This widget owns all per-card logic: enrichment lookups, revoke action, and rebuild scope. The screen only passes it a `GrantEntry` — the card fetches everything else itself.
|
||||||
|
|
||||||
**Key types:**
|
**Key types:**
|
||||||
- `GrantEntry` (from `proto/evm.pb.dart`): `.id`, `.shared.walletAccessId`, `.shared.chainId`, `.specific.whichGrant()`
|
- `GrantEntry` (from `proto/evm.pb.dart`): `.id`, `.shared.walletAccessId`, `.shared.chainId`, `.specific.whichGrant()`
|
||||||
- `SpecificGrant_Grant.etherTransfer` / `.tokenTransfer` — enum values for the oneof
|
- `SpecificGrant_Grant.etherTransfer` / `.tokenTransfer` — enum values for the oneof
|
||||||
- `SdkClientWalletAccess` (from `proto/user_agent.pb.dart`): `.id`, `.access.walletId`, `.access.sdkClientId`
|
- `SdkClientWalletAccess` (from `proto/operator.pb.dart`): `.id`, `.access.walletId`, `.access.sdkClientId`
|
||||||
- `WalletEntry` (from `proto/evm.pb.dart`): `.id`, `.address` (List<int>)
|
- `WalletEntry` (from `proto/evm.pb.dart`): `.id`, `.address` (List<int>)
|
||||||
- `SdkClientEntry` (from `proto/user_agent.pb.dart`): `.id`, `.info.name`
|
- `SdkClientEntry` (from `proto/operator.pb.dart`): `.id`, `.info.name`
|
||||||
- `revokeEvmGrantMutation` — `Mutation<void>` (global; all revoke buttons disable together while any revoke is in flight)
|
- `revokeEvmGrantMutation` — `Mutation<void>` (global; all revoke buttons disable together while any revoke is in flight)
|
||||||
- `executeRevokeEvmGrant(ref, grantId: int)` — `Future<void>`
|
- `executeRevokeEvmGrant(ref, grantId: int)` — `Future<void>`
|
||||||
|
|
||||||
- [ ] **Step 1: Write the widget**
|
- [ ] **Step 1: Write the widget**
|
||||||
|
|
||||||
Create `useragent/lib/screens/dashboard/evm/grants/widgets/grant_card.dart`:
|
Create `operator/lib/screens/dashboard/evm/grants/widgets/grant_card.dart`:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
import 'package:arbiter/proto/evm.pb.dart';
|
import 'package:arbiter/proto/evm.pb.dart';
|
||||||
import 'package:arbiter/proto/user_agent.pb.dart';
|
import 'package:arbiter/proto/operator.pb.dart';
|
||||||
import 'package:arbiter/providers/evm/evm.dart';
|
import 'package:arbiter/providers/evm/evm.dart';
|
||||||
import 'package:arbiter/providers/evm/evm_grants.dart';
|
import 'package:arbiter/providers/evm/evm_grants.dart';
|
||||||
import 'package:arbiter/providers/sdk_clients/list.dart';
|
import 'package:arbiter/providers/sdk_clients/list.dart';
|
||||||
@@ -438,7 +438,7 @@ class GrantCard extends ConsumerWidget {
|
|||||||
- [ ] **Step 2: Verify**
|
- [ ] **Step 2: Verify**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && flutter analyze lib/screens/dashboard/evm/grants/widgets/grant_card.dart
|
cd operator && flutter analyze lib/screens/dashboard/evm/grants/widgets/grant_card.dart
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: no issues.
|
Expected: no issues.
|
||||||
@@ -455,13 +455,13 @@ jj new
|
|||||||
## Task 5: Create `EvmGrantsScreen`
|
## Task 5: Create `EvmGrantsScreen`
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Create: `useragent/lib/screens/dashboard/evm/grants/grants.dart`
|
- Create: `operator/lib/screens/dashboard/evm/grants/grants.dart`
|
||||||
|
|
||||||
The screen watches only `evmGrantsProvider` for top-level state (loading / error / no connection / empty / data). When there is data it renders a list of `GrantCard` widgets — each card manages its own enrichment subscriptions.
|
The screen watches only `evmGrantsProvider` for top-level state (loading / error / no connection / empty / data). When there is data it renders a list of `GrantCard` widgets — each card manages its own enrichment subscriptions.
|
||||||
|
|
||||||
- [ ] **Step 1: Write the screen**
|
- [ ] **Step 1: Write the screen**
|
||||||
|
|
||||||
Create `useragent/lib/screens/dashboard/evm/grants/grants.dart`:
|
Create `operator/lib/screens/dashboard/evm/grants/grants.dart`:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
import 'package:arbiter/proto/evm.pb.dart';
|
import 'package:arbiter/proto/evm.pb.dart';
|
||||||
@@ -702,7 +702,7 @@ class EvmGrantsScreen extends ConsumerWidget {
|
|||||||
- [ ] **Step 2: Verify**
|
- [ ] **Step 2: Verify**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && flutter analyze lib/screens/dashboard/evm/grants/
|
cd operator && flutter analyze lib/screens/dashboard/evm/grants/
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: no issues.
|
Expected: no issues.
|
||||||
@@ -719,13 +719,13 @@ jj new
|
|||||||
## Task 6: Wire router and dashboard tab
|
## Task 6: Wire router and dashboard tab
|
||||||
|
|
||||||
**Files:**
|
**Files:**
|
||||||
- Modify: `useragent/lib/router.dart`
|
- Modify: `operator/lib/router.dart`
|
||||||
- Modify: `useragent/lib/screens/dashboard.dart`
|
- Modify: `operator/lib/screens/dashboard.dart`
|
||||||
- Regenerated: `useragent/lib/router.gr.dart`
|
- Regenerated: `operator/lib/router.gr.dart`
|
||||||
|
|
||||||
- [ ] **Step 1: Add route to `router.dart`**
|
- [ ] **Step 1: Add route to `router.dart`**
|
||||||
|
|
||||||
Replace the contents of `useragent/lib/router.dart` with:
|
Replace the contents of `operator/lib/router.dart` with:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
@@ -759,7 +759,7 @@ class Router extends RootStackRouter {
|
|||||||
|
|
||||||
- [ ] **Step 2: Update `dashboard.dart`**
|
- [ ] **Step 2: Update `dashboard.dart`**
|
||||||
|
|
||||||
In `useragent/lib/screens/dashboard.dart`, replace the `routes` constant:
|
In `operator/lib/screens/dashboard.dart`, replace the `routes` constant:
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
final routes = [
|
final routes = [
|
||||||
@@ -800,7 +800,7 @@ destinations: const [
|
|||||||
- [ ] **Step 3: Regenerate router**
|
- [ ] **Step 3: Regenerate router**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && dart run build_runner build --delete-conflicting-outputs
|
cd operator && dart run build_runner build --delete-conflicting-outputs
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: `lib/router.gr.dart` updated, `EvmGrantsRoute` now available, no errors.
|
Expected: `lib/router.gr.dart` updated, `EvmGrantsRoute` now available, no errors.
|
||||||
@@ -808,7 +808,7 @@ Expected: `lib/router.gr.dart` updated, `EvmGrantsRoute` now available, no error
|
|||||||
- [ ] **Step 4: Full project verify**
|
- [ ] **Step 4: Full project verify**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cd useragent && flutter analyze
|
cd operator && flutter analyze
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected: no issues.
|
Expected: no issues.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Add a "Grants" dashboard tab to the Flutter user-agent app that displays all EVM grants as a card-based grid. Each card shows a compact summary (type, chain, wallet address, client name) with a revoke action. The tab integrates into the existing `AdaptiveScaffold` navigation alongside Wallets, Clients, and About.
|
Add a "Grants" dashboard tab to the Flutter operator app that displays all EVM grants as a card-based grid. Each card shows a compact summary (type, chain, wallet address, client name) with a revoke action. The tab integrates into the existing `AdaptiveScaffold` navigation alongside Wallets, Clients, and About.
|
||||||
|
|
||||||
## Scope
|
## Scope
|
||||||
|
|
||||||
@@ -23,7 +23,7 @@ Add a "Grants" dashboard tab to the Flutter user-agent app that displays all EVM
|
|||||||
|
|
||||||
### `walletAccessListProvider`
|
### `walletAccessListProvider`
|
||||||
|
|
||||||
**File:** `useragent/lib/providers/sdk_clients/wallet_access_list.dart`
|
**File:** `operator/lib/providers/sdk_clients/wallet_access_list.dart`
|
||||||
|
|
||||||
- `@riverpod` class, watches `connectionManagerProvider.future`
|
- `@riverpod` class, watches `connectionManagerProvider.future`
|
||||||
- Returns `List<SdkClientWalletAccess>?` (null when not connected)
|
- Returns `List<SdkClientWalletAccess>?` (null when not connected)
|
||||||
@@ -85,7 +85,7 @@ NavigationDestination(
|
|||||||
|
|
||||||
## Screen: `EvmGrantsScreen`
|
## Screen: `EvmGrantsScreen`
|
||||||
|
|
||||||
**File:** `useragent/lib/screens/dashboard/evm/grants/grants.dart`
|
**File:** `operator/lib/screens/dashboard/evm/grants/grants.dart`
|
||||||
|
|
||||||
```
|
```
|
||||||
Scaffold
|
Scaffold
|
||||||
|
|||||||
74
mise.lock
74
mise.lock
@@ -1,51 +1,51 @@
|
|||||||
# @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html
|
# @generated - this file is auto-generated by `mise lock` https://mise.jdx.dev/dev-tools/mise-lock.html
|
||||||
|
|
||||||
[[tools.ast-grep]]
|
[[tools.ast-grep]]
|
||||||
version = "0.42.0"
|
version = "0.42.1"
|
||||||
backend = "aqua:ast-grep/ast-grep"
|
backend = "aqua:ast-grep/ast-grep"
|
||||||
|
|
||||||
[tools.ast-grep."platforms.linux-arm64"]
|
[tools.ast-grep."platforms.linux-arm64"]
|
||||||
checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836"
|
checksum = "sha256:3ba383839044cf9817929435f5ce0027f91d06931e8efb32d942e58d73d92be5"
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip"
|
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-aarch64-unknown-linux-gnu.zip"
|
||||||
|
|
||||||
[tools.ast-grep."platforms.linux-arm64-musl"]
|
[tools.ast-grep."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836"
|
checksum = "sha256:3ba383839044cf9817929435f5ce0027f91d06931e8efb32d942e58d73d92be5"
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip"
|
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-aarch64-unknown-linux-gnu.zip"
|
||||||
|
|
||||||
[tools.ast-grep."platforms.linux-x64"]
|
[tools.ast-grep."platforms.linux-x64"]
|
||||||
checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651"
|
checksum = "sha256:5de8b87cba67fc8dc3e239d54b6484802ad745a7ae3de76be4fe89661dc52657"
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip"
|
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-x86_64-unknown-linux-gnu.zip"
|
||||||
|
|
||||||
[tools.ast-grep."platforms.linux-x64-musl"]
|
[tools.ast-grep."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651"
|
checksum = "sha256:5de8b87cba67fc8dc3e239d54b6484802ad745a7ae3de76be4fe89661dc52657"
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip"
|
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-x86_64-unknown-linux-gnu.zip"
|
||||||
|
|
||||||
[tools.ast-grep."platforms.macos-arm64"]
|
[tools.ast-grep."platforms.macos-arm64"]
|
||||||
checksum = "sha256:fc300d5293b1c770a5aece03a8a193b92e71e87cec726c28096990691a582620"
|
checksum = "sha256:c3961d8e8a4ee0ce2d0d98c7beeb168bb331cdc766b53630118a7b6c4fd39015"
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-apple-darwin.zip"
|
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-aarch64-apple-darwin.zip"
|
||||||
|
|
||||||
[tools.ast-grep."platforms.macos-x64"]
|
[tools.ast-grep."platforms.macos-x64"]
|
||||||
checksum = "sha256:979ffe611327056f4730a1ae71b0209b3b830f58b22c6ed194cda34f55400db2"
|
checksum = "sha256:a038965bfd7fe44257c771cdf8918dc3467dd8ec0eef673b8b14f639b144cdbd"
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-apple-darwin.zip"
|
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-x86_64-apple-darwin.zip"
|
||||||
|
|
||||||
[tools.ast-grep."platforms.windows-x64"]
|
[tools.ast-grep."platforms.windows-x64"]
|
||||||
checksum = "sha256:55836fa1b2c65dc7d61615a4d9368622a0d2371a76d28b9a165e5a3ab6ae32a4"
|
checksum = "sha256:fe34f631bb24c08ad146f92ca2a92971a53d179461b509fd8d32dc863bff9f83"
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-pc-windows-msvc.zip"
|
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.1/app-x86_64-pc-windows-msvc.zip"
|
||||||
|
|
||||||
[[tools."cargo:cargo-audit"]]
|
[[tools."cargo:cargo-audit"]]
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
backend = "cargo:cargo-audit"
|
backend = "cargo:cargo-audit"
|
||||||
|
|
||||||
[[tools."cargo:cargo-edit"]]
|
[[tools."cargo:cargo-edit"]]
|
||||||
version = "0.13.9"
|
version = "0.13.10"
|
||||||
backend = "cargo:cargo-edit"
|
backend = "cargo:cargo-edit"
|
||||||
|
|
||||||
[[tools."cargo:cargo-features-manager"]]
|
[[tools."cargo:cargo-features-manager"]]
|
||||||
version = "0.11.1"
|
version = "0.12.0"
|
||||||
backend = "cargo:cargo-features-manager"
|
backend = "cargo:cargo-features-manager"
|
||||||
|
|
||||||
[[tools."cargo:cargo-insta"]]
|
[[tools."cargo:cargo-insta"]]
|
||||||
version = "1.46.3"
|
version = "1.47.2"
|
||||||
backend = "cargo:cargo-insta"
|
backend = "cargo:cargo-insta"
|
||||||
|
|
||||||
[[tools."cargo:cargo-mutants"]]
|
[[tools."cargo:cargo-mutants"]]
|
||||||
@@ -53,7 +53,7 @@ version = "27.0.0"
|
|||||||
backend = "cargo:cargo-mutants"
|
backend = "cargo:cargo-mutants"
|
||||||
|
|
||||||
[[tools."cargo:cargo-nextest"]]
|
[[tools."cargo:cargo-nextest"]]
|
||||||
version = "0.9.126"
|
version = "0.9.133"
|
||||||
backend = "cargo:cargo-nextest"
|
backend = "cargo:cargo-nextest"
|
||||||
|
|
||||||
[[tools."cargo:cargo-shear"]]
|
[[tools."cargo:cargo-shear"]]
|
||||||
@@ -65,7 +65,7 @@ version = "0.10.2"
|
|||||||
backend = "cargo:cargo-vet"
|
backend = "cargo:cargo-vet"
|
||||||
|
|
||||||
[[tools."cargo:diesel_cli"]]
|
[[tools."cargo:diesel_cli"]]
|
||||||
version = "2.3.6"
|
version = "2.3.7"
|
||||||
backend = "cargo:diesel_cli"
|
backend = "cargo:diesel_cli"
|
||||||
|
|
||||||
[tools."cargo:diesel_cli".options]
|
[tools."cargo:diesel_cli".options]
|
||||||
@@ -77,7 +77,7 @@ version = "2.12.0"
|
|||||||
backend = "cargo:flutter_rust_bridge_codegen"
|
backend = "cargo:flutter_rust_bridge_codegen"
|
||||||
|
|
||||||
[[tools.flutter]]
|
[[tools.flutter]]
|
||||||
version = "3.38.9-stable"
|
version = "3.41.7-stable"
|
||||||
backend = "asdf:flutter"
|
backend = "asdf:flutter"
|
||||||
|
|
||||||
[[tools.protoc]]
|
[[tools.protoc]]
|
||||||
@@ -113,44 +113,44 @@ checksum = "sha256:1ebd7c87baffb9f1c47169b640872bf5fb1e4408079c691af527be9561d8f
|
|||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-win64.zip"
|
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-win64.zip"
|
||||||
|
|
||||||
[[tools.python]]
|
[[tools.python]]
|
||||||
version = "3.14.3"
|
version = "3.14.4"
|
||||||
backend = "core:python"
|
backend = "core:python"
|
||||||
|
|
||||||
[tools.python."platforms.linux-arm64"]
|
[tools.python."platforms.linux-arm64"]
|
||||||
checksum = "sha256:53700338695e402a1a1fe22be4a41fbdacc70e22bb308a48eca8ed67cb7992be"
|
checksum = "sha256:b8b597fdb2f8dccdc502c11947b60a4b65eb6bce79cfa60c7ccf9b6e8352c60a"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.python."platforms.linux-arm64-musl"]
|
[tools.python."platforms.linux-arm64-musl"]
|
||||||
checksum = "sha256:53700338695e402a1a1fe22be4a41fbdacc70e22bb308a48eca8ed67cb7992be"
|
checksum = "sha256:b8b597fdb2f8dccdc502c11947b60a4b65eb6bce79cfa60c7ccf9b6e8352c60a"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.python."platforms.linux-x64"]
|
[tools.python."platforms.linux-x64"]
|
||||||
checksum = "sha256:d7a9f970914bb4c88756fe3bdcc186d4feb90e9500e54f1db47dae4dc9687e39"
|
checksum = "sha256:fe9a9c32d13870af632cbac3dfc7528ae53597e94472aa4c7d6a42e8166136cd"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.python."platforms.linux-x64-musl"]
|
[tools.python."platforms.linux-x64-musl"]
|
||||||
checksum = "sha256:d7a9f970914bb4c88756fe3bdcc186d4feb90e9500e54f1db47dae4dc9687e39"
|
checksum = "sha256:fe9a9c32d13870af632cbac3dfc7528ae53597e94472aa4c7d6a42e8166136cd"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.python."platforms.macos-arm64"]
|
[tools.python."platforms.macos-arm64"]
|
||||||
checksum = "sha256:c43aecde4a663aebff99b9b83da0efec506479f1c3f98331442f33d2c43501f9"
|
checksum = "blake3:0314ec66e0f33ec04959583b5900bc8edae371a396aa96b8874e750d1fe936e6"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-apple-darwin-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-aarch64-apple-darwin-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.python."platforms.macos-x64"]
|
[tools.python."platforms.macos-x64"]
|
||||||
checksum = "sha256:9ab41dbc2f100a2a45d1833b9c11165f51051c558b5213eda9a9731d5948a0c0"
|
checksum = "sha256:d51250a32fa5d9f0799c7bcb71720c27b10a3afd4a7de288120f96085d508a5a"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-apple-darwin-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-x86_64-apple-darwin-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[tools.python."platforms.windows-x64"]
|
[tools.python."platforms.windows-x64"]
|
||||||
checksum = "sha256:bbe19034b35b0267176a7442575ae7dc6343480fd4d35598cb7700173d431e09"
|
checksum = "sha256:a976991dcd085c1bb5d9a8084823a6bc8b7f9b079d8c432574a6ddd68c3a6fe1"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260414/cpython-3.14.4+20260414-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
provenance = "github-attestations"
|
||||||
|
|
||||||
[[tools.rust]]
|
[[tools.rust]]
|
||||||
version = "1.93.0"
|
version = "1.95.0"
|
||||||
backend = "core:rust"
|
backend = "core:rust"
|
||||||
|
|||||||
18
mise.toml
18
mise.toml
@@ -1,17 +1,17 @@
|
|||||||
[tools]
|
[tools]
|
||||||
"cargo:diesel_cli" = { version = "2.3.6", features = "sqlite,sqlite-bundled", default-features = false }
|
"cargo:diesel_cli" = { version = "2.3.7", features = "sqlite,sqlite-bundled", default-features = "false" }
|
||||||
"cargo:cargo-audit" = "0.22.1"
|
"cargo:cargo-audit" = "0.22.1"
|
||||||
"cargo:cargo-vet" = "0.10.2"
|
"cargo:cargo-vet" = "0.10.2"
|
||||||
flutter = "3.38.9-stable"
|
flutter = "3.41.7-stable"
|
||||||
protoc = "29.6"
|
protoc = "29.6"
|
||||||
"rust" = {version = "1.93.0", components = "clippy,rust-analyzer"}
|
rust = { version = "1.95.0", components = "clippy,rust-analyzer" }
|
||||||
"cargo:cargo-features-manager" = "0.11.1"
|
"cargo:cargo-features-manager" = "0.12.0"
|
||||||
"cargo:cargo-nextest" = "0.9.126"
|
"cargo:cargo-nextest" = "0.9.133"
|
||||||
"cargo:cargo-shear" = "latest"
|
"cargo:cargo-shear" = "latest"
|
||||||
"cargo:cargo-insta" = "1.46.3"
|
"cargo:cargo-insta" = "1.47.2"
|
||||||
python = "3.14.3"
|
python = "3.14.4"
|
||||||
ast-grep = "0.42.0"
|
ast-grep = "0.42.1"
|
||||||
"cargo:cargo-edit" = "0.13.9"
|
"cargo:cargo-edit" = "0.13.10"
|
||||||
"cargo:cargo-mutants" = "27.0.0"
|
"cargo:cargo-mutants" = "27.0.0"
|
||||||
"cargo:flutter_rust_bridge_codegen" = "2.12.0"
|
"cargo:flutter_rust_bridge_codegen" = "2.12.0"
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ syntax = "proto3";
|
|||||||
package arbiter;
|
package arbiter;
|
||||||
|
|
||||||
import "client.proto";
|
import "client.proto";
|
||||||
import "user_agent.proto";
|
import "operator.proto";
|
||||||
|
|
||||||
message ServerInfo {
|
message ServerInfo {
|
||||||
string version = 1;
|
string version = 1;
|
||||||
@@ -12,5 +12,5 @@ message ServerInfo {
|
|||||||
|
|
||||||
service ArbiterService {
|
service ArbiterService {
|
||||||
rpc Client(stream arbiter.client.ClientRequest) returns (stream arbiter.client.ClientResponse);
|
rpc Client(stream arbiter.client.ClientRequest) returns (stream arbiter.client.ClientResponse);
|
||||||
rpc UserAgent(stream arbiter.user_agent.UserAgentRequest) returns (stream arbiter.user_agent.UserAgentResponse);
|
rpc Operator(stream arbiter.operator.OperatorRequest) returns (stream arbiter.operator.OperatorResponse);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ enum AuthResult {
|
|||||||
AUTH_RESULT_INVALID_KEY = 2;
|
AUTH_RESULT_INVALID_KEY = 2;
|
||||||
AUTH_RESULT_INVALID_SIGNATURE = 3;
|
AUTH_RESULT_INVALID_SIGNATURE = 3;
|
||||||
AUTH_RESULT_APPROVAL_DENIED = 4;
|
AUTH_RESULT_APPROVAL_DENIED = 4;
|
||||||
AUTH_RESULT_NO_USER_AGENTS_ONLINE = 5;
|
AUTH_RESULT_NO_OPERATORS_ONLINE = 5;
|
||||||
AUTH_RESULT_INTERNAL = 6;
|
AUTH_RESULT_INTERNAL = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ message SpecificGrant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- UserAgent grant management ---
|
// --- Operator grant management ---
|
||||||
message EvmGrantCreateRequest {
|
message EvmGrantCreateRequest {
|
||||||
SharedSettings shared = 1;
|
SharedSettings shared = 1;
|
||||||
SpecificGrant specific = 2;
|
SpecificGrant specific = 2;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package arbiter.user_agent;
|
package arbiter.operator;
|
||||||
|
|
||||||
import "user_agent/auth.proto";
|
import "operator/auth.proto";
|
||||||
import "user_agent/evm.proto";
|
import "operator/evm.proto";
|
||||||
import "user_agent/sdk_client.proto";
|
import "operator/sdk_client.proto";
|
||||||
import "user_agent/vault/vault.proto";
|
import "operator/vault/vault.proto";
|
||||||
|
|
||||||
message UserAgentRequest {
|
message OperatorRequest {
|
||||||
int32 id = 16;
|
int32 id = 16;
|
||||||
oneof payload {
|
oneof payload {
|
||||||
auth.Request auth = 1;
|
auth.Request auth = 1;
|
||||||
@@ -17,7 +17,7 @@ message UserAgentRequest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message UserAgentResponse {
|
message OperatorResponse {
|
||||||
optional int32 id = 16;
|
optional int32 id = 16;
|
||||||
oneof payload {
|
oneof payload {
|
||||||
auth.Response auth = 1;
|
auth.Response auth = 1;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package arbiter.user_agent.auth;
|
package arbiter.operator.auth;
|
||||||
|
|
||||||
message AuthChallengeRequest {
|
message AuthChallengeRequest {
|
||||||
bytes pubkey = 1;
|
bytes pubkey = 1;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package arbiter.user_agent.evm;
|
package arbiter.operator.evm;
|
||||||
|
|
||||||
import "evm.proto";
|
import "evm.proto";
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package arbiter.user_agent.sdk_client;
|
package arbiter.operator.sdk_client;
|
||||||
|
|
||||||
import "shared/client.proto";
|
import "shared/client.proto";
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package arbiter.user_agent.vault.bootstrap;
|
package arbiter.operator.vault.bootstrap;
|
||||||
|
|
||||||
message BootstrapEncryptedKey {
|
message BootstrapEncryptedKey {
|
||||||
bytes nonce = 1;
|
bytes nonce = 1;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package arbiter.user_agent.vault.unseal;
|
package arbiter.operator.vault.unseal;
|
||||||
|
|
||||||
message UnsealStart {
|
message UnsealStart {
|
||||||
bytes client_pubkey = 1;
|
bytes client_pubkey = 1;
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
|
||||||
package arbiter.user_agent.vault;
|
package arbiter.operator.vault;
|
||||||
|
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
import "shared/vault.proto";
|
import "shared/vault.proto";
|
||||||
import "user_agent/vault/bootstrap.proto";
|
import "operator/vault/bootstrap.proto";
|
||||||
import "user_agent/vault/unseal.proto";
|
import "operator/vault/unseal.proto";
|
||||||
|
|
||||||
message Request {
|
message Request {
|
||||||
oneof payload {
|
oneof payload {
|
||||||
@@ -26,13 +26,13 @@ use chrono::DateTime;
|
|||||||
pub enum AuthError {
|
pub enum AuthError {
|
||||||
#[error("Server sent invalid auth challenge")]
|
#[error("Server sent invalid auth challenge")]
|
||||||
InvalidChallenge,
|
InvalidChallenge,
|
||||||
#[error("Client approval denied by User Agent")]
|
#[error("Client approval denied by Operator")]
|
||||||
ApprovalDenied,
|
ApprovalDenied,
|
||||||
#[error("Auth challenge was not returned by server")]
|
#[error("Auth challenge was not returned by server")]
|
||||||
MissingAuthChallenge,
|
MissingAuthChallenge,
|
||||||
|
|
||||||
#[error("No User Agents online to approve client")]
|
#[error("No Operators online to approve client")]
|
||||||
NoUserAgentsOnline,
|
NoOperatorsOnline,
|
||||||
|
|
||||||
#[error("Signing key storage error")]
|
#[error("Signing key storage error")]
|
||||||
Storage(#[from] StorageError),
|
Storage(#[from] StorageError),
|
||||||
@@ -44,7 +44,7 @@ pub enum AuthError {
|
|||||||
fn map_auth_result(code: i32) -> AuthError {
|
fn map_auth_result(code: i32) -> AuthError {
|
||||||
match AuthResult::try_from(code).unwrap_or(AuthResult::Unspecified) {
|
match AuthResult::try_from(code).unwrap_or(AuthResult::Unspecified) {
|
||||||
AuthResult::ApprovalDenied => AuthError::ApprovalDenied,
|
AuthResult::ApprovalDenied => AuthError::ApprovalDenied,
|
||||||
AuthResult::NoUserAgentsOnline => AuthError::NoUserAgentsOnline,
|
AuthResult::NoOperatorsOnline => AuthError::NoOperatorsOnline,
|
||||||
AuthResult::Unspecified
|
AuthResult::Unspecified
|
||||||
| AuthResult::Success
|
| AuthResult::Success
|
||||||
| AuthResult::InvalidKey
|
| AuthResult::InvalidKey
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use ml_dsa::{
|
|||||||
use rand::RngExt;
|
use rand::RngExt;
|
||||||
|
|
||||||
pub static CLIENT_CONTEXT: &[u8] = b"arbiter_client";
|
pub static CLIENT_CONTEXT: &[u8] = b"arbiter_client";
|
||||||
pub static USERAGENT_CONTEXT: &[u8] = b"arbiter_user_agent";
|
pub static OPERATOR_CONTEXT: &[u8] = b"arbiter_operator";
|
||||||
|
|
||||||
const NONCE_SIZE: usize = 32;
|
const NONCE_SIZE: usize = 32;
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ mod tests {
|
|||||||
|
|
||||||
use crate::authn::AuthChallenge;
|
use crate::authn::AuthChallenge;
|
||||||
|
|
||||||
use super::{CLIENT_CONTEXT, PublicKey, Signature, SigningKey, USERAGENT_CONTEXT};
|
use super::{CLIENT_CONTEXT, PublicKey, Signature, SigningKey, OPERATOR_CONTEXT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn public_key_round_trip_decodes() {
|
fn public_key_round_trip_decodes() {
|
||||||
@@ -227,7 +227,7 @@ mod tests {
|
|||||||
.expect("signature should be created");
|
.expect("signature should be created");
|
||||||
|
|
||||||
assert!(public_key.verify(&challenge, CLIENT_CONTEXT, &signature));
|
assert!(public_key.verify(&challenge, CLIENT_CONTEXT, &signature));
|
||||||
assert!(!public_key.verify(&challenge, USERAGENT_CONTEXT, &signature));
|
assert!(!public_key.verify(&challenge, OPERATOR_CONTEXT, &signature));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.compile_protos(
|
.compile_protos(
|
||||||
&[
|
&[
|
||||||
format!("{}/arbiter.proto", PROTOBUF_DIR),
|
format!("{}/arbiter.proto", PROTOBUF_DIR),
|
||||||
format!("{}/user_agent.proto", PROTOBUF_DIR),
|
format!("{}/operator.proto", PROTOBUF_DIR),
|
||||||
format!("{}/client.proto", PROTOBUF_DIR),
|
format!("{}/client.proto", PROTOBUF_DIR),
|
||||||
format!("{}/evm.proto", PROTOBUF_DIR),
|
format!("{}/evm.proto", PROTOBUF_DIR),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -12,30 +12,30 @@ pub mod proto {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod user_agent {
|
pub mod operator {
|
||||||
tonic::include_proto!("arbiter.user_agent");
|
tonic::include_proto!("arbiter.operator");
|
||||||
|
|
||||||
pub mod auth {
|
pub mod auth {
|
||||||
tonic::include_proto!("arbiter.user_agent.auth");
|
tonic::include_proto!("arbiter.operator.auth");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod evm {
|
pub mod evm {
|
||||||
tonic::include_proto!("arbiter.user_agent.evm");
|
tonic::include_proto!("arbiter.operator.evm");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod sdk_client {
|
pub mod sdk_client {
|
||||||
tonic::include_proto!("arbiter.user_agent.sdk_client");
|
tonic::include_proto!("arbiter.operator.sdk_client");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod vault {
|
pub mod vault {
|
||||||
tonic::include_proto!("arbiter.user_agent.vault");
|
tonic::include_proto!("arbiter.operator.vault");
|
||||||
|
|
||||||
pub mod bootstrap {
|
pub mod bootstrap {
|
||||||
tonic::include_proto!("arbiter.user_agent.vault.bootstrap");
|
tonic::include_proto!("arbiter.operator.vault.bootstrap");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod unseal {
|
pub mod unseal {
|
||||||
tonic::include_proto!("arbiter.user_agent.vault.unseal");
|
tonic::include_proto!("arbiter.operator.vault.unseal");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,13 +43,13 @@ create table if not exists arbiter_settings (
|
|||||||
insert into arbiter_settings (id) values (1) on conflict do nothing;
|
insert into arbiter_settings (id) values (1) on conflict do nothing;
|
||||||
-- ensure singleton row exists
|
-- ensure singleton row exists
|
||||||
|
|
||||||
create table if not exists useragent_client (
|
create table if not exists operator_client (
|
||||||
id integer not null primary key,
|
id integer not null primary key,
|
||||||
public_key blob not null,
|
public_key blob not null,
|
||||||
created_at integer not null default(unixepoch ('now')),
|
created_at integer not null default(unixepoch ('now')),
|
||||||
updated_at integer not null default(unixepoch ('now'))
|
updated_at integer not null default(unixepoch ('now'))
|
||||||
) STRICT;
|
) STRICT;
|
||||||
create unique index if not exists uniq_useragent_client_public_key on useragent_client (public_key);
|
create unique index if not exists uniq_operator_client_public_key on operator_client (public_key);
|
||||||
|
|
||||||
create table if not exists client_metadata (
|
create table if not exists client_metadata (
|
||||||
id integer not null primary key,
|
id integer not null primary key,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ impl Bootstrapper {
|
|||||||
let row_count: i64 = {
|
let row_count: i64 = {
|
||||||
let mut conn = db.get().await?;
|
let mut conn = db.get().await?;
|
||||||
|
|
||||||
schema::useragent_client::table
|
schema::operator_client::table
|
||||||
.count()
|
.count()
|
||||||
.get_result(&mut conn)
|
.get_result(&mut conn)
|
||||||
.await?
|
.await?
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ impl EvmActor {
|
|||||||
#[messages]
|
#[messages]
|
||||||
impl EvmActor {
|
impl EvmActor {
|
||||||
#[message]
|
#[message]
|
||||||
pub async fn useragent_create_grant(
|
pub async fn operator_create_grant(
|
||||||
&mut self,
|
&mut self,
|
||||||
basic: SharedGrantSettings,
|
basic: SharedGrantSettings,
|
||||||
grant: SpecificGrant,
|
grant: SpecificGrant,
|
||||||
@@ -161,7 +161,7 @@ impl EvmActor {
|
|||||||
|
|
||||||
#[message]
|
#[message]
|
||||||
#[expect(clippy::unused_async, reason = "reserved for impl")]
|
#[expect(clippy::unused_async, reason = "reserved for impl")]
|
||||||
pub async fn useragent_delete_grant(&mut self, _grant_id: i32) -> Result<(), Error> {
|
pub async fn operator_delete_grant(&mut self, _grant_id: i32) -> Result<(), Error> {
|
||||||
// let mut conn = self.db.get().await.map_err(DatabaseError::from)?;
|
// let mut conn = self.db.get().await.map_err(DatabaseError::from)?;
|
||||||
// let vault = self.vault.clone();
|
// let vault = self.vault.clone();
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@ impl EvmActor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[message]
|
#[message]
|
||||||
pub async fn useragent_list_grants(&mut self) -> Result<Vec<Grant<SpecificGrant>>, Error> {
|
pub async fn operator_list_grants(&mut self) -> Result<Vec<Grant<SpecificGrant>>, Error> {
|
||||||
match self.engine.list_all_grants().await {
|
match self.engine.list_all_grants().await {
|
||||||
Ok(grants) => Ok(grants),
|
Ok(grants) => Ok(grants),
|
||||||
Err(ListError::Database(db_err)) => Err(Error::Database(db_err)),
|
Err(ListError::Database(db_err)) => Err(Error::Database(db_err)),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::{
|
|||||||
actors::flow_coordinator::ApprovalError,
|
actors::flow_coordinator::ApprovalError,
|
||||||
peers::{
|
peers::{
|
||||||
client::ClientProfile,
|
client::ClientProfile,
|
||||||
user_agent::{UserAgentSession, session::BeginNewClientApproval},
|
operator::{OperatorSession, session::BeginNewClientApproval},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -15,12 +15,12 @@ use std::ops::ControlFlow;
|
|||||||
|
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
pub client: ClientProfile,
|
pub client: ClientProfile,
|
||||||
pub user_agents: Vec<ActorRef<UserAgentSession>>,
|
pub operators: Vec<ActorRef<OperatorSession>>,
|
||||||
pub reply: ReplySender<Result<bool, ApprovalError>>,
|
pub reply: ReplySender<Result<bool, ApprovalError>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ClientApprovalController {
|
pub struct ClientApprovalController {
|
||||||
/// Number of UAs that have not yet responded (approval or denial) or died.
|
/// Number of operators that have not yet responded (approval or denial) or died.
|
||||||
pending: usize,
|
pending: usize,
|
||||||
/// Number of approvals received so far.
|
/// Number of approvals received so far.
|
||||||
approved: usize,
|
approved: usize,
|
||||||
@@ -42,21 +42,21 @@ impl Actor for ClientApprovalController {
|
|||||||
async fn on_start(
|
async fn on_start(
|
||||||
Args {
|
Args {
|
||||||
client,
|
client,
|
||||||
user_agents,
|
operators,
|
||||||
reply,
|
reply,
|
||||||
}: Self::Args,
|
}: Self::Args,
|
||||||
actor_ref: ActorRef<Self>,
|
actor_ref: ActorRef<Self>,
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
let this = Self {
|
let this = Self {
|
||||||
pending: user_agents.len(),
|
pending: operators.len(),
|
||||||
approved: 0,
|
approved: 0,
|
||||||
reply: Some(reply),
|
reply: Some(reply),
|
||||||
};
|
};
|
||||||
|
|
||||||
for user_agent in user_agents {
|
for operator in operators {
|
||||||
actor_ref.link(&user_agent).await;
|
actor_ref.link(&operator).await;
|
||||||
|
|
||||||
let _ = user_agent
|
let _ = operator
|
||||||
.tell(BeginNewClientApproval {
|
.tell(BeginNewClientApproval {
|
||||||
client: client.clone(),
|
client: client.clone(),
|
||||||
controller: actor_ref.clone(),
|
controller: actor_ref.clone(),
|
||||||
@@ -73,10 +73,10 @@ impl Actor for ClientApprovalController {
|
|||||||
_: ActorId,
|
_: ActorId,
|
||||||
_: ActorStopReason,
|
_: ActorStopReason,
|
||||||
) -> Result<ControlFlow<ActorStopReason>, Self::Error> {
|
) -> Result<ControlFlow<ActorStopReason>, Self::Error> {
|
||||||
// A linked UA died before responding — counts as a non-approval.
|
// A linked operator died before responding — counts as a non-approval.
|
||||||
self.pending = self.pending.saturating_sub(1);
|
self.pending = self.pending.saturating_sub(1);
|
||||||
if self.pending == 0 {
|
if self.pending == 0 {
|
||||||
// At least one UA didn't approve: deny.
|
// At least one operator didn't approve: deny.
|
||||||
self.send_reply(Ok(false));
|
self.send_reply(Ok(false));
|
||||||
return Ok(ControlFlow::Break(ActorStopReason::Normal));
|
return Ok(ControlFlow::Break(ActorStopReason::Normal));
|
||||||
}
|
}
|
||||||
@@ -99,7 +99,7 @@ impl ClientApprovalController {
|
|||||||
self.pending = self.pending.saturating_sub(1);
|
self.pending = self.pending.saturating_sub(1);
|
||||||
|
|
||||||
if self.pending == 0 {
|
if self.pending == 0 {
|
||||||
// Every connected UA approved.
|
// Every connected operator approved.
|
||||||
self.send_reply(Ok(true));
|
self.send_reply(Ok(true));
|
||||||
ctx.stop();
|
ctx.stop();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
actors::{
|
actors::{
|
||||||
flow_coordinator::client_connect_approval::ClientApprovalController,
|
flow_coordinator::client_connect_approval::ClientApprovalController,
|
||||||
useragent_registry::{GetConnected, UserAgentRegistry},
|
operator_registry::{GetConnected, OperatorRegistry},
|
||||||
},
|
},
|
||||||
peers::client::{ClientProfile, session::ClientSession},
|
peers::client::{ClientProfile, session::ClientSession},
|
||||||
};
|
};
|
||||||
@@ -20,14 +20,14 @@ pub mod client_connect_approval;
|
|||||||
|
|
||||||
pub struct FlowCoordinator {
|
pub struct FlowCoordinator {
|
||||||
pub clients: HashMap<ActorId, ActorRef<ClientSession>>,
|
pub clients: HashMap<ActorId, ActorRef<ClientSession>>,
|
||||||
useragent_registry: ActorRef<UserAgentRegistry>,
|
operator_registry: ActorRef<OperatorRegistry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FlowCoordinator {
|
impl FlowCoordinator {
|
||||||
pub fn new(useragent_registry: ActorRef<UserAgentRegistry>) -> Self {
|
pub fn new(operator_registry: ActorRef<OperatorRegistry>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
clients: HashMap::default(),
|
clients: HashMap::default(),
|
||||||
useragent_registry,
|
operator_registry,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,8 +66,8 @@ impl Actor for FlowCoordinator {
|
|||||||
|
|
||||||
#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum ApprovalError {
|
pub enum ApprovalError {
|
||||||
#[error("No user agents connected")]
|
#[error("No operators connected")]
|
||||||
NoUserAgentsConnected,
|
NoOperatorsConnected,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[messages]
|
#[messages]
|
||||||
@@ -93,19 +93,19 @@ impl FlowCoordinator {
|
|||||||
unreachable!("Expected `request_client_approval` to have callback channel");
|
unreachable!("Expected `request_client_approval` to have callback channel");
|
||||||
};
|
};
|
||||||
|
|
||||||
let Ok(refs) = self.useragent_registry.ask(GetConnected).await else {
|
let Ok(refs) = self.operator_registry.ask(GetConnected).await else {
|
||||||
reply_sender.send(Err(ApprovalError::NoUserAgentsConnected));
|
reply_sender.send(Err(ApprovalError::NoOperatorsConnected));
|
||||||
return reply;
|
return reply;
|
||||||
};
|
};
|
||||||
|
|
||||||
if refs.is_empty() {
|
if refs.is_empty() {
|
||||||
reply_sender.send(Err(ApprovalError::NoUserAgentsConnected));
|
reply_sender.send(Err(ApprovalError::NoOperatorsConnected));
|
||||||
return reply;
|
return reply;
|
||||||
}
|
}
|
||||||
|
|
||||||
ClientApprovalController::spawn(client_connect_approval::Args {
|
ClientApprovalController::spawn(client_connect_approval::Args {
|
||||||
client,
|
client,
|
||||||
user_agents: refs,
|
operators: refs,
|
||||||
reply: reply_sender,
|
reply: reply_sender,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
actors::{
|
actors::{
|
||||||
bootstrap::Bootstrapper, evm::EvmActor, flow_coordinator::FlowCoordinator,
|
bootstrap::Bootstrapper, evm::EvmActor, flow_coordinator::FlowCoordinator,
|
||||||
useragent_registry::UserAgentRegistry, vault::Vault,
|
operator_registry::OperatorRegistry, vault::Vault,
|
||||||
},
|
},
|
||||||
db,
|
db,
|
||||||
};
|
};
|
||||||
@@ -13,7 +13,7 @@ use thiserror::Error;
|
|||||||
pub mod bootstrap;
|
pub mod bootstrap;
|
||||||
pub mod evm;
|
pub mod evm;
|
||||||
pub mod flow_coordinator;
|
pub mod flow_coordinator;
|
||||||
pub mod useragent_registry;
|
pub mod operator_registry;
|
||||||
pub mod vault;
|
pub mod vault;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
@@ -31,7 +31,7 @@ pub struct GlobalActors {
|
|||||||
pub vault: ActorRef<Vault>,
|
pub vault: ActorRef<Vault>,
|
||||||
pub bootstrapper: ActorRef<Bootstrapper>,
|
pub bootstrapper: ActorRef<Bootstrapper>,
|
||||||
pub flow_coordinator: ActorRef<FlowCoordinator>,
|
pub flow_coordinator: ActorRef<FlowCoordinator>,
|
||||||
pub useragent_registry: ActorRef<UserAgentRegistry>,
|
pub operator_registry: ActorRef<OperatorRegistry>,
|
||||||
pub evm: ActorRef<EvmActor>,
|
pub evm: ActorRef<EvmActor>,
|
||||||
pub events: ActorRef<MessageBus>,
|
pub events: ActorRef<MessageBus>,
|
||||||
}
|
}
|
||||||
@@ -44,15 +44,15 @@ impl GlobalActors {
|
|||||||
pub async fn spawn(db: db::DatabasePool) -> Result<Self, SpawnError> {
|
pub async fn spawn(db: db::DatabasePool) -> Result<Self, SpawnError> {
|
||||||
let message_bus = Self::spawn_message_bus();
|
let message_bus = Self::spawn_message_bus();
|
||||||
let key_holder = Vault::spawn(Vault::new(db.clone(), message_bus.clone()).await?);
|
let key_holder = Vault::spawn(Vault::new(db.clone(), message_bus.clone()).await?);
|
||||||
let useragent_registry = UserAgentRegistry::spawn(UserAgentRegistry::default());
|
let operator_registry = OperatorRegistry::spawn(OperatorRegistry::default());
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
bootstrapper: Bootstrapper::spawn(Bootstrapper::new(&db).await?),
|
bootstrapper: Bootstrapper::spawn(Bootstrapper::new(&db).await?),
|
||||||
evm: EvmActor::spawn(EvmActor::new(key_holder.clone(), db)),
|
evm: EvmActor::spawn(EvmActor::new(key_holder.clone(), db)),
|
||||||
vault: key_holder,
|
vault: key_holder,
|
||||||
flow_coordinator: FlowCoordinator::spawn(FlowCoordinator::new(
|
flow_coordinator: FlowCoordinator::spawn(FlowCoordinator::new(
|
||||||
useragent_registry.clone(),
|
operator_registry.clone(),
|
||||||
)),
|
)),
|
||||||
useragent_registry,
|
operator_registry,
|
||||||
events: message_bus,
|
events: message_bus,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::peers::user_agent::UserAgentSession;
|
use crate::peers::operator::OperatorSession;
|
||||||
|
|
||||||
use kameo::{
|
use kameo::{
|
||||||
Actor,
|
Actor,
|
||||||
@@ -11,11 +11,11 @@ use std::{collections::HashMap, ops::ControlFlow};
|
|||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct UserAgentRegistry {
|
pub struct OperatorRegistry {
|
||||||
connected: HashMap<ActorId, ActorRef<UserAgentSession>>,
|
connected: HashMap<ActorId, ActorRef<OperatorSession>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for UserAgentRegistry {
|
impl Actor for OperatorRegistry {
|
||||||
type Args = Self;
|
type Args = Self;
|
||||||
|
|
||||||
type Error = Infallible;
|
type Error = Infallible;
|
||||||
@@ -33,8 +33,8 @@ impl Actor for UserAgentRegistry {
|
|||||||
if self.connected.remove(&id).is_some() {
|
if self.connected.remove(&id).is_some() {
|
||||||
info!(
|
info!(
|
||||||
?id,
|
?id,
|
||||||
actor = "UserAgentRegistry",
|
actor = "OperatorRegistry",
|
||||||
event = "useragent.disconnected"
|
event = "operator.disconnected"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(ControlFlow::Continue(()))
|
Ok(ControlFlow::Continue(()))
|
||||||
@@ -42,20 +42,20 @@ impl Actor for UserAgentRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[messages]
|
#[messages]
|
||||||
impl UserAgentRegistry {
|
impl OperatorRegistry {
|
||||||
#[message(ctx)]
|
#[message(ctx)]
|
||||||
pub async fn connect_useragent(
|
pub async fn connect_operator(
|
||||||
&mut self,
|
&mut self,
|
||||||
actor: ActorRef<UserAgentSession>,
|
actor: ActorRef<OperatorSession>,
|
||||||
ctx: &mut Context<Self, ()>,
|
ctx: &mut Context<Self, ()>,
|
||||||
) {
|
) {
|
||||||
info!(id = %actor.id(), actor = "UserAgentRegistry", event = "useragent.connected");
|
info!(id = %actor.id(), actor = "OperatorRegistry", event = "operator.connected");
|
||||||
ctx.actor_ref().link(&actor).await;
|
ctx.actor_ref().link(&actor).await;
|
||||||
self.connected.insert(actor.id(), actor);
|
self.connected.insert(actor.id(), actor);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[message]
|
#[message]
|
||||||
pub fn get_connected(&self) -> Vec<ActorRef<UserAgentSession>> {
|
pub fn get_connected(&self) -> Vec<ActorRef<OperatorSession>> {
|
||||||
self.connected.values().cloned().collect()
|
self.connected.values().cloned().collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,8 +248,8 @@ pub struct ProgramClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Queryable, Debug)]
|
#[derive(Queryable, Debug)]
|
||||||
#[diesel(table_name = schema::useragent_client, check_for_backend(Sqlite))]
|
#[diesel(table_name = schema::operator_client, check_for_backend(Sqlite))]
|
||||||
pub struct UseragentClient {
|
pub struct OperatorClient {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub public_key: Vec<u8>,
|
pub public_key: Vec<u8>,
|
||||||
pub created_at: SqliteTimestamp,
|
pub created_at: SqliteTimestamp,
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ diesel::table! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
useragent_client (id) {
|
operator_client (id) {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
public_key -> Binary,
|
public_key -> Binary,
|
||||||
created_at -> Integer,
|
created_at -> Integer,
|
||||||
@@ -233,5 +233,5 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||||||
program_client,
|
program_client,
|
||||||
root_key_history,
|
root_key_history,
|
||||||
tls_history,
|
tls_history,
|
||||||
useragent_client,
|
operator_client,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -176,8 +176,8 @@ impl Convert for auth::Error {
|
|||||||
InvalidChallengeSolution => ProtoAuthResult::InvalidSignature,
|
InvalidChallengeSolution => ProtoAuthResult::InvalidSignature,
|
||||||
ApproveError(auth::ApproveError::Denied) => ProtoAuthResult::ApprovalDenied,
|
ApproveError(auth::ApproveError::Denied) => ProtoAuthResult::ApprovalDenied,
|
||||||
ApproveError(auth::ApproveError::Upstream(
|
ApproveError(auth::ApproveError::Upstream(
|
||||||
crate::actors::flow_coordinator::ApprovalError::NoUserAgentsConnected,
|
crate::actors::flow_coordinator::ApprovalError::NoOperatorsConnected,
|
||||||
)) => ProtoAuthResult::NoUserAgentsOnline,
|
)) => ProtoAuthResult::NoOperatorsOnline,
|
||||||
ApproveError(auth::ApproveError::Internal)
|
ApproveError(auth::ApproveError::Internal)
|
||||||
| DatabasePoolUnavailable
|
| DatabasePoolUnavailable
|
||||||
| DatabaseOperationFailed
|
| DatabaseOperationFailed
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::peers::{client::ClientConnection, user_agent::UserAgentConnection};
|
use crate::peers::{client::ClientConnection, operator::OperatorConnection};
|
||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
proto::{
|
proto::{
|
||||||
client::{ClientRequest, ClientResponse},
|
client::{ClientRequest, ClientResponse},
|
||||||
user_agent::{UserAgentRequest, UserAgentResponse},
|
operator::{OperatorRequest, OperatorResponse},
|
||||||
},
|
},
|
||||||
transport::grpc::GrpcBi,
|
transport::grpc::GrpcBi,
|
||||||
};
|
};
|
||||||
@@ -14,7 +14,7 @@ use tracing::info;
|
|||||||
mod request_tracker;
|
mod request_tracker;
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod user_agent;
|
pub mod operator;
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ pub trait TryConvert {
|
|||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl arbiter_proto::proto::arbiter_service_server::ArbiterService for super::Server {
|
impl arbiter_proto::proto::arbiter_service_server::ArbiterService for super::Server {
|
||||||
type UserAgentStream = ReceiverStream<Result<UserAgentResponse, Status>>;
|
type OperatorStream = ReceiverStream<Result<OperatorResponse, Status>>;
|
||||||
type ClientStream = ReceiverStream<Result<ClientResponse, Status>>;
|
type ClientStream = ReceiverStream<Result<ClientResponse, Status>>;
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(self))]
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
@@ -52,23 +52,23 @@ impl arbiter_proto::proto::arbiter_service_server::ArbiterService for super::Ser
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip(self))]
|
#[tracing::instrument(level = "debug", skip(self))]
|
||||||
async fn user_agent(
|
async fn operator(
|
||||||
&self,
|
&self,
|
||||||
request: Request<tonic::Streaming<UserAgentRequest>>,
|
request: Request<tonic::Streaming<OperatorRequest>>,
|
||||||
) -> Result<Response<Self::UserAgentStream>, Status> {
|
) -> Result<Response<Self::OperatorStream>, Status> {
|
||||||
let req_stream = request.into_inner();
|
let req_stream = request.into_inner();
|
||||||
|
|
||||||
let (bi, rx) = GrpcBi::from_bi_stream(req_stream);
|
let (bi, rx) = GrpcBi::from_bi_stream(req_stream);
|
||||||
|
|
||||||
tokio::spawn(user_agent::start(
|
tokio::spawn(operator::start(
|
||||||
UserAgentConnection {
|
OperatorConnection {
|
||||||
db: self.context.db.clone(),
|
db: self.context.db.clone(),
|
||||||
actors: self.context.actors.clone(),
|
actors: self.context.actors.clone(),
|
||||||
},
|
},
|
||||||
bi,
|
bi,
|
||||||
));
|
));
|
||||||
|
|
||||||
info!(event = "connection established", "grpc.user_agent");
|
info!(event = "connection established", "grpc.operator");
|
||||||
|
|
||||||
Ok(Response::new(rx))
|
Ok(Response::new(rx))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
grpc::request_tracker::RequestTracker,
|
grpc::request_tracker::RequestTracker,
|
||||||
peers::user_agent::{OutOfBand, UserAgentConnection, UserAgentSession},
|
peers::operator::{OutOfBand, OperatorConnection, OperatorSession},
|
||||||
};
|
};
|
||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
proto::user_agent::{
|
proto::operator::{
|
||||||
UserAgentRequest, UserAgentResponse,
|
OperatorRequest, OperatorResponse,
|
||||||
user_agent_request::Payload as UserAgentRequestPayload,
|
operator_request::Payload as OperatorRequestPayload,
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
operator_response::Payload as OperatorResponsePayload,
|
||||||
},
|
},
|
||||||
transport::{Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
transport::{Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
||||||
};
|
};
|
||||||
@@ -38,8 +38,8 @@ impl Sender<OutOfBand> for OutOfBandAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn dispatch_loop(
|
async fn dispatch_loop(
|
||||||
mut bi: GrpcBi<UserAgentRequest, UserAgentResponse>,
|
mut bi: GrpcBi<OperatorRequest, OperatorResponse>,
|
||||||
actor: ActorRef<UserAgentSession>,
|
actor: ActorRef<OperatorSession>,
|
||||||
mut receiver: mpsc::Receiver<OutOfBand>,
|
mut receiver: mpsc::Receiver<OutOfBand>,
|
||||||
mut request_tracker: RequestTracker,
|
mut request_tracker: RequestTracker,
|
||||||
) {
|
) {
|
||||||
@@ -53,7 +53,7 @@ async fn dispatch_loop(
|
|||||||
|
|
||||||
let payload = sdk_client::out_of_band_payload(oob);
|
let payload = sdk_client::out_of_band_payload(oob);
|
||||||
|
|
||||||
if bi.send(Ok(UserAgentResponse { id: None, payload: Some(payload) })).await.is_err() {
|
if bi.send(Ok(OperatorResponse { id: None, payload: Some(payload) })).await.is_err() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +64,7 @@ async fn dispatch_loop(
|
|||||||
let conn = match message {
|
let conn = match message {
|
||||||
Ok(conn) => conn,
|
Ok(conn) => conn,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(error = ?err, "Failed to receive user agent request");
|
warn!(error = ?err, "Failed to receive operator request");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -78,13 +78,13 @@ async fn dispatch_loop(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let Some(payload) = conn.payload else {
|
let Some(payload) = conn.payload else {
|
||||||
let _ = bi.send(Err(Status::invalid_argument("Missing user-agent request payload"))).await;
|
let _ = bi.send(Err(Status::invalid_argument("Missing operator request payload"))).await;
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
match dispatch_inner(&actor, payload).await {
|
match dispatch_inner(&actor, payload).await {
|
||||||
Ok(Some(response)) => {
|
Ok(Some(response)) => {
|
||||||
if bi.send(Ok(UserAgentResponse {
|
if bi.send(Ok(OperatorResponse {
|
||||||
id: Some(request_id),
|
id: Some(request_id),
|
||||||
payload: Some(response),
|
payload: Some(response),
|
||||||
})).await.is_err() {
|
})).await.is_err() {
|
||||||
@@ -93,7 +93,7 @@ async fn dispatch_loop(
|
|||||||
}
|
}
|
||||||
Ok(None) => {}
|
Ok(None) => {}
|
||||||
Err(status) => {
|
Err(status) => {
|
||||||
error!(?status, "Failed to process user agent request");
|
error!(?status, "Failed to process operator request");
|
||||||
let _ = bi.send(Err(status)).await;
|
let _ = bi.send(Err(status)).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -104,23 +104,23 @@ async fn dispatch_loop(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn dispatch_inner(
|
async fn dispatch_inner(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
payload: UserAgentRequestPayload,
|
payload: OperatorRequestPayload,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
match payload {
|
match payload {
|
||||||
UserAgentRequestPayload::Vault(req) => vault::dispatch(actor, req).await,
|
OperatorRequestPayload::Vault(req) => vault::dispatch(actor, req).await,
|
||||||
UserAgentRequestPayload::Evm(req) => evm::dispatch(actor, req).await,
|
OperatorRequestPayload::Evm(req) => evm::dispatch(actor, req).await,
|
||||||
UserAgentRequestPayload::SdkClient(req) => sdk_client::dispatch(actor, req).await,
|
OperatorRequestPayload::SdkClient(req) => sdk_client::dispatch(actor, req).await,
|
||||||
UserAgentRequestPayload::Auth(..) => {
|
OperatorRequestPayload::Auth(..) => {
|
||||||
warn!("Unsupported post-auth user agent auth request");
|
warn!("Unsupported post-auth operator auth request");
|
||||||
Err(Status::invalid_argument("Unsupported user-agent request"))
|
Err(Status::invalid_argument("Unsupported operator request"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start(
|
pub async fn start(
|
||||||
mut conn: UserAgentConnection,
|
mut conn: OperatorConnection,
|
||||||
mut bi: GrpcBi<UserAgentRequest, UserAgentResponse>,
|
mut bi: GrpcBi<OperatorRequest, OperatorResponse>,
|
||||||
) {
|
) {
|
||||||
let mut request_tracker = RequestTracker::default();
|
let mut request_tracker = RequestTracker::default();
|
||||||
|
|
||||||
@@ -129,16 +129,16 @@ pub async fn start(
|
|||||||
|
|
||||||
let actor = {
|
let actor = {
|
||||||
let transport = auth::AuthTransportAdapter::new(&mut bi, &mut request_tracker);
|
let transport = auth::AuthTransportAdapter::new(&mut bi, &mut request_tracker);
|
||||||
match crate::peers::user_agent::start(&mut conn, transport, Box::new(oob_adapter)).await {
|
match crate::peers::operator::start(&mut conn, transport, Box::new(oob_adapter)).await {
|
||||||
Ok(actor) => actor,
|
Ok(actor) => actor,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!(error = ?e, "User agent connection failed");
|
warn!(error = ?e, "Operator connection failed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("User agent session established");
|
info!("Operator session established");
|
||||||
|
|
||||||
dispatch_loop(bi, actor.clone(), oob_receiver, request_tracker).await;
|
dispatch_loop(bi, actor.clone(), oob_receiver, request_tracker).await;
|
||||||
actor.kill();
|
actor.kill();
|
||||||
@@ -1,16 +1,16 @@
|
|||||||
use crate::{grpc::request_tracker::RequestTracker, peers::user_agent::auth};
|
use crate::{grpc::request_tracker::RequestTracker, peers::operator::auth};
|
||||||
use arbiter_crypto::authn;
|
use arbiter_crypto::authn;
|
||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
proto::user_agent::{
|
proto::operator::{
|
||||||
UserAgentRequest, UserAgentResponse,
|
OperatorRequest, OperatorResponse,
|
||||||
auth::{
|
auth::{
|
||||||
self as proto_auth, AuthChallenge as ProtoAuthChallenge,
|
self as proto_auth, AuthChallenge as ProtoAuthChallenge,
|
||||||
AuthChallengeRequest as ProtoAuthChallengeRequest,
|
AuthChallengeRequest as ProtoAuthChallengeRequest,
|
||||||
AuthChallengeSolution as ProtoAuthChallengeSolution, AuthResult as ProtoAuthResult,
|
AuthChallengeSolution as ProtoAuthChallengeSolution, AuthResult as ProtoAuthResult,
|
||||||
request::Payload as AuthRequestPayload, response::Payload as AuthResponsePayload,
|
request::Payload as AuthRequestPayload, response::Payload as AuthResponsePayload,
|
||||||
},
|
},
|
||||||
user_agent_request::Payload as UserAgentRequestPayload,
|
operator_request::Payload as OperatorRequestPayload,
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
operator_response::Payload as OperatorResponsePayload,
|
||||||
},
|
},
|
||||||
transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
||||||
};
|
};
|
||||||
@@ -20,13 +20,13 @@ use tonic::Status;
|
|||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
pub(super) struct AuthTransportAdapter<'a> {
|
pub(super) struct AuthTransportAdapter<'a> {
|
||||||
pub(super) bi: &'a mut GrpcBi<UserAgentRequest, UserAgentResponse>,
|
pub(super) bi: &'a mut GrpcBi<OperatorRequest, OperatorResponse>,
|
||||||
pub(super) request_tracker: &'a mut RequestTracker,
|
pub(super) request_tracker: &'a mut RequestTracker,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AuthTransportAdapter<'a> {
|
impl<'a> AuthTransportAdapter<'a> {
|
||||||
pub(super) const fn new(
|
pub(super) const fn new(
|
||||||
bi: &'a mut GrpcBi<UserAgentRequest, UserAgentResponse>,
|
bi: &'a mut GrpcBi<OperatorRequest, OperatorResponse>,
|
||||||
request_tracker: &'a mut RequestTracker,
|
request_tracker: &'a mut RequestTracker,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -35,7 +35,7 @@ impl<'a> AuthTransportAdapter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) const fn bi_mut(&mut self) -> &mut GrpcBi<UserAgentRequest, UserAgentResponse> {
|
pub(super) const fn bi_mut(&mut self) -> &mut GrpcBi<OperatorRequest, OperatorResponse> {
|
||||||
self.bi
|
self.bi
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,21 +45,21 @@ impl<'a> AuthTransportAdapter<'a> {
|
|||||||
|
|
||||||
pub(super) async fn send_response_payload(
|
pub(super) async fn send_response_payload(
|
||||||
&mut self,
|
&mut self,
|
||||||
payload: UserAgentResponsePayload,
|
payload: OperatorResponsePayload,
|
||||||
) -> Result<(), TransportError> {
|
) -> Result<(), TransportError> {
|
||||||
self.bi
|
self.bi
|
||||||
.send(Ok(UserAgentResponse {
|
.send(Ok(OperatorResponse {
|
||||||
id: Some(self.request_tracker.current_request_id()),
|
id: Some(self.request_tracker.current_request_id()),
|
||||||
payload: Some(payload),
|
payload: Some(payload),
|
||||||
}))
|
}))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_user_agent_response(
|
async fn send_operator_response(
|
||||||
&mut self,
|
&mut self,
|
||||||
payload: AuthResponsePayload,
|
payload: AuthResponsePayload,
|
||||||
) -> Result<(), TransportError> {
|
) -> Result<(), TransportError> {
|
||||||
self.send_response_payload(UserAgentResponsePayload::Auth(proto_auth::Response {
|
self.send_response_payload(OperatorResponsePayload::Auth(proto_auth::Response {
|
||||||
payload: Some(payload),
|
payload: Some(payload),
|
||||||
}))
|
}))
|
||||||
.await
|
.await
|
||||||
@@ -107,7 +107,7 @@ impl Sender<Result<auth::Outbound, auth::Error>> for AuthTransportAdapter<'_> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.send_user_agent_response(payload).await
|
self.send_operator_response(payload).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,7 +117,7 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
let request = match self.bi.recv().await? {
|
let request = match self.bi.recv().await? {
|
||||||
Ok(request) => request,
|
Ok(request) => request,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
warn!(error = ?error, "Failed to receive user agent auth request");
|
warn!(error = ?error, "Failed to receive operator auth request");
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -133,16 +133,16 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
let Some(payload) = request.payload else {
|
let Some(payload) = request.payload else {
|
||||||
warn!(
|
warn!(
|
||||||
event = "received request with empty payload",
|
event = "received request with empty payload",
|
||||||
"grpc.useragent.auth_adapter"
|
"grpc.operator.auth_adapter"
|
||||||
);
|
);
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
|
|
||||||
let UserAgentRequestPayload::Auth(auth_request) = payload else {
|
let OperatorRequestPayload::Auth(auth_request) = payload else {
|
||||||
let _ = self
|
let _ = self
|
||||||
.bi
|
.bi
|
||||||
.send(Err(Status::invalid_argument(
|
.send(Err(Status::invalid_argument(
|
||||||
"Unsupported user-agent auth request",
|
"Unsupported operator auth request",
|
||||||
)))
|
)))
|
||||||
.await;
|
.await;
|
||||||
return None;
|
return None;
|
||||||
@@ -151,7 +151,7 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
let Some(payload) = auth_request.payload else {
|
let Some(payload) = auth_request.payload else {
|
||||||
warn!(
|
warn!(
|
||||||
event = "received auth request with empty payload",
|
event = "received auth request with empty payload",
|
||||||
"grpc.useragent.auth_adapter"
|
"grpc.operator.auth_adapter"
|
||||||
);
|
);
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
@@ -164,7 +164,7 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
let Ok(pubkey) = authn::PublicKey::try_from(pubkey.as_slice()) else {
|
let Ok(pubkey) = authn::PublicKey::try_from(pubkey.as_slice()) else {
|
||||||
warn!(
|
warn!(
|
||||||
event = "received request with invalid public key",
|
event = "received request with invalid public key",
|
||||||
"grpc.useragent.auth_adapter"
|
"grpc.operator.auth_adapter"
|
||||||
);
|
);
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
@@ -3,8 +3,8 @@ use crate::{
|
|||||||
Convert, TryConvert,
|
Convert, TryConvert,
|
||||||
common::inbound::{RawEvmAddress, RawEvmTransaction},
|
common::inbound::{RawEvmAddress, RawEvmTransaction},
|
||||||
},
|
},
|
||||||
peers::user_agent::{
|
peers::operator::{
|
||||||
UserAgentSession,
|
OperatorSession,
|
||||||
session::handlers::{
|
session::handlers::{
|
||||||
GrantMutationError, HandleEvmWalletCreate, HandleEvmWalletList, HandleGrantCreate,
|
GrantMutationError, HandleEvmWalletCreate, HandleEvmWalletList, HandleGrantCreate,
|
||||||
HandleGrantDelete, HandleGrantList, HandleSignTransaction,
|
HandleGrantDelete, HandleGrantList, HandleSignTransaction,
|
||||||
@@ -24,12 +24,12 @@ use arbiter_proto::proto::{
|
|||||||
wallet_create_response::Result as WalletCreateResult,
|
wallet_create_response::Result as WalletCreateResult,
|
||||||
wallet_list_response::Result as WalletListResult,
|
wallet_list_response::Result as WalletListResult,
|
||||||
},
|
},
|
||||||
user_agent::{
|
operator::{
|
||||||
evm::{
|
evm::{
|
||||||
self as proto_evm, SignTransactionRequest as ProtoSignTransactionRequest,
|
self as proto_evm, SignTransactionRequest as ProtoSignTransactionRequest,
|
||||||
request::Payload as EvmRequestPayload, response::Payload as EvmResponsePayload,
|
request::Payload as EvmRequestPayload, response::Payload as EvmResponsePayload,
|
||||||
},
|
},
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
operator_response::Payload as OperatorResponsePayload,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,16 +37,16 @@ use kameo::actor::ActorRef;
|
|||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
const fn wrap_evm_response(payload: EvmResponsePayload) -> UserAgentResponsePayload {
|
const fn wrap_evm_response(payload: EvmResponsePayload) -> OperatorResponsePayload {
|
||||||
UserAgentResponsePayload::Evm(proto_evm::Response {
|
OperatorResponsePayload::Evm(proto_evm::Response {
|
||||||
payload: Some(payload),
|
payload: Some(payload),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn dispatch(
|
pub(super) async fn dispatch(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
req: proto_evm::Request,
|
req: proto_evm::Request,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
let Some(payload) = req.payload else {
|
let Some(payload) = req.payload else {
|
||||||
return Err(Status::invalid_argument("Missing EVM request payload"));
|
return Err(Status::invalid_argument("Missing EVM request payload"));
|
||||||
};
|
};
|
||||||
@@ -62,8 +62,8 @@ pub(super) async fn dispatch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_wallet_create(
|
async fn handle_wallet_create(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
let result = match actor.ask(HandleEvmWalletCreate {}).await {
|
let result = match actor.ask(HandleEvmWalletCreate {}).await {
|
||||||
Ok((wallet_id, address)) => WalletCreateResult::Wallet(WalletEntry {
|
Ok((wallet_id, address)) => WalletCreateResult::Wallet(WalletEntry {
|
||||||
id: wallet_id,
|
id: wallet_id,
|
||||||
@@ -82,8 +82,8 @@ async fn handle_wallet_create(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_wallet_list(
|
async fn handle_wallet_list(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
let result = match actor.ask(HandleEvmWalletList {}).await {
|
let result = match actor.ask(HandleEvmWalletList {}).await {
|
||||||
Ok(wallets) => WalletListResult::Wallets(WalletList {
|
Ok(wallets) => WalletListResult::Wallets(WalletList {
|
||||||
wallets: wallets
|
wallets: wallets
|
||||||
@@ -107,8 +107,8 @@ async fn handle_wallet_list(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_grant_list(
|
async fn handle_grant_list(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
let result = match actor.ask(HandleGrantList {}).await {
|
let result = match actor.ask(HandleGrantList {}).await {
|
||||||
Ok(grants) => EvmGrantListResult::Grants(EvmGrantList {
|
Ok(grants) => EvmGrantListResult::Grants(EvmGrantList {
|
||||||
grants: grants
|
grants: grants
|
||||||
@@ -134,9 +134,9 @@ async fn handle_grant_list(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_grant_create(
|
async fn handle_grant_create(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
req: EvmGrantCreateRequest,
|
req: EvmGrantCreateRequest,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
let basic = req
|
let basic = req
|
||||||
.shared
|
.shared
|
||||||
.ok_or_else(|| Status::invalid_argument("Missing shared grant settings"))?
|
.ok_or_else(|| Status::invalid_argument("Missing shared grant settings"))?
|
||||||
@@ -164,9 +164,9 @@ async fn handle_grant_create(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_grant_delete(
|
async fn handle_grant_delete(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
req: EvmGrantDeleteRequest,
|
req: EvmGrantDeleteRequest,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
let result = match actor
|
let result = match actor
|
||||||
.ask(HandleGrantDelete {
|
.ask(HandleGrantDelete {
|
||||||
grant_id: req.grant_id,
|
grant_id: req.grant_id,
|
||||||
@@ -190,9 +190,9 @@ async fn handle_grant_delete(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_sign_transaction(
|
async fn handle_sign_transaction(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
req: ProtoSignTransactionRequest,
|
req: ProtoSignTransactionRequest,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
let request = req
|
let request = req
|
||||||
.request
|
.request
|
||||||
.ok_or_else(|| Status::invalid_argument("Missing sign transaction request"))?;
|
.ok_or_else(|| Status::invalid_argument("Missing sign transaction request"))?;
|
||||||
@@ -14,7 +14,7 @@ use arbiter_proto::{
|
|||||||
TransactionRateLimit as ProtoTransactionRateLimit, VolumeRateLimit as ProtoVolumeRateLimit,
|
TransactionRateLimit as ProtoTransactionRateLimit, VolumeRateLimit as ProtoVolumeRateLimit,
|
||||||
specific_grant::Grant as ProtoSpecificGrantType,
|
specific_grant::Grant as ProtoSpecificGrantType,
|
||||||
},
|
},
|
||||||
proto::user_agent::sdk_client::{WalletAccess, WalletAccessEntry as SdkClientWalletAccess},
|
proto::operator::sdk_client::{WalletAccess, WalletAccessEntry as SdkClientWalletAccess},
|
||||||
};
|
};
|
||||||
|
|
||||||
use alloy::primitives::{Address, U256};
|
use alloy::primitives::{Address, U256};
|
||||||
@@ -10,7 +10,7 @@ use arbiter_proto::proto::{
|
|||||||
TransactionRateLimit as ProtoTransactionRateLimit, VolumeRateLimit as ProtoVolumeRateLimit,
|
TransactionRateLimit as ProtoTransactionRateLimit, VolumeRateLimit as ProtoVolumeRateLimit,
|
||||||
specific_grant::Grant as ProtoSpecificGrantType,
|
specific_grant::Grant as ProtoSpecificGrantType,
|
||||||
},
|
},
|
||||||
user_agent::sdk_client::{WalletAccess, WalletAccessEntry as ProtoSdkClientWalletAccess},
|
operator::sdk_client::{WalletAccess, WalletAccessEntry as ProtoSdkClientWalletAccess},
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
db::models::NewEvmWalletAccess,
|
db::models::NewEvmWalletAccess,
|
||||||
grpc::Convert,
|
grpc::Convert,
|
||||||
peers::user_agent::{
|
peers::operator::{
|
||||||
OutOfBand, UserAgentSession,
|
OutOfBand, OperatorSession,
|
||||||
session::handlers::{
|
session::handlers::{
|
||||||
HandleGrantEvmWalletAccess, HandleListWalletAccess, HandleNewClientApprove,
|
HandleGrantEvmWalletAccess, HandleListWalletAccess, HandleNewClientApprove,
|
||||||
HandleRevokeEvmWalletAccess, HandleSdkClientList,
|
HandleRevokeEvmWalletAccess, HandleSdkClientList,
|
||||||
@@ -12,7 +12,7 @@ use crate::{
|
|||||||
use arbiter_crypto::authn;
|
use arbiter_crypto::authn;
|
||||||
use arbiter_proto::proto::{
|
use arbiter_proto::proto::{
|
||||||
shared::ClientInfo as ProtoClientMetadata,
|
shared::ClientInfo as ProtoClientMetadata,
|
||||||
user_agent::{
|
operator::{
|
||||||
sdk_client::{
|
sdk_client::{
|
||||||
self as proto_sdk_client, ConnectionCancel as ProtoSdkClientConnectionCancel,
|
self as proto_sdk_client, ConnectionCancel as ProtoSdkClientConnectionCancel,
|
||||||
ConnectionRequest as ProtoSdkClientConnectionRequest,
|
ConnectionRequest as ProtoSdkClientConnectionRequest,
|
||||||
@@ -24,7 +24,7 @@ use arbiter_proto::proto::{
|
|||||||
request::Payload as SdkClientRequestPayload,
|
request::Payload as SdkClientRequestPayload,
|
||||||
response::Payload as SdkClientResponsePayload,
|
response::Payload as SdkClientResponsePayload,
|
||||||
},
|
},
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
operator_response::Payload as OperatorResponsePayload,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -32,13 +32,13 @@ use kameo::actor::ActorRef;
|
|||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
const fn wrap_sdk_client_response(payload: SdkClientResponsePayload) -> UserAgentResponsePayload {
|
const fn wrap_sdk_client_response(payload: SdkClientResponsePayload) -> OperatorResponsePayload {
|
||||||
UserAgentResponsePayload::SdkClient(proto_sdk_client::Response {
|
OperatorResponsePayload::SdkClient(proto_sdk_client::Response {
|
||||||
payload: Some(payload),
|
payload: Some(payload),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn out_of_band_payload(oob: OutOfBand) -> UserAgentResponsePayload {
|
pub(super) fn out_of_band_payload(oob: OutOfBand) -> OperatorResponsePayload {
|
||||||
match oob {
|
match oob {
|
||||||
OutOfBand::ClientConnectionRequest { profile } => wrap_sdk_client_response(
|
OutOfBand::ClientConnectionRequest { profile } => wrap_sdk_client_response(
|
||||||
SdkClientResponsePayload::ConnectionRequest(ProtoSdkClientConnectionRequest {
|
SdkClientResponsePayload::ConnectionRequest(ProtoSdkClientConnectionRequest {
|
||||||
@@ -59,9 +59,9 @@ pub(super) fn out_of_band_payload(oob: OutOfBand) -> UserAgentResponsePayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn dispatch(
|
pub(super) async fn dispatch(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
req: proto_sdk_client::Request,
|
req: proto_sdk_client::Request,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
let Some(payload) = req.payload else {
|
let Some(payload) = req.payload else {
|
||||||
return Err(Status::invalid_argument(
|
return Err(Status::invalid_argument(
|
||||||
"Missing SDK client request payload",
|
"Missing SDK client request payload",
|
||||||
@@ -87,9 +87,9 @@ pub(super) async fn dispatch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_connection_response(
|
async fn handle_connection_response(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
resp: ProtoSdkClientConnectionResponse,
|
resp: ProtoSdkClientConnectionResponse,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
let pubkey = authn::PublicKey::try_from(resp.pubkey.as_slice())
|
let pubkey = authn::PublicKey::try_from(resp.pubkey.as_slice())
|
||||||
.map_err(|()| Status::invalid_argument("Invalid ML-DSA public key"))?;
|
.map_err(|()| Status::invalid_argument("Invalid ML-DSA public key"))?;
|
||||||
|
|
||||||
@@ -108,8 +108,8 @@ async fn handle_connection_response(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_list(
|
async fn handle_list(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
let result = match actor.ask(HandleSdkClientList {}).await {
|
let result = match actor.ask(HandleSdkClientList {}).await {
|
||||||
Ok(clients) => ProtoSdkClientListResult::Clients(ProtoSdkClientList {
|
Ok(clients) => ProtoSdkClientListResult::Clients(ProtoSdkClientList {
|
||||||
clients: clients
|
clients: clients
|
||||||
@@ -144,9 +144,9 @@ async fn handle_list(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_grant_wallet_access(
|
async fn handle_grant_wallet_access(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
req: ProtoSdkClientGrantWalletAccess,
|
req: ProtoSdkClientGrantWalletAccess,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
let entries: Vec<NewEvmWalletAccess> = req.accesses.into_iter().map(Convert::convert).collect();
|
let entries: Vec<NewEvmWalletAccess> = req.accesses.into_iter().map(Convert::convert).collect();
|
||||||
match actor.ask(HandleGrantEvmWalletAccess { entries }).await {
|
match actor.ask(HandleGrantEvmWalletAccess { entries }).await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
@@ -161,9 +161,9 @@ async fn handle_grant_wallet_access(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_revoke_wallet_access(
|
async fn handle_revoke_wallet_access(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
req: ProtoSdkClientRevokeWalletAccess,
|
req: ProtoSdkClientRevokeWalletAccess,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
match actor
|
match actor
|
||||||
.ask(HandleRevokeEvmWalletAccess {
|
.ask(HandleRevokeEvmWalletAccess {
|
||||||
entries: req.accesses,
|
entries: req.accesses,
|
||||||
@@ -182,8 +182,8 @@ async fn handle_revoke_wallet_access(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_list_wallet_access(
|
async fn handle_list_wallet_access(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
match actor.ask(HandleListWalletAccess {}).await {
|
match actor.ask(HandleListWalletAccess {}).await {
|
||||||
Ok(accesses) => Ok(Some(wrap_sdk_client_response(
|
Ok(accesses) => Ok(Some(wrap_sdk_client_response(
|
||||||
SdkClientResponsePayload::ListWalletAccess(ListWalletAccessResponse {
|
SdkClientResponsePayload::ListWalletAccess(ListWalletAccessResponse {
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
actors::vault::VaultState,
|
actors::vault::VaultState,
|
||||||
peers::user_agent::{UserAgentSession, session::handlers::HandleQueryVaultState},
|
peers::operator::{OperatorSession, session::handlers::HandleQueryVaultState},
|
||||||
};
|
};
|
||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
proto::shared::VaultState as ProtoVaultState,
|
proto::shared::VaultState as ProtoVaultState,
|
||||||
proto::user_agent::{
|
proto::operator::{
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
operator_response::Payload as OperatorResponsePayload,
|
||||||
vault::{
|
vault::{
|
||||||
self as proto_vault, request::Payload as VaultRequestPayload,
|
self as proto_vault, request::Payload as VaultRequestPayload,
|
||||||
response::Payload as VaultResponsePayload,
|
response::Payload as VaultResponsePayload,
|
||||||
@@ -17,16 +17,16 @@ use kameo::actor::ActorRef;
|
|||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
const fn wrap_vault_response(payload: VaultResponsePayload) -> UserAgentResponsePayload {
|
const fn wrap_vault_response(payload: VaultResponsePayload) -> OperatorResponsePayload {
|
||||||
UserAgentResponsePayload::Vault(proto_vault::Response {
|
OperatorResponsePayload::Vault(proto_vault::Response {
|
||||||
payload: Some(payload),
|
payload: Some(payload),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) async fn dispatch(
|
pub(super) async fn dispatch(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
req: proto_vault::Request,
|
req: proto_vault::Request,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
let Some(payload) = req.payload else {
|
let Some(payload) = req.payload else {
|
||||||
return Err(Status::invalid_argument("Missing vault request payload"));
|
return Err(Status::invalid_argument("Missing vault request payload"));
|
||||||
};
|
};
|
||||||
@@ -42,8 +42,8 @@ pub(super) async fn dispatch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_query_vault_state(
|
async fn handle_query_vault_state(
|
||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<OperatorSession>,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<OperatorResponsePayload>, Status> {
|
||||||
let state = match actor.ask(HandleQueryVaultState {}).await {
|
let state = match actor.ask(HandleQueryVaultState {}).await {
|
||||||
Ok(VaultState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
|
Ok(VaultState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
|
||||||
Ok(VaultState::Sealed) => ProtoVaultState::Sealed,
|
Ok(VaultState::Sealed) => ProtoVaultState::Sealed,
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use super::auth::AuthTransportAdapter;
|
use super::auth::AuthTransportAdapter;
|
||||||
use crate::{
|
use crate::{
|
||||||
grpc::TryConvert,
|
grpc::TryConvert,
|
||||||
peers::user_agent::vault_gate::{self as vault_gate},
|
peers::operator::vault_gate::{self as vault_gate},
|
||||||
};
|
};
|
||||||
use arbiter_proto::transport::{Bi, Error as TransportError, Receiver, Sender};
|
use arbiter_proto::transport::{Bi, Error as TransportError, Receiver, Sender};
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ impl Receiver<vault_gate::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
Err(error) => {
|
Err(error) => {
|
||||||
warn!(
|
warn!(
|
||||||
?error,
|
?error,
|
||||||
"Failed to receive user agent request during vault gate"
|
"Failed to receive operator request during vault gate"
|
||||||
);
|
);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
grpc::{Convert, TryConvert},
|
grpc::{Convert, TryConvert},
|
||||||
peers::user_agent::vault_gate::{
|
peers::operator::vault_gate::{
|
||||||
self as vault_gate, HandleBootstrapEncryptedKey, HandleHandshake, HandleUnsealEncryptedKey,
|
self as vault_gate, HandleBootstrapEncryptedKey, HandleHandshake, HandleUnsealEncryptedKey,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use arbiter_proto::proto::user_agent::{
|
use arbiter_proto::proto::operator::{
|
||||||
user_agent_request::Payload as UserAgentRequestPayload,
|
operator_request::Payload as OperatorRequestPayload,
|
||||||
vault::{
|
vault::{
|
||||||
self as proto_vault,
|
self as proto_vault,
|
||||||
bootstrap::{self as proto_bootstrap},
|
bootstrap::{self as proto_bootstrap},
|
||||||
@@ -16,7 +16,7 @@ use arbiter_proto::proto::user_agent::{
|
|||||||
|
|
||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
|
|
||||||
impl TryConvert for UserAgentRequestPayload {
|
impl TryConvert for OperatorRequestPayload {
|
||||||
type Output = vault_gate::Inbound;
|
type Output = vault_gate::Inbound;
|
||||||
type Error = Status;
|
type Error = Status;
|
||||||
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
actors::vault::VaultState,
|
actors::vault::VaultState,
|
||||||
grpc::{Convert, TryConvert},
|
grpc::{Convert, TryConvert},
|
||||||
peers::user_agent::vault_gate::{self as vault_gate},
|
peers::operator::vault_gate::{self as vault_gate},
|
||||||
};
|
};
|
||||||
use arbiter_proto::proto::{
|
use arbiter_proto::proto::{
|
||||||
shared::VaultState as ProtoVaultState,
|
shared::VaultState as ProtoVaultState,
|
||||||
user_agent::{
|
operator::{
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
operator_response::Payload as OperatorResponsePayload,
|
||||||
vault::{
|
vault::{
|
||||||
self as proto_vault,
|
self as proto_vault,
|
||||||
bootstrap::{self as proto_bootstrap, BootstrapResult as ProtoBootstrapResult},
|
bootstrap::{self as proto_bootstrap, BootstrapResult as ProtoBootstrapResult},
|
||||||
@@ -22,28 +22,28 @@ use arbiter_proto::proto::{
|
|||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
const fn wrap_vault_response(payload: VaultResponsePayload) -> UserAgentResponsePayload {
|
const fn wrap_vault_response(payload: VaultResponsePayload) -> OperatorResponsePayload {
|
||||||
UserAgentResponsePayload::Vault(proto_vault::Response {
|
OperatorResponsePayload::Vault(proto_vault::Response {
|
||||||
payload: Some(payload),
|
payload: Some(payload),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn wrap_unseal_response(payload: UnsealResponsePayload) -> UserAgentResponsePayload {
|
const fn wrap_unseal_response(payload: UnsealResponsePayload) -> OperatorResponsePayload {
|
||||||
wrap_vault_response(VaultResponsePayload::Unseal(proto_unseal::Response {
|
wrap_vault_response(VaultResponsePayload::Unseal(proto_unseal::Response {
|
||||||
payload: Some(payload),
|
payload: Some(payload),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrap_bootstrap_response(result: ProtoBootstrapResult) -> UserAgentResponsePayload {
|
fn wrap_bootstrap_response(result: ProtoBootstrapResult) -> OperatorResponsePayload {
|
||||||
wrap_vault_response(VaultResponsePayload::Bootstrap(proto_bootstrap::Response {
|
wrap_vault_response(VaultResponsePayload::Bootstrap(proto_bootstrap::Response {
|
||||||
result: result.into(),
|
result: result.into(),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Convert for VaultState {
|
impl Convert for VaultState {
|
||||||
type Output = UserAgentResponsePayload;
|
type Output = OperatorResponsePayload;
|
||||||
|
|
||||||
fn convert(self) -> UserAgentResponsePayload {
|
fn convert(self) -> OperatorResponsePayload {
|
||||||
let proto_state = match self {
|
let proto_state = match self {
|
||||||
Self::Unbootstrapped => ProtoVaultState::Unbootstrapped,
|
Self::Unbootstrapped => ProtoVaultState::Unbootstrapped,
|
||||||
Self::Sealed => ProtoVaultState::Sealed,
|
Self::Sealed => ProtoVaultState::Sealed,
|
||||||
@@ -54,9 +54,9 @@ impl Convert for VaultState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Convert for vault_gate::HandshakeResponse {
|
impl Convert for vault_gate::HandshakeResponse {
|
||||||
type Output = UserAgentResponsePayload;
|
type Output = OperatorResponsePayload;
|
||||||
|
|
||||||
fn convert(self) -> UserAgentResponsePayload {
|
fn convert(self) -> OperatorResponsePayload {
|
||||||
wrap_unseal_response(UnsealResponsePayload::Start(
|
wrap_unseal_response(UnsealResponsePayload::Start(
|
||||||
proto_unseal::UnsealStartResponse {
|
proto_unseal::UnsealStartResponse {
|
||||||
server_pubkey: self.server_pubkey.as_bytes().to_vec(),
|
server_pubkey: self.server_pubkey.as_bytes().to_vec(),
|
||||||
@@ -66,10 +66,10 @@ impl Convert for vault_gate::HandshakeResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TryConvert for vault_gate::Outbound {
|
impl TryConvert for vault_gate::Outbound {
|
||||||
type Output = UserAgentResponsePayload;
|
type Output = OperatorResponsePayload;
|
||||||
type Error = Status;
|
type Error = Status;
|
||||||
|
|
||||||
fn try_convert(self) -> Result<UserAgentResponsePayload, Status> {
|
fn try_convert(self) -> Result<OperatorResponsePayload, Status> {
|
||||||
match self {
|
match self {
|
||||||
Self::HandleVaultState(result) => result
|
Self::HandleVaultState(result) => result
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
@@ -54,7 +54,7 @@ impl From<diesel::result::Error> for Error {
|
|||||||
pub enum ApproveError {
|
pub enum ApproveError {
|
||||||
#[error("Internal error")]
|
#[error("Internal error")]
|
||||||
Internal,
|
Internal,
|
||||||
#[error("Client connection denied by user agents")]
|
#[error("Client connection denied by operators")]
|
||||||
Denied,
|
Denied,
|
||||||
#[error("Upstream error: {0}")]
|
#[error("Upstream error: {0}")]
|
||||||
Upstream(flow_coordinator::ApprovalError),
|
Upstream(flow_coordinator::ApprovalError),
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod user_agent;
|
pub mod operator;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use super::{Credentials, UserAgentConnection};
|
use super::{Credentials, OperatorConnection};
|
||||||
use arbiter_crypto::authn::{self, AuthChallenge};
|
use arbiter_crypto::authn::{self, AuthChallenge};
|
||||||
use arbiter_proto::transport::Bi;
|
use arbiter_proto::transport::Bi;
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ fn parse_auth_event(payload: Inbound) -> AuthEvents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn authenticate<T>(
|
pub async fn authenticate<T>(
|
||||||
props: &mut UserAgentConnection,
|
props: &mut OperatorConnection,
|
||||||
transport: &mut T,
|
transport: &mut T,
|
||||||
) -> Result<Credentials, Error>
|
) -> Result<Credentials, Error>
|
||||||
where
|
where
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
use super::{
|
use super::{
|
||||||
super::{Credentials, UserAgentConnection},
|
super::{Credentials, OperatorConnection},
|
||||||
Error,
|
Error,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::bootstrap::ConsumeToken,
|
actors::bootstrap::ConsumeToken,
|
||||||
db::{DatabasePool, schema::useragent_client},
|
db::{DatabasePool, schema::operator_client},
|
||||||
peers::user_agent::auth::Outbound,
|
peers::operator::auth::Outbound,
|
||||||
};
|
};
|
||||||
use arbiter_crypto::authn::{self, AuthChallenge, USERAGENT_CONTEXT};
|
use arbiter_crypto::authn::{self, AuthChallenge, OPERATOR_CONTEXT};
|
||||||
use arbiter_proto::transport::Bi;
|
use arbiter_proto::transport::Bi;
|
||||||
|
|
||||||
use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl};
|
use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl};
|
||||||
@@ -44,9 +44,9 @@ async fn get_client_id(db: &DatabasePool, pubkey: &authn::PublicKey) -> Result<O
|
|||||||
Error::internal("Database unavailable")
|
Error::internal("Database unavailable")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
useragent_client::table
|
operator_client::table
|
||||||
.filter(useragent_client::public_key.eq(pubkey.to_bytes()))
|
.filter(operator_client::public_key.eq(pubkey.to_bytes()))
|
||||||
.select(useragent_client::id)
|
.select(operator_client::id)
|
||||||
.first::<i32>(&mut conn)
|
.first::<i32>(&mut conn)
|
||||||
.await
|
.await
|
||||||
.optional()
|
.optional()
|
||||||
@@ -63,9 +63,9 @@ async fn register_key(db: &DatabasePool, pubkey: &authn::PublicKey) -> Result<i3
|
|||||||
Error::internal("Database unavailable")
|
Error::internal("Database unavailable")
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let id: i32 = diesel::insert_into(useragent_client::table)
|
let id: i32 = diesel::insert_into(operator_client::table)
|
||||||
.values((useragent_client::public_key.eq(pubkey_bytes),))
|
.values((operator_client::public_key.eq(pubkey_bytes),))
|
||||||
.returning(useragent_client::id)
|
.returning(operator_client::id)
|
||||||
.get_result(&mut conn)
|
.get_result(&mut conn)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@@ -77,12 +77,12 @@ async fn register_key(db: &DatabasePool, pubkey: &authn::PublicKey) -> Result<i3
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct AuthContext<'a, T: ?Sized> {
|
pub(super) struct AuthContext<'a, T: ?Sized> {
|
||||||
pub(super) conn: &'a mut UserAgentConnection,
|
pub(super) conn: &'a mut OperatorConnection,
|
||||||
pub(super) transport: &'a mut T,
|
pub(super) transport: &'a mut T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T: ?Sized> AuthContext<'a, T> {
|
impl<'a, T: ?Sized> AuthContext<'a, T> {
|
||||||
pub(super) const fn new(conn: &'a mut UserAgentConnection, transport: &'a mut T) -> Self {
|
pub(super) const fn new(conn: &'a mut OperatorConnection, transport: &'a mut T) -> Self {
|
||||||
Self { conn, transport }
|
Self { conn, transport }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,7 +143,7 @@ where
|
|||||||
Error::InvalidChallengeSolution
|
Error::InvalidChallengeSolution
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let valid = pubkey.verify(challenge, USERAGENT_CONTEXT, &signature);
|
let valid = pubkey.verify(challenge, OPERATOR_CONTEXT, &signature);
|
||||||
|
|
||||||
if !valid {
|
if !valid {
|
||||||
self.transport
|
self.transport
|
||||||
@@ -17,7 +17,7 @@ use tokio::sync::oneshot;
|
|||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
|
|
||||||
pub use auth::authenticate;
|
pub use auth::authenticate;
|
||||||
pub use session::UserAgentSession;
|
pub use session::OperatorSession;
|
||||||
|
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
@@ -30,10 +30,10 @@ pub struct Credentials {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Integrable for Credentials {
|
impl Integrable for Credentials {
|
||||||
const KIND: &'static str = "useragent_credentials";
|
const KIND: &'static str = "operator_credentials";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Messages, sent by user agent to connection client without having a request
|
// Messages, sent by operator to connection client without having a request
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum OutOfBand {
|
pub enum OutOfBand {
|
||||||
ClientConnectionRequest { profile: ClientProfile },
|
ClientConnectionRequest { profile: ClientProfile },
|
||||||
@@ -41,12 +41,12 @@ pub enum OutOfBand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct UserAgentConnection {
|
pub struct OperatorConnection {
|
||||||
pub(crate) db: DatabasePool,
|
pub(crate) db: DatabasePool,
|
||||||
pub(crate) actors: GlobalActors,
|
pub(crate) actors: GlobalActors,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserAgentConnection {
|
impl OperatorConnection {
|
||||||
pub const fn new(db: DatabasePool, actors: GlobalActors) -> Self {
|
pub const fn new(db: DatabasePool, actors: GlobalActors) -> Self {
|
||||||
Self { db, actors }
|
Self { db, actors }
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ async fn should_run_gate(vault: &ActorRef<Vault>) -> Result<bool, Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn run_vault_gate<T>(
|
async fn run_vault_gate<T>(
|
||||||
props: &UserAgentConnection,
|
props: &OperatorConnection,
|
||||||
transport: &mut T,
|
transport: &mut T,
|
||||||
auth_creds: Credentials,
|
auth_creds: Credentials,
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error>
|
||||||
@@ -160,10 +160,10 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn start<T>(
|
pub async fn start<T>(
|
||||||
props: &mut UserAgentConnection,
|
props: &mut OperatorConnection,
|
||||||
mut transport: T,
|
mut transport: T,
|
||||||
oob_sender: Box<dyn Sender<OutOfBand>>,
|
oob_sender: Box<dyn Sender<OutOfBand>>,
|
||||||
) -> Result<ActorRef<UserAgentSession>, Error>
|
) -> Result<ActorRef<OperatorSession>, Error>
|
||||||
where
|
where
|
||||||
T: Bi<auth::Inbound, Result<auth::Outbound, auth::Error>> + Send,
|
T: Bi<auth::Inbound, Result<auth::Outbound, auth::Error>> + Send,
|
||||||
T: Bi<vault_gate::Inbound, Result<vault_gate::Outbound, vault_gate::Error>> + Send,
|
T: Bi<vault_gate::Inbound, Result<vault_gate::Outbound, vault_gate::Error>> + Send,
|
||||||
@@ -178,7 +178,7 @@ where
|
|||||||
// checking the integrity
|
// checking the integrity
|
||||||
verify_integrity(&props.db, &props.actors.vault, &creds).await?;
|
verify_integrity(&props.db, &props.actors.vault, &creds).await?;
|
||||||
|
|
||||||
Ok(UserAgentSession::spawn(UserAgentSession::new(
|
Ok(OperatorSession::spawn(OperatorSession::new(
|
||||||
props.clone(),
|
props.clone(),
|
||||||
oob_sender,
|
oob_sender,
|
||||||
)))
|
)))
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use super::{Error, UserAgentSession};
|
use super::{Error, OperatorSession};
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::evm::{
|
actors::evm::{
|
||||||
ClientSignTransaction, Generate, ListWallets, SignTransactionError as EvmSignError,
|
ClientSignTransaction, Generate, ListWallets, SignTransactionError as EvmSignError,
|
||||||
UseragentCreateGrant, UseragentListGrants,
|
OperatorCreateGrant, OperatorListGrants,
|
||||||
},
|
},
|
||||||
actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer,
|
actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer,
|
||||||
actors::vault::VaultState,
|
actors::vault::VaultState,
|
||||||
@@ -36,7 +36,7 @@ pub enum GrantMutationError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[messages]
|
#[messages]
|
||||||
impl UserAgentSession {
|
impl OperatorSession {
|
||||||
#[message]
|
#[message]
|
||||||
pub(crate) async fn handle_query_vault_state(&mut self) -> Result<VaultState, Error> {
|
pub(crate) async fn handle_query_vault_state(&mut self) -> Result<VaultState, Error> {
|
||||||
use crate::actors::vault::GetState;
|
use crate::actors::vault::GetState;
|
||||||
@@ -44,7 +44,7 @@ impl UserAgentSession {
|
|||||||
let vault_state = match self.props.actors.vault.ask(GetState {}).await {
|
let vault_state = match self.props.actors.vault.ask(GetState {}).await {
|
||||||
Ok(state) => state,
|
Ok(state) => state,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, actor = "useragent", "vault.query.failed");
|
error!(?err, actor = "operator", "vault.query.failed");
|
||||||
return Err(Error::internal("Vault is in broken state"));
|
return Err(Error::internal("Vault is in broken state"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -54,7 +54,7 @@ impl UserAgentSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[messages]
|
#[messages]
|
||||||
impl UserAgentSession {
|
impl OperatorSession {
|
||||||
#[message]
|
#[message]
|
||||||
pub(crate) async fn handle_evm_wallet_create(&mut self) -> Result<(i32, Address), Error> {
|
pub(crate) async fn handle_evm_wallet_create(&mut self) -> Result<(i32, Address), Error> {
|
||||||
match self.props.actors.evm.ask(Generate {}).await {
|
match self.props.actors.evm.ask(Generate {}).await {
|
||||||
@@ -82,10 +82,10 @@ impl UserAgentSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[messages]
|
#[messages]
|
||||||
impl UserAgentSession {
|
impl OperatorSession {
|
||||||
#[message]
|
#[message]
|
||||||
pub(crate) async fn handle_grant_list(&mut self) -> Result<Vec<Grant<SpecificGrant>>, Error> {
|
pub(crate) async fn handle_grant_list(&mut self) -> Result<Vec<Grant<SpecificGrant>>, Error> {
|
||||||
match self.props.actors.evm.ask(UseragentListGrants {}).await {
|
match self.props.actors.evm.ask(OperatorListGrants {}).await {
|
||||||
Ok(grants) => Ok(grants),
|
Ok(grants) => Ok(grants),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, "EVM grant list failed");
|
error!(?err, "EVM grant list failed");
|
||||||
@@ -104,7 +104,7 @@ impl UserAgentSession {
|
|||||||
.props
|
.props
|
||||||
.actors
|
.actors
|
||||||
.evm
|
.evm
|
||||||
.ask(UseragentCreateGrant { basic, grant })
|
.ask(OperatorCreateGrant { basic, grant })
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(grant_id) => Ok(grant_id),
|
Ok(grant_id) => Ok(grant_id),
|
||||||
@@ -121,7 +121,7 @@ impl UserAgentSession {
|
|||||||
// .props
|
// .props
|
||||||
// .actors
|
// .actors
|
||||||
// .evm
|
// .evm
|
||||||
// .ask(UseragentDeleteGrant { grant_id })
|
// .ask(OperatorDeleteGrant { grant_id })
|
||||||
// .await
|
// .await
|
||||||
// {
|
// {
|
||||||
// Ok(()) => Ok(()),
|
// Ok(()) => Ok(()),
|
||||||
@@ -157,7 +157,7 @@ impl UserAgentSession {
|
|||||||
Err(SignTransactionError::Vet(vet_error))
|
Err(SignTransactionError::Vet(vet_error))
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, "EVM sign transaction failed in user-agent session");
|
error!(?err, "EVM sign transaction failed in operator session");
|
||||||
Err(SignTransactionError::Internal)
|
Err(SignTransactionError::Internal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -226,7 +226,7 @@ impl UserAgentSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[messages]
|
#[messages]
|
||||||
impl UserAgentSession {
|
impl OperatorSession {
|
||||||
#[message(ctx)]
|
#[message(ctx)]
|
||||||
pub(crate) async fn handle_new_client_approve(
|
pub(crate) async fn handle_new_client_approve(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use super::{OutOfBand, UserAgentConnection};
|
use super::{OutOfBand, OperatorConnection};
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::{
|
actors::{
|
||||||
flow_coordinator::client_connect_approval::ClientApprovalController,
|
flow_coordinator::client_connect_approval::ClientApprovalController,
|
||||||
useragent_registry::ConnectUseragent,
|
operator_registry::ConnectOperator,
|
||||||
},
|
},
|
||||||
peers::client::ClientProfile,
|
peers::client::ClientProfile,
|
||||||
};
|
};
|
||||||
@@ -49,8 +49,8 @@ pub struct PendingClientApproval {
|
|||||||
controller: ActorRef<ClientApprovalController>,
|
controller: ActorRef<ClientApprovalController>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct UserAgentSession {
|
pub struct OperatorSession {
|
||||||
props: UserAgentConnection,
|
props: OperatorConnection,
|
||||||
sender: Box<dyn Sender<OutOfBand>>,
|
sender: Box<dyn Sender<OutOfBand>>,
|
||||||
|
|
||||||
pending_client_approvals: HashMap<Vec<u8>, PendingClientApproval>,
|
pending_client_approvals: HashMap<Vec<u8>, PendingClientApproval>,
|
||||||
@@ -58,8 +58,8 @@ pub struct UserAgentSession {
|
|||||||
|
|
||||||
pub mod handlers;
|
pub mod handlers;
|
||||||
|
|
||||||
impl UserAgentSession {
|
impl OperatorSession {
|
||||||
pub(crate) fn new(props: UserAgentConnection, sender: Box<dyn Sender<OutOfBand>>) -> Self {
|
pub(crate) fn new(props: OperatorConnection, sender: Box<dyn Sender<OutOfBand>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
props,
|
props,
|
||||||
sender,
|
sender,
|
||||||
@@ -69,7 +69,7 @@ impl UserAgentSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[messages]
|
#[messages]
|
||||||
impl UserAgentSession {
|
impl OperatorSession {
|
||||||
#[message]
|
#[message]
|
||||||
pub async fn begin_new_client_approval(
|
pub async fn begin_new_client_approval(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -85,7 +85,7 @@ impl UserAgentSession {
|
|||||||
{
|
{
|
||||||
error!(
|
error!(
|
||||||
?e,
|
?e,
|
||||||
actor = "user_agent",
|
actor = "operator",
|
||||||
event = "failed to announce new client connection"
|
event = "failed to announce new client connection"
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
@@ -101,7 +101,7 @@ impl UserAgentSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for UserAgentSession {
|
impl Actor for OperatorSession {
|
||||||
type Args = Self;
|
type Args = Self;
|
||||||
|
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
@@ -109,17 +109,17 @@ impl Actor for UserAgentSession {
|
|||||||
async fn on_start(args: Self::Args, this: ActorRef<Self>) -> Result<Self, Self::Error> {
|
async fn on_start(args: Self::Args, this: ActorRef<Self>) -> Result<Self, Self::Error> {
|
||||||
args.props
|
args.props
|
||||||
.actors
|
.actors
|
||||||
.useragent_registry
|
.operator_registry
|
||||||
.ask(ConnectUseragent {
|
.ask(ConnectOperator {
|
||||||
actor: this.clone(),
|
actor: this.clone(),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
error!(
|
error!(
|
||||||
?err,
|
?err,
|
||||||
"Failed to register user agent connection with user agent registry"
|
"Failed to register operator connection with operator registry"
|
||||||
);
|
);
|
||||||
Error::internal("Failed to register user agent connection with user agent registry")
|
Error::internal("Failed to register operator connection with operator registry")
|
||||||
})?;
|
})?;
|
||||||
Ok(args)
|
Ok(args)
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ impl Actor for UserAgentSession {
|
|||||||
{
|
{
|
||||||
error!(
|
error!(
|
||||||
?e,
|
?e,
|
||||||
actor = "user_agent",
|
actor = "operator",
|
||||||
event = "failed to announce client connection cancellation"
|
event = "failed to announce client connection cancellation"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -79,22 +79,22 @@ fn sign_client_challenge(key: &SigningKey<MlDsa87>, challenge: &AuthChallenge) -
|
|||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn insert_bootstrap_sentinel_useragent(db: &db::DatabasePool) {
|
async fn insert_bootstrap_sentinel_operator(db: &db::DatabasePool) {
|
||||||
let mut conn = db.get().await.unwrap();
|
let mut conn = db.get().await.unwrap();
|
||||||
let sentinel_key = verifying_key(&MlDsa87::key_gen(&mut rand::rng()))
|
let sentinel_key = verifying_key(&MlDsa87::key_gen(&mut rand::rng()))
|
||||||
.encode()
|
.encode()
|
||||||
.0
|
.0
|
||||||
.to_vec();
|
.to_vec();
|
||||||
|
|
||||||
insert_into(schema::useragent_client::table)
|
insert_into(schema::operator_client::table)
|
||||||
.values((schema::useragent_client::public_key.eq(sentinel_key),))
|
.values((schema::operator_client::public_key.eq(sentinel_key),))
|
||||||
.execute(&mut conn)
|
.execute(&mut conn)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn spawn_test_actors(db: &db::DatabasePool) -> GlobalActors {
|
async fn spawn_test_actors(db: &db::DatabasePool) -> GlobalActors {
|
||||||
insert_bootstrap_sentinel_useragent(db).await;
|
insert_bootstrap_sentinel_operator(db).await;
|
||||||
|
|
||||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||||
actors
|
actors
|
||||||
|
|||||||
6
server/crates/arbiter-server/tests/operator.rs
Normal file
6
server/crates/arbiter-server/tests/operator.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
mod common;
|
||||||
|
|
||||||
|
#[path = "operator/auth.rs"]
|
||||||
|
mod auth;
|
||||||
|
#[path = "operator/unseal.rs"]
|
||||||
|
mod unseal;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::common::ChannelTransport;
|
use super::common::ChannelTransport;
|
||||||
use arbiter_crypto::{
|
use arbiter_crypto::{
|
||||||
authn::{self, AuthChallenge, USERAGENT_CONTEXT},
|
authn::{self, AuthChallenge, OPERATOR_CONTEXT},
|
||||||
safecell::{SafeCell, SafeCellHandle as _},
|
safecell::{SafeCell, SafeCellHandle as _},
|
||||||
};
|
};
|
||||||
use arbiter_proto::transport::{Error as TransportError, Receiver, Sender};
|
use arbiter_proto::transport::{Error as TransportError, Receiver, Sender};
|
||||||
@@ -8,7 +8,7 @@ use arbiter_server::{
|
|||||||
actors::{GlobalActors, bootstrap::GetToken, vault::Bootstrap},
|
actors::{GlobalActors, bootstrap::GetToken, vault::Bootstrap},
|
||||||
crypto::integrity,
|
crypto::integrity,
|
||||||
db::{self, schema},
|
db::{self, schema},
|
||||||
peers::user_agent::{self, Credentials, UserAgentConnection, auth, vault_gate},
|
peers::operator::{self, Credentials, OperatorConnection, auth, vault_gate},
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@@ -21,13 +21,13 @@ fn verifying_key(key: &SigningKey<MlDsa87>) -> VerifyingKey<MlDsa87> {
|
|||||||
<SigningKey<MlDsa87> as Keypair>::verifying_key(key)
|
<SigningKey<MlDsa87> as Keypair>::verifying_key(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sign_useragent_challenge(
|
fn sign_operator_challenge(
|
||||||
key: &SigningKey<MlDsa87>,
|
key: &SigningKey<MlDsa87>,
|
||||||
challenge: &AuthChallenge,
|
challenge: &AuthChallenge,
|
||||||
) -> authn::Signature {
|
) -> authn::Signature {
|
||||||
let challenge = challenge.format();
|
let challenge = challenge.format();
|
||||||
key.signing_key()
|
key.signing_key()
|
||||||
.sign_deterministic(&challenge, USERAGENT_CONTEXT)
|
.sign_deterministic(&challenge, OPERATOR_CONTEXT)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
@@ -41,8 +41,8 @@ fn tamper_challenge(challenge: &AuthChallenge) -> AuthChallenge {
|
|||||||
struct NullOobSender;
|
struct NullOobSender;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl Sender<user_agent::OutOfBand> for NullOobSender {
|
impl Sender<operator::OutOfBand> for NullOobSender {
|
||||||
async fn send(&mut self, _item: user_agent::OutOfBand) -> Result<(), TransportError> {
|
async fn send(&mut self, _item: operator::OutOfBand) -> Result<(), TransportError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,7 +166,7 @@ pub async fn bootstrap_token_auth() {
|
|||||||
let (mut server_transport, mut test_transport) = ChannelTransport::new();
|
let (mut server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
let db_for_task = db.clone();
|
let db_for_task = db.clone();
|
||||||
let task = tokio::spawn(async move {
|
let task = tokio::spawn(async move {
|
||||||
let mut props = UserAgentConnection::new(db_for_task, actors);
|
let mut props = OperatorConnection::new(db_for_task, actors);
|
||||||
auth::authenticate(&mut props, &mut server_transport).await
|
auth::authenticate(&mut props, &mut server_transport).await
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ pub async fn bootstrap_token_auth() {
|
|||||||
other => panic!("Expected AuthChallenge, got {other:?}"),
|
other => panic!("Expected AuthChallenge, got {other:?}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let signature = sign_useragent_challenge(&new_key, &challenge);
|
let signature = sign_operator_challenge(&new_key, &challenge);
|
||||||
|
|
||||||
test_transport
|
test_transport
|
||||||
.send(auth::Inbound::AuthChallengeSolution {
|
.send(auth::Inbound::AuthChallengeSolution {
|
||||||
@@ -206,8 +206,8 @@ pub async fn bootstrap_token_auth() {
|
|||||||
task.await.unwrap().unwrap();
|
task.await.unwrap().unwrap();
|
||||||
|
|
||||||
let mut conn = db.get().await.unwrap();
|
let mut conn = db.get().await.unwrap();
|
||||||
let stored_pubkey: Vec<u8> = schema::useragent_client::table
|
let stored_pubkey: Vec<u8> = schema::operator_client::table
|
||||||
.select(schema::useragent_client::public_key)
|
.select(schema::operator_client::public_key)
|
||||||
.first::<Vec<u8>>(&mut conn)
|
.first::<Vec<u8>>(&mut conn)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -223,7 +223,7 @@ pub async fn bootstrap_invalid_token_auth() {
|
|||||||
let (mut server_transport, mut test_transport) = ChannelTransport::new();
|
let (mut server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
let db_for_task = db.clone();
|
let db_for_task = db.clone();
|
||||||
let task = tokio::spawn(async move {
|
let task = tokio::spawn(async move {
|
||||||
let mut props = UserAgentConnection::new(db_for_task, actors);
|
let mut props = OperatorConnection::new(db_for_task, actors);
|
||||||
auth::authenticate(&mut props, &mut server_transport).await
|
auth::authenticate(&mut props, &mut server_transport).await
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -245,7 +245,7 @@ pub async fn bootstrap_invalid_token_auth() {
|
|||||||
other => panic!("Expected AuthChallenge, got {other:?}"),
|
other => panic!("Expected AuthChallenge, got {other:?}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let signature = sign_useragent_challenge(&new_key, &challenge);
|
let signature = sign_operator_challenge(&new_key, &challenge);
|
||||||
test_transport
|
test_transport
|
||||||
.send(auth::Inbound::AuthChallengeSolution {
|
.send(auth::Inbound::AuthChallengeSolution {
|
||||||
signature: signature.to_bytes(),
|
signature: signature.to_bytes(),
|
||||||
@@ -259,7 +259,7 @@ pub async fn bootstrap_invalid_token_auth() {
|
|||||||
));
|
));
|
||||||
|
|
||||||
let mut conn = db.get().await.unwrap();
|
let mut conn = db.get().await.unwrap();
|
||||||
let count: i64 = schema::useragent_client::table
|
let count: i64 = schema::operator_client::table
|
||||||
.count()
|
.count()
|
||||||
.get_result::<i64>(&mut conn)
|
.get_result::<i64>(&mut conn)
|
||||||
.await
|
.await
|
||||||
@@ -285,9 +285,9 @@ pub async fn challenge_auth() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let mut conn = db.get().await.unwrap();
|
let mut conn = db.get().await.unwrap();
|
||||||
let id: i32 = insert_into(schema::useragent_client::table)
|
let id: i32 = insert_into(schema::operator_client::table)
|
||||||
.values((schema::useragent_client::public_key.eq(pubkey_bytes.clone()),))
|
.values((schema::operator_client::public_key.eq(pubkey_bytes.clone()),))
|
||||||
.returning(schema::useragent_client::id)
|
.returning(schema::operator_client::id)
|
||||||
.get_result(&mut conn)
|
.get_result(&mut conn)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -307,7 +307,7 @@ pub async fn challenge_auth() {
|
|||||||
let (mut server_transport, mut test_transport) = ChannelTransport::new();
|
let (mut server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
let db_for_task = db.clone();
|
let db_for_task = db.clone();
|
||||||
let task = tokio::spawn(async move {
|
let task = tokio::spawn(async move {
|
||||||
let mut props = UserAgentConnection::new(db_for_task, actors);
|
let mut props = OperatorConnection::new(db_for_task, actors);
|
||||||
auth::authenticate(&mut props, &mut server_transport).await
|
auth::authenticate(&mut props, &mut server_transport).await
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -331,7 +331,7 @@ pub async fn challenge_auth() {
|
|||||||
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let signature = sign_useragent_challenge(&new_key, &challenge);
|
let signature = sign_operator_challenge(&new_key, &challenge);
|
||||||
|
|
||||||
test_transport
|
test_transport
|
||||||
.send(auth::Inbound::AuthChallengeSolution {
|
.send(auth::Inbound::AuthChallengeSolution {
|
||||||
@@ -371,8 +371,8 @@ pub async fn challenge_auth_rejects_integrity_tag_mismatch_when_unsealed() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let mut conn = db.get().await.unwrap();
|
let mut conn = db.get().await.unwrap();
|
||||||
insert_into(schema::useragent_client::table)
|
insert_into(schema::operator_client::table)
|
||||||
.values((schema::useragent_client::public_key.eq(pubkey_bytes.clone()),))
|
.values((schema::operator_client::public_key.eq(pubkey_bytes.clone()),))
|
||||||
.execute(&mut conn)
|
.execute(&mut conn)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -381,8 +381,8 @@ pub async fn challenge_auth_rejects_integrity_tag_mismatch_when_unsealed() {
|
|||||||
let (server_transport, mut test_transport) = start_transport_pair();
|
let (server_transport, mut test_transport) = start_transport_pair();
|
||||||
let db_for_task = db.clone();
|
let db_for_task = db.clone();
|
||||||
let task = tokio::spawn(async move {
|
let task = tokio::spawn(async move {
|
||||||
let mut props = UserAgentConnection::new(db_for_task, actors);
|
let mut props = OperatorConnection::new(db_for_task, actors);
|
||||||
user_agent::start(&mut props, server_transport, Box::new(NullOobSender)).await
|
operator::start(&mut props, server_transport, Box::new(NullOobSender)).await
|
||||||
});
|
});
|
||||||
|
|
||||||
test_transport
|
test_transport
|
||||||
@@ -405,7 +405,7 @@ pub async fn challenge_auth_rejects_integrity_tag_mismatch_when_unsealed() {
|
|||||||
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let signature = sign_useragent_challenge(&new_key, &challenge);
|
let signature = sign_operator_challenge(&new_key, &challenge);
|
||||||
|
|
||||||
test_transport
|
test_transport
|
||||||
.send(auth::Inbound::AuthChallengeSolution {
|
.send(auth::Inbound::AuthChallengeSolution {
|
||||||
@@ -422,7 +422,7 @@ pub async fn challenge_auth_rejects_integrity_tag_mismatch_when_unsealed() {
|
|||||||
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
task.await.unwrap(),
|
task.await.unwrap(),
|
||||||
Err(user_agent::Error::Internal(_))
|
Err(operator::Error::Internal(_))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,9 +444,9 @@ pub async fn challenge_auth_rejects_invalid_signature() {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let mut conn = db.get().await.unwrap();
|
let mut conn = db.get().await.unwrap();
|
||||||
let id: i32 = insert_into(schema::useragent_client::table)
|
let id: i32 = insert_into(schema::operator_client::table)
|
||||||
.values((schema::useragent_client::public_key.eq(pubkey_bytes.clone()),))
|
.values((schema::operator_client::public_key.eq(pubkey_bytes.clone()),))
|
||||||
.returning(schema::useragent_client::id)
|
.returning(schema::operator_client::id)
|
||||||
.get_result(&mut conn)
|
.get_result(&mut conn)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -466,7 +466,7 @@ pub async fn challenge_auth_rejects_invalid_signature() {
|
|||||||
let (mut server_transport, mut test_transport) = ChannelTransport::new();
|
let (mut server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
let db_for_task = db.clone();
|
let db_for_task = db.clone();
|
||||||
let task = tokio::spawn(async move {
|
let task = tokio::spawn(async move {
|
||||||
let mut props = UserAgentConnection::new(db_for_task, actors);
|
let mut props = OperatorConnection::new(db_for_task, actors);
|
||||||
auth::authenticate(&mut props, &mut server_transport).await
|
auth::authenticate(&mut props, &mut server_transport).await
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -490,7 +490,7 @@ pub async fn challenge_auth_rejects_invalid_signature() {
|
|||||||
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let signature = sign_useragent_challenge(&new_key, &tamper_challenge(&challenge));
|
let signature = sign_operator_challenge(&new_key, &tamper_challenge(&challenge));
|
||||||
|
|
||||||
test_transport
|
test_transport
|
||||||
.send(auth::Inbound::AuthChallengeSolution {
|
.send(auth::Inbound::AuthChallengeSolution {
|
||||||
@@ -8,7 +8,7 @@ use arbiter_server::{
|
|||||||
vault::{Bootstrap, Seal},
|
vault::{Bootstrap, Seal},
|
||||||
},
|
},
|
||||||
db,
|
db,
|
||||||
peers::user_agent::{
|
peers::operator::{
|
||||||
Credentials,
|
Credentials,
|
||||||
vault_gate::{
|
vault_gate::{
|
||||||
Error as VaultGateError, HandleHandshake, HandleUnsealEncryptedKey, VaultGate,
|
Error as VaultGateError, HandleHandshake, HandleUnsealEncryptedKey, VaultGate,
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
mod common;
|
|
||||||
|
|
||||||
#[path = "user_agent/auth.rs"]
|
|
||||||
mod auth;
|
|
||||||
#[path = "user_agent/unseal.rs"]
|
|
||||||
mod unseal;
|
|
||||||
Reference in New Issue
Block a user