Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/workflows/rust-api-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Compile and test the Rust API crate.
#
# Why this exists: before it, CI never built `rust/api` at all. `deploy.yml`
# runs `cargo make build-site` (wasm + hugo only), and `integration-test.yml`
# is `workflow_dispatch:` only. So a compile error in `rust/api` -- or any
# failing unit test in it -- would merge green and only surface when someone
# ran the deploy script on vega.
#
# That gap let a real bug through: adding `reqwest` with `rustls-tls` enabled a
# second rustls provider feature alongside axum-server's, which made gkapi panic
# at startup in HTTPS mode ("no process-level CryptoProvider available"). Unit
# tests could not catch it because none of them construct a TLS config, hence
# the https_startup integration test, which this job runs.
name: Rust API Tests

on:
pull_request:
paths:
- "rust/**"
- ".github/workflows/rust-api-tests.yml"
push:
branches: [main]
paths:
- "rust/**"
- ".github/workflows/rust-api-tests.yml"

concurrency:
group: rust-api-tests-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4

- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- name: Cache cargo
uses: Swatinem/rust-cache@v2
with:
workspaces: rust

- name: Formatting
working-directory: rust
run: cargo fmt -p ghostkey-api -- --check

- name: Build
working-directory: rust
run: cargo build -p ghostkey-api

# Unit tests plus the HTTPS startup smoke test. The latter spawns the real
# binary with a self-signed cert and makes a real HTTPS request, because
# the TLS provider failure mode is invisible to any in-process test.
#
# Network-dependent tests are #[ignore]d and deliberately NOT run here --
# the live Tor Project fetch would make this job flaky. Run them manually:
# cargo test --bins -- --ignored
- name: Test
working-directory: rust
run: cargo test -p ghostkey-api
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,25 @@
inviteLoading.style.display = 'none';
inviteError.style.display = 'block';
errorMessage.textContent = data.error;
if (data.retry_after_seconds) {
const hours = Math.ceil(data.retry_after_seconds / 3600);
retryMessage.textContent = `You can try again in approximately ${hours} hour(s).`;
// Retry hints can now be sub-hour: the shared Tor ceiling uses a
// 60-minute window, so values arrive anywhere in 0..3600s.
// Rounding everything up to hours told a user waiting 90 seconds
// to come back in "approximately 1 hour(s)".
// Note `!= null` rather than truthiness, so a legitimate 0 is
// rendered instead of leaving a stale message from a prior try.
if (data.retry_after_seconds != null) {
const secs = Math.max(0, Math.round(data.retry_after_seconds));
let when;
if (secs < 60) {
when = `${Math.max(1, secs)} second(s)`;
} else if (secs < 3600) {
when = `${Math.ceil(secs / 60)} minute(s)`;
} else {
when = `${Math.ceil(secs / 3600)} hour(s)`;
}
retryMessage.textContent = `You can try again in approximately ${when}.`;
} else {
retryMessage.textContent = '';
}
return;
}
Expand Down
75 changes: 74 additions & 1 deletion rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions rust/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,18 @@ serde = { version = "1.0", features = ["derive"] }
blind-rsa-signatures = "0.15.1"
ciborium = "0.2"
bs58 = "0.5"
# Tor exit-list fetch.
#
# NOTE: `rustls-tls` enables `rustls/ring`, while axum-server's `tls-rustls`
# enables `rustls/aws-lc-rs`. With BOTH provider features on, rustls 0.23's
# `CryptoProvider::from_crate_features()` returns None and TLS startup panics
# with "no process-level CryptoProvider available". main() therefore installs
# the aws-lc-rs provider explicitly before any TLS is constructed -- see
# `install_crypto_provider()` and `https_mode_starts_without_crypto_provider_panic`.
# Do NOT remove that call, and do NOT assume unit tests cover it: only an
# actual HTTPS-mode start exercises this path.
reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }
rustls = { version = "0.23", features = ["aws-lc-rs"] }

[dev-dependencies]
tempfile = "3"
2 changes: 1 addition & 1 deletion rust/api/src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use serde::de::StdError;
use ghostkey_lib::errors::GhostkeyError;
use serde::de::StdError;

#[derive(Debug)]
pub enum CertificateError {
Expand Down
13 changes: 7 additions & 6 deletions rust/api/src/invite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,18 +166,19 @@ mod tests {
let owner_vk_bytes = bs58::decode("93XNNwmRLQ6nwUwi4dDmp3kpjMb5ekMRc2e22x5TAnUY")
.into_vec()
.expect("Failed to decode owner VK");
let owner_vk = VerifyingKey::from_bytes(&owner_vk_bytes.try_into().expect("Invalid VK length"))
.expect("Invalid VK");
let owner_vk =
VerifyingKey::from_bytes(&owner_vk_bytes.try_into().expect("Invalid VK length"))
.expect("Invalid VK");

// Signing key from rooms.json for freenet-chat
let signing_key_bytes: [u8; 32] = [
1, 3, 163, 211, 4, 113, 25, 236, 171, 57, 117, 76, 11, 233, 182, 31,
111, 137, 94, 202, 149, 4, 41, 59, 145, 54, 18, 82, 243, 194, 71, 224
1, 3, 163, 211, 4, 113, 25, 236, 171, 57, 117, 76, 11, 233, 182, 31, 111, 137, 94, 202,
149, 4, 41, 59, 145, 54, 18, 82, 243, 194, 71, 224,
];
let signing_key = SigningKey::from_bytes(&signing_key_bytes);

let invite = create_invitation(&owner_vk, &signing_key)
.expect("Failed to create invitation");
let invite =
create_invitation(&owner_vk, &signing_key).expect("Failed to create invitation");

println!("\n=== Generated Invite for freenet-chat ===");
println!("{}", invite);
Expand Down
Loading
Loading