mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-09 03:20:56 +00:00
Move GitHub App ID and Client ID from env vars to TOML config
Non-secret config (app_id, client_id) now lives in [git] section of ~/.arc/arc.toml. Secrets remain in .env. Setup callback writes non-secrets to TOML and secrets to .env. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
eab81aee99
commit
ae41f1c2c3
5 changed files with 92 additions and 9 deletions
|
|
@ -12,8 +12,6 @@ export ARC_JWT_PRIVATE_KEY=
|
|||
export ARC_JWT_PUBLIC_KEY=
|
||||
|
||||
export SESSION_SECRET=
|
||||
export GITHUB_APP_ID=
|
||||
export GITHUB_APP_CLIENT_ID=
|
||||
export GITHUB_APP_CLIENT_SECRET=
|
||||
export GITHUB_APP_WEBHOOK_SECRET=
|
||||
export GITHUB_APP_PRIVATE_KEY=
|
||||
|
|
@ -13,9 +13,16 @@ interface ApiConfig {
|
|||
authentication_strategy: "jwt" | "insecure_disabled";
|
||||
}
|
||||
|
||||
interface GitConfig {
|
||||
provider: "github";
|
||||
app_id: string | null;
|
||||
client_id: string | null;
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
auth: AuthConfig;
|
||||
api: ApiConfig;
|
||||
git: GitConfig;
|
||||
}
|
||||
|
||||
const AUTH_DEFAULTS: AuthConfig = {
|
||||
|
|
@ -28,6 +35,12 @@ const API_DEFAULTS: ApiConfig = {
|
|||
authentication_strategy: "jwt",
|
||||
};
|
||||
|
||||
const GIT_DEFAULTS: GitConfig = {
|
||||
provider: "github",
|
||||
app_id: null,
|
||||
client_id: null,
|
||||
};
|
||||
|
||||
function loadAppConfig(): AppConfig {
|
||||
const configPath = join(homedir(), ".arc", "arc.toml");
|
||||
|
||||
|
|
@ -40,10 +53,12 @@ function loadAppConfig(): AppConfig {
|
|||
|
||||
const rawAuth = (raw.auth ?? {}) as Partial<AuthConfig>;
|
||||
const rawApi = (raw.api ?? {}) as Partial<ApiConfig>;
|
||||
const rawGit = (raw.git ?? {}) as Partial<GitConfig>;
|
||||
|
||||
return {
|
||||
auth: { ...AUTH_DEFAULTS, ...rawAuth },
|
||||
api: { ...API_DEFAULTS, ...rawApi },
|
||||
git: { ...GIT_DEFAULTS, ...rawGit },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { GitHub, generateState } from "arctic";
|
||||
import { getAppConfig } from "./config.server";
|
||||
|
||||
export { generateState };
|
||||
|
||||
export function getGitHubOAuth() {
|
||||
const clientId = process.env.GITHUB_APP_CLIENT_ID;
|
||||
const clientId = getAppConfig().git.client_id;
|
||||
const clientSecret = process.env.GITHUB_APP_CLIENT_SECRET;
|
||||
if (!clientId || !clientSecret) {
|
||||
throw new Error("GitHub App is not configured");
|
||||
|
|
@ -12,7 +13,7 @@ export function getGitHubOAuth() {
|
|||
}
|
||||
|
||||
export function isGitHubAppConfigured(): boolean {
|
||||
return !!process.env.GITHUB_APP_CLIENT_ID;
|
||||
return getAppConfig().git.client_id !== null;
|
||||
}
|
||||
|
||||
export function getGitHubAppPrivateKey(): string {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
import { homedir } from "node:os";
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { redirect } from "react-router";
|
||||
import { parse, stringify } from "smol-toml";
|
||||
import type { Route } from "./+types/setup-callback";
|
||||
|
||||
const ENV_PATH = resolve(import.meta.dirname, "../../../../.env");
|
||||
const TOML_PATH = resolve(homedir(), ".arc", "arc.toml");
|
||||
|
||||
export async function loader({ request }: Route.LoaderArgs) {
|
||||
const url = new URL(request.url);
|
||||
|
|
@ -33,6 +36,23 @@ export async function loader({ request }: Route.LoaderArgs) {
|
|||
|
||||
const sessionSecret = randomBytes(32).toString("hex");
|
||||
|
||||
// Write non-secrets to TOML config
|
||||
let tomlConfig: Record<string, unknown> = {};
|
||||
try {
|
||||
tomlConfig = parse(await readFile(TOML_PATH, "utf-8")) as Record<string, unknown>;
|
||||
} catch {
|
||||
// file doesn't exist yet
|
||||
}
|
||||
tomlConfig.git = {
|
||||
...((tomlConfig.git as Record<string, unknown>) ?? {}),
|
||||
provider: "github",
|
||||
app_id: String(data.id),
|
||||
client_id: data.client_id,
|
||||
};
|
||||
await mkdir(resolve(homedir(), ".arc"), { recursive: true });
|
||||
await writeFile(TOML_PATH, stringify(tomlConfig), "utf-8");
|
||||
|
||||
// Write secrets to .env
|
||||
let existing = "";
|
||||
try {
|
||||
existing = await readFile(ENV_PATH, "utf-8");
|
||||
|
|
@ -42,8 +62,6 @@ export async function loader({ request }: Route.LoaderArgs) {
|
|||
|
||||
const newVars = [
|
||||
`export SESSION_SECRET=${sessionSecret}`,
|
||||
`export GITHUB_APP_ID=${data.id}`,
|
||||
`export GITHUB_APP_CLIENT_ID=${data.client_id}`,
|
||||
`export GITHUB_APP_CLIENT_SECRET=${data.client_secret}`,
|
||||
`export GITHUB_APP_WEBHOOK_SECRET=${data.webhook_secret}`,
|
||||
`export GITHUB_APP_PRIVATE_KEY=${Buffer.from(data.pem).toString("base64")}`,
|
||||
|
|
@ -53,8 +71,6 @@ export async function loader({ request }: Route.LoaderArgs) {
|
|||
await writeFile(ENV_PATH, envContent, "utf-8");
|
||||
|
||||
process.env.SESSION_SECRET = sessionSecret;
|
||||
process.env.GITHUB_APP_ID = String(data.id);
|
||||
process.env.GITHUB_APP_CLIENT_ID = data.client_id;
|
||||
process.env.GITHUB_APP_CLIENT_SECRET = data.client_secret;
|
||||
process.env.GITHUB_APP_WEBHOOK_SECRET = data.webhook_secret;
|
||||
process.env.GITHUB_APP_PRIVATE_KEY = Buffer.from(data.pem).toString("base64");
|
||||
|
|
|
|||
|
|
@ -57,6 +57,26 @@ impl Default for ApiConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GitProvider {
|
||||
Github,
|
||||
}
|
||||
|
||||
impl Default for GitProvider {
|
||||
fn default() -> Self {
|
||||
Self::Github
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, PartialEq)]
|
||||
pub struct GitConfig {
|
||||
#[serde(default)]
|
||||
pub provider: GitProvider,
|
||||
pub app_id: Option<String>,
|
||||
pub client_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct AppConfig {
|
||||
pub data_dir: Option<PathBuf>,
|
||||
|
|
@ -64,6 +84,8 @@ pub struct AppConfig {
|
|||
pub auth: AuthConfig,
|
||||
#[serde(default)]
|
||||
pub api: ApiConfig,
|
||||
#[serde(default)]
|
||||
pub git: GitConfig,
|
||||
}
|
||||
|
||||
/// Load app config from `~/.arc/arc.toml`, returning defaults if the file doesn't exist.
|
||||
|
|
@ -138,6 +160,11 @@ allowed_usernames = ["brynary", "alice"]
|
|||
[api]
|
||||
base_url = "http://example.com:8080"
|
||||
authentication_strategy = "jwt"
|
||||
|
||||
[git]
|
||||
provider = "github"
|
||||
app_id = "12345"
|
||||
client_id = "Iv1.abc123"
|
||||
"#;
|
||||
let config: AppConfig = toml::from_str(toml).unwrap();
|
||||
assert_eq!(config.auth.provider, AuthProvider::Github);
|
||||
|
|
@ -147,6 +174,9 @@ authentication_strategy = "jwt"
|
|||
config.api.authentication_strategy,
|
||||
ApiAuthenticationStrategy::Jwt
|
||||
);
|
||||
assert_eq!(config.git.provider, GitProvider::Github);
|
||||
assert_eq!(config.git.app_id.as_deref(), Some("12345"));
|
||||
assert_eq!(config.git.client_id.as_deref(), Some("Iv1.abc123"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -168,6 +198,29 @@ authentication_strategy = "jwt"
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_git_config() {
|
||||
let toml = r#"
|
||||
[git]
|
||||
provider = "github"
|
||||
app_id = "12345"
|
||||
client_id = "Iv1.abc123"
|
||||
"#;
|
||||
let config: AppConfig = toml::from_str(toml).unwrap();
|
||||
assert_eq!(config.git.provider, GitProvider::Github);
|
||||
assert_eq!(config.git.app_id.as_deref(), Some("12345"));
|
||||
assert_eq!(config.git.client_id.as_deref(), Some("Iv1.abc123"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_git_defaults() {
|
||||
let toml = "";
|
||||
let config: AppConfig = toml::from_str(toml).unwrap();
|
||||
assert_eq!(config.git.provider, GitProvider::Github);
|
||||
assert_eq!(config.git.app_id, None);
|
||||
assert_eq!(config.git.client_id, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_insecure_disabled_values() {
|
||||
let toml = r#"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue