mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-10 03:30:59 +00:00
fix(auth): restore local dev-token client coverage
Propagate the local dev token through worker subprocesses, share the same authenticated local-server helper across CLI integration tests, and clean up the async token wait path so fmt, clippy, and full tests pass again after the dev-token auth rollout.
This commit is contained in:
parent
a6775a051c
commit
2bf88cfe7f
16 changed files with 148 additions and 216 deletions
|
|
@ -9,12 +9,12 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct ServerRecord {
|
||||
pub pid: u32,
|
||||
pub bind: Bind,
|
||||
pub log_path: PathBuf,
|
||||
pub pid: u32,
|
||||
pub bind: Bind,
|
||||
pub log_path: PathBuf,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub dev_token_path: Option<PathBuf>,
|
||||
pub started_at: DateTime<Utc>,
|
||||
pub started_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
|||
|
|
@ -8,10 +8,9 @@ use fabro_config::user::default_socket_path;
|
|||
use fabro_server::bind::{Bind, BindRequest};
|
||||
use fabro_server::serve;
|
||||
use fabro_server::serve::{DEFAULT_TCP_PORT, ServeArgs};
|
||||
use fabro_util::Home;
|
||||
use fabro_util::dev_token;
|
||||
use fabro_util::printer::Printer;
|
||||
use fabro_util::terminal::Styles;
|
||||
use fabro_util::{Home, dev_token};
|
||||
use tokio::process::Command as TokioCommand;
|
||||
use tokio::time;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@ use anyhow::{Context as _, Result, anyhow, bail};
|
|||
use bytes::Bytes;
|
||||
use fabro_api::types;
|
||||
use fabro_config::Storage;
|
||||
use fabro_http::header::{CONTENT_LENGTH, CONTENT_TYPE};
|
||||
use fabro_http::header::{AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE};
|
||||
use fabro_http::multipart::{Form, Part};
|
||||
use fabro_server::bind::Bind;
|
||||
use fabro_store::{EventEnvelope, RunSummary, StageId};
|
||||
use fabro_types::settings::SettingsLayer;
|
||||
use fabro_types::{RunBlobId, RunEvent, RunId};
|
||||
use fabro_util::Home;
|
||||
use fabro_util::dev_token::validate_dev_token_format;
|
||||
use fabro_workflow::artifact_snapshot::CapturedArtifactInfo;
|
||||
use futures::StreamExt;
|
||||
use serde::Serialize;
|
||||
|
|
@ -23,12 +25,9 @@ use tokio::time::sleep;
|
|||
use tokio_util::io::ReaderStream;
|
||||
|
||||
use crate::args::ServerTargetArgs;
|
||||
use crate::commands::server::start;
|
||||
use crate::commands::server::record;
|
||||
use crate::commands::server::{record, start};
|
||||
use crate::user_config::cli_http_client_builder;
|
||||
use crate::{sse, user_config};
|
||||
use fabro_util::Home;
|
||||
use fabro_util::dev_token::validate_dev_token_format;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ServerStoreClient {
|
||||
|
|
@ -123,7 +122,7 @@ async fn connect_api_client_bundle(storage_dir: &Path) -> Result<ServerStoreClie
|
|||
match bind {
|
||||
Bind::Unix(path) => connect_unix_socket_api_client_bundle(&path, Some(storage_dir)).await,
|
||||
Bind::Tcp(addr) => {
|
||||
let token = wait_for_local_dev_token(storage_dir)?;
|
||||
let token = wait_for_local_dev_token(storage_dir).await?;
|
||||
let builder = cli_http_client_builder().no_proxy();
|
||||
let http_client = apply_bearer_token_auth(builder, &token)?.build()?;
|
||||
let base_url = format!("http://{addr}");
|
||||
|
|
@ -226,14 +225,14 @@ fn load_dev_token_if_available(storage_dir: Option<&Path>) -> Option<String> {
|
|||
read_dev_token_file(&Home::from_env().dev_token_path())
|
||||
}
|
||||
|
||||
fn wait_for_local_dev_token(storage_dir: &Path) -> Result<String> {
|
||||
async fn wait_for_local_dev_token(storage_dir: &Path) -> Result<String> {
|
||||
let deadline = std::time::Instant::now() + Duration::from_secs(5);
|
||||
|
||||
while std::time::Instant::now() < deadline {
|
||||
if let Some(token) = load_dev_token_if_available(Some(storage_dir)) {
|
||||
return Ok(token);
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(50));
|
||||
sleep(Duration::from_millis(50)).await;
|
||||
}
|
||||
|
||||
bail!(
|
||||
|
|
@ -248,7 +247,7 @@ fn apply_bearer_token_auth(
|
|||
) -> Result<fabro_http::HttpClientBuilder> {
|
||||
let mut headers = fabro_http::HeaderMap::new();
|
||||
headers.insert(
|
||||
fabro_http::header::AUTHORIZATION,
|
||||
AUTHORIZATION,
|
||||
fabro_http::HeaderValue::from_str(&format!("Bearer {token}"))
|
||||
.context("invalid dev token header value")?,
|
||||
);
|
||||
|
|
@ -287,10 +286,13 @@ async fn try_connect_unix_socket_api_client_bundle(
|
|||
check_server_ready(&probe_client).await?;
|
||||
|
||||
let http_client = if let Some(storage_dir) = storage_dir {
|
||||
let token = wait_for_local_dev_token(storage_dir)?;
|
||||
apply_bearer_token_auth(cli_http_client_builder().unix_socket(path).no_proxy(), &token)?
|
||||
.build()
|
||||
.context("Failed to build Unix-socket HTTP client for fabro server")?
|
||||
let token = wait_for_local_dev_token(storage_dir).await?;
|
||||
apply_bearer_token_auth(
|
||||
cli_http_client_builder().unix_socket(path).no_proxy(),
|
||||
&token,
|
||||
)?
|
||||
.build()
|
||||
.context("Failed to build Unix-socket HTTP client for fabro server")?
|
||||
} else {
|
||||
apply_dev_token_auth(cli_http_client_builder().unix_socket(path).no_proxy(), None)?
|
||||
.build()
|
||||
|
|
@ -311,10 +313,13 @@ async fn connect_unix_socket_api_client_bundle(
|
|||
wait_for_server_ready(&probe_client).await?;
|
||||
|
||||
let http_client = if let Some(storage_dir) = storage_dir {
|
||||
let token = wait_for_local_dev_token(storage_dir)?;
|
||||
apply_bearer_token_auth(cli_http_client_builder().unix_socket(path).no_proxy(), &token)?
|
||||
.build()
|
||||
.context("Failed to build Unix-socket HTTP client for fabro server")?
|
||||
let token = wait_for_local_dev_token(storage_dir).await?;
|
||||
apply_bearer_token_auth(
|
||||
cli_http_client_builder().unix_socket(path).no_proxy(),
|
||||
&token,
|
||||
)?
|
||||
.build()
|
||||
.context("Failed to build Unix-socket HTTP client for fabro server")?
|
||||
} else {
|
||||
apply_dev_token_auth(cli_http_client_builder().unix_socket(path).no_proxy(), None)?
|
||||
.build()
|
||||
|
|
@ -1023,9 +1028,7 @@ mod tests {
|
|||
|
||||
assert_eq!(
|
||||
token.as_deref(),
|
||||
Some(
|
||||
"fabro_dev_cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"
|
||||
)
|
||||
Some("fabro_dev_cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd")
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1060,11 +1063,11 @@ mod tests {
|
|||
.server_state()
|
||||
.record_path();
|
||||
record::write_server_record(&record_path, &record::ServerRecord {
|
||||
pid: std::process::id(),
|
||||
bind: fabro_server::bind::Bind::Unix(temp_home.path().join("fabro.sock")),
|
||||
log_path: storage.path().join("server.log"),
|
||||
pid: std::process::id(),
|
||||
bind: fabro_server::bind::Bind::Unix(temp_home.path().join("fabro.sock")),
|
||||
log_path: storage.path().join("server.log"),
|
||||
dev_token_path: Some(token_path),
|
||||
started_at: chrono::Utc::now(),
|
||||
started_at: chrono::Utc::now(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use std::io::{BufRead, BufReader, Read};
|
||||
use std::path::Path;
|
||||
use std::process::{Output, Stdio};
|
||||
use std::sync::mpsc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
|
@ -8,34 +7,12 @@ use fabro_test::{apply_filters, fabro_snapshot, test_context};
|
|||
use serde_json::Value;
|
||||
|
||||
use super::support::{
|
||||
output_stdout, resolve_run, server_target, wait_for_status, write_gated_workflow,
|
||||
output_stdout, resolve_run, server_endpoint, wait_for_status, write_gated_workflow,
|
||||
};
|
||||
use crate::support::{example_fixture, fabro_json_snapshot, run_output_filters, unique_run_id};
|
||||
|
||||
const SHARED_DAEMON_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
fn server_endpoint(storage_dir: &Path) -> (fabro_http::HttpClient, String) {
|
||||
let target = server_target(storage_dir);
|
||||
if target.starts_with('/') {
|
||||
(
|
||||
fabro_http::HttpClientBuilder::new()
|
||||
.unix_socket(target)
|
||||
.no_proxy()
|
||||
.build()
|
||||
.expect("test Unix-socket HTTP client should build"),
|
||||
"http://fabro".to_string(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
fabro_http::HttpClientBuilder::new()
|
||||
.no_proxy()
|
||||
.build()
|
||||
.expect("test TCP HTTP client should build"),
|
||||
target,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async fn wait_for_server_question(
|
||||
client: &fabro_http::HttpClient,
|
||||
base_url: &str,
|
||||
|
|
@ -465,6 +442,11 @@ fn attach_json_errors_without_prompting_for_human_input() {
|
|||
fabro_json_snapshot!(context, &progress, @r#"
|
||||
[
|
||||
{
|
||||
"actor": {
|
||||
"display": "dev",
|
||||
"id": "dev",
|
||||
"kind": "user"
|
||||
},
|
||||
"event": "run.created",
|
||||
"id": "[EVENT_ID]",
|
||||
"properties": {
|
||||
|
|
@ -580,7 +562,8 @@ fn attach_json_errors_without_prompting_for_human_input() {
|
|||
"version": "0.176.2"
|
||||
},
|
||||
"subject": {
|
||||
"auth_method": "disabled"
|
||||
"auth_method": "dev_token",
|
||||
"login": "dev"
|
||||
}
|
||||
},
|
||||
"run_dir": "[RUN_DIR]",
|
||||
|
|
@ -847,7 +830,8 @@ fn attach_json_errors_without_prompting_for_human_input() {
|
|||
tokio::runtime::Runtime::new()
|
||||
.expect("test runtime should build")
|
||||
.block_on(async {
|
||||
let (client, base_url) = server_endpoint(&context.storage_dir);
|
||||
let (client, base_url) =
|
||||
server_endpoint(&context.storage_dir).expect("server endpoint should exist");
|
||||
let question = wait_for_server_question(&client, &base_url, &run_id).await;
|
||||
let question_id = question["id"]
|
||||
.as_str()
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ fn inspect_created_run_shows_run_record_without_start_or_conclusion() {
|
|||
let run = setup_created_fast_dry_run(&context);
|
||||
let output = run_success(&context, &["inspect", &run.run_id]);
|
||||
|
||||
assert_snapshot!(serde_json::to_string_pretty(&compact_inspect(&output)).unwrap(), @r###"
|
||||
assert_snapshot!(serde_json::to_string_pretty(&compact_inspect(&output)).unwrap(), @r#"
|
||||
[
|
||||
{
|
||||
"run_id": "[ULID]",
|
||||
|
|
@ -55,7 +55,7 @@ fn inspect_created_run_shows_run_record_without_start_or_conclusion() {
|
|||
"server_version": "[VERSION]",
|
||||
"client_name": "fabro-cli",
|
||||
"client_version": "[VERSION]",
|
||||
"subject_auth_method": "disabled"
|
||||
"subject_auth_method": "dev_token"
|
||||
}
|
||||
},
|
||||
"start_record": null,
|
||||
|
|
@ -64,7 +64,7 @@ fn inspect_created_run_shows_run_record_without_start_or_conclusion() {
|
|||
"sandbox": null
|
||||
}
|
||||
]
|
||||
"###);
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -88,7 +88,7 @@ fn inspect_completed_run_shows_run_start_conclusion_checkpoint() {
|
|||
"server_version": "[VERSION]",
|
||||
"client_name": "fabro-cli",
|
||||
"client_version": "[VERSION]",
|
||||
"subject_auth_method": "disabled"
|
||||
"subject_auth_method": "dev_token"
|
||||
}
|
||||
},
|
||||
"start_record": {
|
||||
|
|
@ -154,7 +154,7 @@ fn inspect_completed_run_reads_store_without_disk_metadata_files() {
|
|||
"server_version": "[VERSION]",
|
||||
"client_name": "fabro-cli",
|
||||
"client_version": "[VERSION]",
|
||||
"subject_auth_method": "disabled"
|
||||
"subject_auth_method": "dev_token"
|
||||
}
|
||||
},
|
||||
"start_record": {
|
||||
|
|
@ -205,7 +205,7 @@ fn inspect_git_backed_run_exposes_checkpoint_and_sandbox_state() {
|
|||
"server_version": "[VERSION]",
|
||||
"client_name": "fabro-cli",
|
||||
"client_version": "[VERSION]",
|
||||
"subject_auth_method": "disabled"
|
||||
"subject_auth_method": "dev_token"
|
||||
}
|
||||
},
|
||||
"start_record": {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,10 @@
|
|||
#![allow(clippy::absolute_paths)]
|
||||
|
||||
use fabro_config::Storage;
|
||||
use fabro_server::bind::Bind;
|
||||
use fabro_test::{fabro_snapshot, test_context};
|
||||
use fabro_types::run_event::PullRequestCreatedProps;
|
||||
use fabro_types::{EventBody, RunEvent, RunId};
|
||||
|
||||
use super::support::setup_completed_fast_dry_run;
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct TestServerRecord {
|
||||
bind: Bind,
|
||||
}
|
||||
use super::support::{server_endpoint, setup_completed_fast_dry_run};
|
||||
|
||||
#[test]
|
||||
fn help() {
|
||||
|
|
@ -65,28 +58,8 @@ fn pr_view_reads_pull_request_from_store_without_pull_request_json() {
|
|||
|
||||
let runtime = tokio::runtime::Runtime::new().unwrap();
|
||||
runtime.block_on(async {
|
||||
let record_path = Storage::new(&context.storage_dir)
|
||||
.server_state()
|
||||
.record_path();
|
||||
let record: TestServerRecord =
|
||||
serde_json::from_str(&std::fs::read_to_string(record_path).unwrap()).unwrap();
|
||||
let (client, base_url) = match record.bind {
|
||||
Bind::Unix(path) => (
|
||||
fabro_http::HttpClientBuilder::new()
|
||||
.unix_socket(path)
|
||||
.no_proxy()
|
||||
.build()
|
||||
.unwrap(),
|
||||
"http://fabro".to_string(),
|
||||
),
|
||||
Bind::Tcp(addr) => (
|
||||
fabro_http::HttpClientBuilder::new()
|
||||
.no_proxy()
|
||||
.build()
|
||||
.unwrap(),
|
||||
format!("http://{addr}"),
|
||||
),
|
||||
};
|
||||
let (client, base_url) =
|
||||
server_endpoint(&context.storage_dir).expect("server endpoint should exist");
|
||||
let event = RunEvent {
|
||||
id: ulid::Ulid::new().to_string(),
|
||||
ts: chrono::Utc::now(),
|
||||
|
|
|
|||
|
|
@ -812,6 +812,11 @@ fn json_run_implies_auto_approve_for_human_gates() {
|
|||
fabro_json_snapshot!(context, &progress, @r#"
|
||||
[
|
||||
{
|
||||
"actor": {
|
||||
"display": "dev",
|
||||
"id": "dev",
|
||||
"kind": "user"
|
||||
},
|
||||
"event": "run.created",
|
||||
"id": "[EVENT_ID]",
|
||||
"properties": {
|
||||
|
|
@ -927,7 +932,8 @@ fn json_run_implies_auto_approve_for_human_gates() {
|
|||
"version": "0.176.2"
|
||||
},
|
||||
"subject": {
|
||||
"auth_method": "disabled"
|
||||
"auth_method": "dev_token",
|
||||
"login": "dev"
|
||||
}
|
||||
},
|
||||
"run_dir": "[RUN_DIR]",
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ use fabro_types::{EventBody, RunEvent, StatusReason};
|
|||
use httpmock::MockServer;
|
||||
|
||||
use super::support::{
|
||||
output_stderr, run_events, run_state, server_target, wait_for_event_names, wait_for_status,
|
||||
write_gated_workflow,
|
||||
local_dev_token, output_stderr, run_events, run_state, server_endpoint, server_target,
|
||||
wait_for_event_names, wait_for_status, write_gated_workflow,
|
||||
};
|
||||
use crate::support::{fabro_json_snapshot, unique_run_id};
|
||||
|
||||
|
|
@ -55,6 +55,9 @@ fn spawn_worker_process(
|
|||
.env("FABRO_HTTP_PROXY_POLICY", "disabled");
|
||||
cmd.env("FABRO_SERVER_MAX_CONCURRENT_RUNS", "64");
|
||||
cmd.env("FABRO_TEST_IN_MEMORY_STORE", "1");
|
||||
if let Some(token) = local_dev_token(&context.storage_dir) {
|
||||
cmd.env("FABRO_DEV_TOKEN", token);
|
||||
}
|
||||
cmd.args([
|
||||
"__run-worker",
|
||||
"--server",
|
||||
|
|
@ -108,26 +111,12 @@ fn child_output(mut child: Child, status: ExitStatus) -> Output {
|
|||
}
|
||||
}
|
||||
|
||||
fn server_endpoint(storage_dir: &std::path::Path) -> (fabro_http::HttpClient, String) {
|
||||
let target = server_target(storage_dir);
|
||||
if target.starts_with('/') {
|
||||
(
|
||||
fabro_http::HttpClientBuilder::new()
|
||||
.unix_socket(target)
|
||||
.no_proxy()
|
||||
.build()
|
||||
.expect("test Unix-socket HTTP client should build"),
|
||||
"http://fabro".to_string(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
fabro_http::HttpClientBuilder::new()
|
||||
.no_proxy()
|
||||
.build()
|
||||
.expect("test TCP HTTP client should build"),
|
||||
target,
|
||||
)
|
||||
fn worker_command(context: &fabro_test::TestContext) -> assert_cmd::Command {
|
||||
let mut cmd = context.command();
|
||||
if let Some(token) = local_dev_token(&context.storage_dir) {
|
||||
cmd.env("FABRO_DEV_TOKEN", token);
|
||||
}
|
||||
cmd
|
||||
}
|
||||
|
||||
async fn wait_for_server_question(
|
||||
|
|
@ -225,8 +214,7 @@ digraph CachedGraph {
|
|||
let server = server_target(&context.storage_dir);
|
||||
std::fs::remove_file(&workflow_path).unwrap();
|
||||
|
||||
let output = context
|
||||
.command()
|
||||
let output = worker_command(&context)
|
||||
.args([
|
||||
"__run-worker",
|
||||
"--server",
|
||||
|
|
@ -306,7 +294,7 @@ digraph GitHubApp {
|
|||
context.write_home(".fabro/settings.toml", "_version = 1\n");
|
||||
|
||||
let server = server_target(&context.storage_dir);
|
||||
let mut cmd = context.command();
|
||||
let mut cmd = worker_command(&context);
|
||||
cmd.env("GITHUB_APP_PRIVATE_KEY", "%%%not-base64%%%");
|
||||
cmd.args([
|
||||
"__run-worker",
|
||||
|
|
@ -356,8 +344,7 @@ digraph DetachedStoreOnly {
|
|||
|
||||
let run_dir = context.find_run_dir(&run_id);
|
||||
let server = server_target(&context.storage_dir);
|
||||
let output = context
|
||||
.command()
|
||||
let output = worker_command(&context)
|
||||
.args([
|
||||
"__run-worker",
|
||||
"--server",
|
||||
|
|
@ -441,7 +428,7 @@ digraph Test {
|
|||
}
|
||||
"#);
|
||||
|
||||
let mut cmd = context.command();
|
||||
let mut cmd = worker_command(&context);
|
||||
cmd.args([
|
||||
"__run-worker",
|
||||
"--server",
|
||||
|
|
@ -601,7 +588,8 @@ fn detached_run_answers_pending_question_without_interview_scratch_files() {
|
|||
let run_dir = context.find_run_dir(&run_id);
|
||||
let runtime = tokio::runtime::Runtime::new().expect("test runtime should build");
|
||||
let question_id = runtime.block_on(async {
|
||||
let (client, base_url) = server_endpoint(&context.storage_dir);
|
||||
let (client, base_url) =
|
||||
server_endpoint(&context.storage_dir).expect("server endpoint should exist");
|
||||
let question = wait_for_server_question(&client, &base_url, &run_id).await;
|
||||
let question_id = question["id"]
|
||||
.as_str()
|
||||
|
|
|
|||
|
|
@ -654,19 +654,50 @@ fn block_on<T>(future: impl std::future::Future<Output = T>) -> T {
|
|||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct TestServerRecord {
|
||||
bind: Bind,
|
||||
bind: Bind,
|
||||
#[serde(default)]
|
||||
dev_token_path: Option<PathBuf>,
|
||||
}
|
||||
|
||||
fn server_endpoint(storage_dir: &Path) -> Option<(fabro_http::HttpClient, String)> {
|
||||
fn read_dev_token(path: &Path) -> Option<String> {
|
||||
std::fs::read_to_string(path)
|
||||
.ok()
|
||||
.map(|token| token.trim().to_string())
|
||||
.filter(|token| !token.is_empty())
|
||||
}
|
||||
|
||||
pub(crate) fn local_dev_token(storage_dir: &Path) -> Option<String> {
|
||||
let server_state = Storage::new(storage_dir).server_state();
|
||||
|
||||
read_dev_token(&server_state.dev_token_path()).or_else(|| {
|
||||
std::fs::read_to_string(server_state.record_path())
|
||||
.ok()
|
||||
.and_then(|content| serde_json::from_str::<TestServerRecord>(&content).ok())
|
||||
.and_then(|record| record.dev_token_path)
|
||||
.as_deref()
|
||||
.and_then(read_dev_token)
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn server_endpoint(storage_dir: &Path) -> Option<(fabro_http::HttpClient, String)> {
|
||||
let record_path = Storage::new(storage_dir).server_state().record_path();
|
||||
let record = std::fs::read_to_string(record_path)
|
||||
.ok()
|
||||
.and_then(|content| serde_json::from_str::<TestServerRecord>(&content).ok())?;
|
||||
let mut headers = fabro_http::HeaderMap::new();
|
||||
if let Some(token) = local_dev_token(storage_dir) {
|
||||
headers.insert(
|
||||
fabro_http::header::AUTHORIZATION,
|
||||
fabro_http::HeaderValue::from_str(&format!("Bearer {token}"))
|
||||
.expect("local dev token should build an authorization header"),
|
||||
);
|
||||
}
|
||||
match record.bind {
|
||||
Bind::Unix(path) if path.exists() => Some((
|
||||
fabro_http::HttpClientBuilder::new()
|
||||
.unix_socket(path)
|
||||
.no_proxy()
|
||||
.default_headers(headers.clone())
|
||||
.build()
|
||||
.expect("test Unix-socket HTTP client should build"),
|
||||
"http://fabro".to_string(),
|
||||
|
|
@ -675,6 +706,7 @@ fn server_endpoint(storage_dir: &Path) -> Option<(fabro_http::HttpClient, String
|
|||
Bind::Tcp(addr) => Some((
|
||||
fabro_http::HttpClientBuilder::new()
|
||||
.no_proxy()
|
||||
.default_headers(headers)
|
||||
.build()
|
||||
.expect("test TCP HTTP client should build"),
|
||||
format!("http://{addr}"),
|
||||
|
|
|
|||
|
|
@ -10,10 +10,7 @@ mod smoke;
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use fabro_config::Storage;
|
||||
use fabro_server::bind::Bind;
|
||||
|
||||
use crate::cmd::support::RunProjection;
|
||||
use crate::cmd::support::{RunProjection, server_endpoint};
|
||||
pub(super) fn fixture(name: &str) -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests/it/workflow/fixtures")
|
||||
|
|
@ -36,41 +33,11 @@ fn infer_run_id(run_dir: &Path) -> String {
|
|||
.expect("run dir should contain resolvable run id")
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct TestServerRecord {
|
||||
bind: Bind,
|
||||
}
|
||||
|
||||
fn server_endpoint(storage_dir: &Path) -> (fabro_http::HttpClient, String) {
|
||||
let record_path = Storage::new(storage_dir).server_state().record_path();
|
||||
let record: TestServerRecord = serde_json::from_str(
|
||||
&std::fs::read_to_string(record_path).expect("server record should exist"),
|
||||
)
|
||||
.expect("server record should parse");
|
||||
match record.bind {
|
||||
Bind::Unix(path) => (
|
||||
fabro_http::HttpClientBuilder::new()
|
||||
.unix_socket(path)
|
||||
.no_proxy()
|
||||
.build()
|
||||
.expect("test Unix-socket HTTP client should build"),
|
||||
"http://fabro".to_string(),
|
||||
),
|
||||
Bind::Tcp(addr) => (
|
||||
fabro_http::HttpClientBuilder::new()
|
||||
.no_proxy()
|
||||
.build()
|
||||
.expect("test TCP HTTP client should build"),
|
||||
format!("http://{addr}"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_server_json_for_storage<T: serde::de::DeserializeOwned>(
|
||||
storage_dir: &Path,
|
||||
path: &str,
|
||||
) -> T {
|
||||
let (client, base_url) = server_endpoint(storage_dir);
|
||||
let (client, base_url) = server_endpoint(storage_dir).expect("server endpoint should exist");
|
||||
let response = client
|
||||
.get(format!("{base_url}{path}"))
|
||||
.send()
|
||||
|
|
|
|||
|
|
@ -13,13 +13,11 @@ mod real_cli;
|
|||
use std::path::{Path, PathBuf};
|
||||
use std::time::Duration;
|
||||
|
||||
use fabro_config::Storage;
|
||||
use fabro_server::bind::Bind;
|
||||
use fabro_store::EventEnvelope;
|
||||
use fabro_test::TestContext;
|
||||
use serde_json::Value;
|
||||
|
||||
use crate::cmd::support::RunProjection;
|
||||
use crate::cmd::support::{RunProjection, server_endpoint};
|
||||
|
||||
pub(super) fn fixture(name: &str) -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
|
|
@ -105,41 +103,11 @@ fn block_on<T>(future: impl std::future::Future<Output = T>) -> T {
|
|||
.block_on(future)
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct TestServerRecord {
|
||||
bind: Bind,
|
||||
}
|
||||
|
||||
fn server_endpoint(storage_dir: &Path) -> (fabro_http::HttpClient, String) {
|
||||
let record_path = Storage::new(storage_dir).server_state().record_path();
|
||||
let record: TestServerRecord = serde_json::from_str(
|
||||
&std::fs::read_to_string(record_path).expect("server record should exist"),
|
||||
)
|
||||
.expect("server record should parse");
|
||||
match record.bind {
|
||||
Bind::Unix(path) => (
|
||||
fabro_http::HttpClientBuilder::new()
|
||||
.unix_socket(path)
|
||||
.no_proxy()
|
||||
.build()
|
||||
.expect("test Unix-socket HTTP client should build"),
|
||||
"http://fabro".to_string(),
|
||||
),
|
||||
Bind::Tcp(addr) => (
|
||||
fabro_http::HttpClientBuilder::new()
|
||||
.no_proxy()
|
||||
.build()
|
||||
.expect("test TCP HTTP client should build"),
|
||||
format!("http://{addr}"),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_server_json_for_storage<T: serde::de::DeserializeOwned>(
|
||||
storage_dir: &Path,
|
||||
path: &str,
|
||||
) -> T {
|
||||
let (client, base_url) = server_endpoint(storage_dir);
|
||||
let (client, base_url) = server_endpoint(storage_dir).expect("server endpoint should exist");
|
||||
let response = client
|
||||
.get(format!("{base_url}{path}"))
|
||||
.send()
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
|
|||
use cookie::Key;
|
||||
use fabro_types::RunAuthMethod;
|
||||
use fabro_types::settings::{ServerListenSettings, ServerSettings as ResolvedServerSettings};
|
||||
use fabro_util::dev_token::validate_dev_token_format;
|
||||
use hmac::{Hmac, Mac};
|
||||
use jsonwebtoken::{Algorithm, DecodingKey, Validation};
|
||||
use rustls_pki_types::CertificateDer;
|
||||
|
|
@ -15,7 +16,6 @@ use sha2::Sha256;
|
|||
|
||||
use crate::error::ApiError;
|
||||
use crate::web_auth::SessionCookie;
|
||||
use fabro_util::dev_token::validate_dev_token_format;
|
||||
|
||||
type HmacSha256 = Hmac<Sha256>;
|
||||
const DEV_TOKEN_COMPARE_KEY: &[u8] = b"fabro-dev-token-compare-key";
|
||||
|
|
@ -1077,14 +1077,14 @@ enabled = true
|
|||
.body(Body::empty())
|
||||
.unwrap();
|
||||
req.extensions_mut().insert(SessionCookie {
|
||||
login: "brynary".to_string(),
|
||||
provider: "github".to_string(),
|
||||
name: "Brynary".to_string(),
|
||||
email: "b@example.com".to_string(),
|
||||
avatar_url: "https://example.com/avatar.png".to_string(),
|
||||
user_url: "https://github.com/brynary".to_string(),
|
||||
login: "brynary".to_string(),
|
||||
provider: "github".to_string(),
|
||||
name: "Brynary".to_string(),
|
||||
email: "b@example.com".to_string(),
|
||||
avatar_url: "https://example.com/avatar.png".to_string(),
|
||||
user_url: "https://github.com/brynary".to_string(),
|
||||
provider_id: Some(1),
|
||||
exp: 9_999_999_999,
|
||||
exp: 9_999_999_999,
|
||||
});
|
||||
|
||||
let response = app.oneshot(req).await.unwrap();
|
||||
|
|
|
|||
|
|
@ -617,7 +617,7 @@ impl AppState {
|
|||
pub(crate) fn session_key(&self) -> Option<Key> {
|
||||
self.session_key_override.clone().or_else(|| {
|
||||
self.server_secret("SESSION_SECRET")
|
||||
.map(|value| Key::derive_from(value.as_bytes()))
|
||||
.map(|value| Key::derive_from(value.as_bytes()))
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -3172,6 +3172,9 @@ fn worker_command(
|
|||
.stderr(Stdio::piped());
|
||||
|
||||
cmd.env_remove("FABRO_JSON");
|
||||
if let Some(token) = std::env::var_os("FABRO_DEV_TOKEN") {
|
||||
cmd.env("FABRO_DEV_TOKEN", token);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fabro_proc::pre_exec_setpgid(cmd.as_std_mut());
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ use cookie::time::Duration;
|
|||
use cookie::{Cookie, CookieJar, Expiration, Key, SameSite};
|
||||
use fabro_config::Storage;
|
||||
use fabro_types::settings::{InterpString, SettingsLayer};
|
||||
use fabro_util::dev_token::validate_dev_token_format;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::json;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
|
@ -19,21 +20,20 @@ use tracing::{debug, error, info, warn};
|
|||
use crate::jwt_auth::{AuthMode, AuthStrategy, dev_token_matches};
|
||||
use crate::server::AppState;
|
||||
use crate::server_secrets::ServerSecrets;
|
||||
use fabro_util::dev_token::validate_dev_token_format;
|
||||
|
||||
pub const SESSION_COOKIE_NAME: &str = "__fabro_session";
|
||||
const OAUTH_STATE_COOKIE_NAME: &str = "fabro_oauth_state";
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct SessionCookie {
|
||||
pub login: String,
|
||||
pub provider: String,
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
pub avatar_url: String,
|
||||
pub user_url: String,
|
||||
pub login: String,
|
||||
pub provider: String,
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
pub avatar_url: String,
|
||||
pub user_url: String,
|
||||
pub provider_id: Option<i64>,
|
||||
pub exp: i64,
|
||||
pub exp: i64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
|
@ -224,8 +224,7 @@ async fn login_dev_token(
|
|||
return json_response(StatusCode::UNAUTHORIZED, json!({"error": "Unauthorized"}));
|
||||
};
|
||||
|
||||
if !validate_dev_token_format(&payload.token) || !dev_token_matches(&payload.token, &expected)
|
||||
{
|
||||
if !validate_dev_token_format(&payload.token) || !dev_token_matches(&payload.token, &expected) {
|
||||
return json_response(StatusCode::UNAUTHORIZED, json!({"error": "Unauthorized"}));
|
||||
}
|
||||
|
||||
|
|
@ -889,7 +888,9 @@ mod tests {
|
|||
use serde_json::{Value, json};
|
||||
use tower::ServiceExt;
|
||||
|
||||
use super::{GitHubManifestConversion, api_routes, merge_settings_keys, read_private_session, routes};
|
||||
use super::{
|
||||
GitHubManifestConversion, api_routes, merge_settings_keys, read_private_session, routes,
|
||||
};
|
||||
use crate::jwt_auth::{AuthMode, AuthStrategy};
|
||||
use crate::server;
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,9 @@ pub fn load_or_create_dev_token(path: &Path) -> Result<String> {
|
|||
let token = generate_dev_token();
|
||||
let temp_path = path.with_file_name(format!(
|
||||
".{}.tmp-{:x}",
|
||||
path.file_name().and_then(|name| name.to_str()).unwrap_or("dev-token"),
|
||||
path.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("dev-token"),
|
||||
rand::random::<u64>()
|
||||
));
|
||||
write_private_token_file(&temp_path, &token)?;
|
||||
|
|
@ -75,7 +77,9 @@ pub fn write_dev_token(path: &Path, token: &str) -> Result<()> {
|
|||
|
||||
let temp_path = path.with_file_name(format!(
|
||||
".{}.tmp-{:x}",
|
||||
path.file_name().and_then(|name| name.to_str()).unwrap_or("dev-token"),
|
||||
path.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("dev-token"),
|
||||
rand::random::<u64>()
|
||||
));
|
||||
write_private_token_file(&temp_path, token)?;
|
||||
|
|
|
|||
|
|
@ -84,9 +84,10 @@ impl Home {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Home;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
|
||||
use super::Home;
|
||||
|
||||
static ENV_LOCK: LazyLock<Mutex<()>> = LazyLock::new(|| Mutex::new(()));
|
||||
|
||||
#[test]
|
||||
|
|
@ -143,6 +144,9 @@ mod tests {
|
|||
|
||||
std::env::remove_var("HOME");
|
||||
|
||||
assert_eq!(home.root(), std::path::Path::new("/tmp/fabro-home-env/.fabro"));
|
||||
assert_eq!(
|
||||
home.root(),
|
||||
std::path::Path::new("/tmp/fabro-home-env/.fabro")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue