use super::layers;
use crate::context::Context;
use std::path::PathBuf;
pub(crate) const AUTH_FILE: &str = "auth.json";
pub(crate) fn home(ctx: &Context) -> PathBuf {
match ctx.codex_home() {
Some(dir) if !dir.is_empty() => PathBuf::from(dir),
_ => ctx.home().join(".codex"),
}
}
pub(crate) fn auth_file(ctx: &Context) -> PathBuf {
home(ctx).join(AUTH_FILE)
}
pub(crate) fn install_places(home: &std::path::Path) -> Vec<PathBuf> {
std::iter::once(home.join(".local/bin"))
.chain(crate::host::OS.package_bins().iter().map(PathBuf::from))
.collect()
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Backend {
File,
Keyring,
Either,
Ephemeral,
Secrets,
Unknown,
}
pub(crate) fn config_file(ctx: &Context) -> PathBuf {
home(ctx).join("config.toml")
}
pub(crate) fn store(ctx: &Context) -> layers::Store {
layers::of(ctx, &config_file(ctx))
}
pub(crate) fn sign_in_store(ctx: &Context) -> layers::Store {
layers::of_a_sign_in(ctx, &config_file(ctx))
}
#[cfg(test)]
mod tests {
use super::*;
fn backend(ctx: &Context) -> Backend {
store(ctx).backend
}
fn at(dir: &std::path::Path, config: &str) -> Context {
std::fs::create_dir_all(dir).expect("a scratch codex home");
std::fs::write(dir.join("config.toml"), config).expect("a config");
Context::for_unit_test().with_codex_home(dir.to_string_lossy().into())
}
fn scratch(name: &str) -> PathBuf {
let dir = std::env::temp_dir().join(format!(
"pitboard-codex-{name}-{}-{:?}",
std::process::id(),
std::thread::current().id()
));
let _ = std::fs::remove_dir_all(&dir);
dir
}
#[test]
fn no_setting_means_the_file() {
let dir = scratch("default");
let ctx = at(&dir, "model = \"gpt-5\"\n");
assert_eq!(backend(&ctx), Backend::File);
assert_eq!(auth_file(&ctx), dir.join("auth.json"));
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn each_store_is_recognised() {
let dir = scratch("stores");
for (written, expected) in [
("cli_auth_credentials_store = \"keyring\"", Backend::Keyring),
("cli_auth_credentials_store=\"auto\"", Backend::Either),
(
" cli_auth_credentials_store = 'ephemeral'",
Backend::Ephemeral,
),
("cli_auth_credentials_store = \"file\"", Backend::File),
(
"cli_auth_credentials_store = \"nonsense\"",
Backend::Unknown,
),
("cli_auth_credentials_store = keyring", Backend::Unknown),
] {
let ctx = at(&dir, written);
assert_eq!(backend(&ctx), expected, "{written}");
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn a_comment_after_the_value_is_not_the_value() {
let dir = scratch("comment");
let ctx = at(
&dir,
"cli_auth_credentials_store = \"keyring\" # on this mac\n",
);
assert_eq!(backend(&ctx), Backend::Keyring);
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn only_the_top_level_key_is_the_setting() {
let dir = scratch("tables");
for (config, expected, why) in [
(
"[profiles.work]\ncli_auth_credentials_store = \"keyring\"\n",
Backend::File,
"a key inside another table is not the setting",
),
(
"cli_auth_credentials_store = \"keyring\"\n\
[features]\nsecret_auth_storage = true\n",
Backend::Secrets,
"an encrypted file whose key is in the keychain is a store of its own",
),
(
"[features]\nsecret_auth_storage = true\n",
Backend::File,
"the feature only changes a keyring store",
),
] {
assert_eq!(backend(&at(&dir, config)), expected, "{why}");
}
let _ = std::fs::remove_dir_all(&dir);
}
#[test]
fn the_system_layers_are_read_as_well_as_the_persons_own() {
let dir = scratch("system");
let host = crate::host::memory::MemoryHost::new();
let ctx = at(&dir, "model = \"gpt-5\"\n").with_memory_stores(host.clone());
host.administers(
"/etc/codex/config.toml",
"cli_auth_credentials_store = \"keyring\"\n",
);
assert_eq!(backend(&ctx), Backend::Keyring);
std::fs::write(
dir.join("config.toml"),
"cli_auth_credentials_store = \"file\"\n",
)
.expect("a config");
assert_eq!(backend(&ctx), Backend::File);
host.administers(
"/etc/codex/requirements.toml",
"cli_auth_credentials_store = \"keyring\"\n",
);
assert_eq!(backend(&ctx), Backend::Keyring);
assert_eq!(
sign_in_store(&ctx).backend,
Backend::Keyring,
"a requirement pins a sign-in's store too"
);
let _ = std::fs::remove_dir_all(&dir);
}
}