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:
Bryan Helmkamp 2026-04-13 07:33:35 -04:00
parent a6775a051c
commit 2bf88cfe7f
16 changed files with 148 additions and 216 deletions

View file

@ -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)]

View file

@ -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;

View file

@ -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();

View file

@ -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()

View file

@ -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": {

View file

@ -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(),

View file

@ -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]",

View file

@ -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()

View file

@ -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}"),

View file

@ -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()

View file

@ -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()

View file

@ -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();

View file

@ -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());

View file

@ -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;

View file

@ -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)?;

View file

@ -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")
);
}
}