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
34 changes: 34 additions & 0 deletions src/auth/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,40 @@ describe("resolveAuth", () => {
expect(auth.type).toBe("bearer");
expect(auth.value).toBe("env-token");
});

it("uses .toclirc auth token", async () => {
const auth = await resolveAuth({ rcAuthType: "bearer", rcAuthToken: "rc-token-123" }, minimalSpec, {});
expect(auth.type).toBe("bearer");
expect(auth.value).toBe("rc-token-123");
});

it("uses .toclirc auth envVar", async () => {
const auth = await resolveAuth({ rcAuthType: "bearer", rcAuthEnvVar: "MY_API_TOKEN" }, minimalSpec, { MY_API_TOKEN: "env-resolved" });
expect(auth.type).toBe("bearer");
expect(auth.value).toBe("env-resolved");
});

it("defaults rcAuthType to bearer when not specified", async () => {
const auth = await resolveAuth({ rcAuthToken: "tok" }, minimalSpec, {});
expect(auth.type).toBe("bearer");
expect(auth.value).toBe("tok");
});

it("inline --token takes priority over .toclirc auth", async () => {
const auth = await resolveAuth({ token: "inline-tok", rcAuthToken: "rc-tok" }, minimalSpec, {});
expect(auth.value).toBe("inline-tok");
});

it(".toclirc auth takes priority over saved profile", async () => {
await saveProfile("default", { type: "bearer", value: "profile-tok" });
const auth = await resolveAuth({ rcAuthToken: "rc-tok" }, minimalSpec);
expect(auth.value).toBe("rc-tok");
});

it("resolves env vars in .toclirc auth token", async () => {
const auth = await resolveAuth({ rcAuthToken: "$SECRET_TOK" }, minimalSpec, { SECRET_TOK: "resolved-secret" });
expect(auth.value).toBe("resolved-secret");
});
});

describe("auth config (profile persistence)", () => {
Expand Down
20 changes: 18 additions & 2 deletions src/auth/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export interface AuthFlags {
apiKey?: string;
authHeader?: string;
profile?: string;
rcAuthType?: string;
rcAuthToken?: string;
rcAuthEnvVar?: string;
}

export async function resolveAuth(
Expand All @@ -26,7 +29,20 @@ export async function resolveAuth(
return { type: "bearer", value: resolveEnvVar(flags.authHeader, env) };
}

// Priority 2: Environment variables from spec
// Priority 2: .toclirc auth config
if (flags.rcAuthToken) {
const type = (flags.rcAuthType as AuthConfig["type"]) ?? "bearer";
return { type, value: resolveEnvVar(flags.rcAuthToken, env) };
}
if (flags.rcAuthEnvVar) {
const envVal = env[flags.rcAuthEnvVar];
if (envVal) {
const type = (flags.rcAuthType as AuthConfig["type"]) ?? "bearer";
return { type, value: envVal };
}
}

// Priority 3: Environment variables from spec
const specAuth = detectAuthFromSpec(spec);
if (specAuth) {
// Check common env var names
Expand All @@ -38,7 +54,7 @@ export async function resolveAuth(
}
}

// Priority 3: Saved profile
// Priority 4: Saved profile
const profileName = flags.profile ?? "default";
const profile = await getProfile(profileName);
if (profile) {
Expand Down
24 changes: 24 additions & 0 deletions src/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,30 @@ describe("resolveConfig", () => {
expect(resolved.authEnvVar).toBe("STAGING_TOKEN");
});

it("returns auth token from config", () => {
const config = {
spec: "./api.yaml",
auth: { type: "bearer", token: "direct-token" },
};
const resolved = resolveConfig(config);
expect(resolved.authType).toBe("bearer");
expect(resolved.authToken).toBe("direct-token");
});

it("overrides auth token with environment config", () => {
const config = {
spec: "./api.yaml",
auth: { type: "bearer", token: "prod-token" },
environments: {
staging: {
auth: { token: "staging-token" },
},
},
};
const resolved = resolveConfig(config, "staging");
expect(resolved.authToken).toBe("staging-token");
});

it("keeps base config for unknown env", () => {
const config = {
spec: "./api.yaml",
Expand Down
11 changes: 9 additions & 2 deletions src/config/rc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ export interface RcConfig {
baseUrl?: string;
auth?: {
type?: string;
token?: string;
envVar?: string;
};
environments?: Record<string, {
baseUrl?: string;
auth?: {
type?: string;
token?: string;
envVar?: string;
};
}>;
Expand All @@ -34,22 +36,27 @@ export async function loadConfig(startDir?: string): Promise<RcConfig | null> {
return config;
}

export function resolveConfig(config: RcConfig, envName?: string): { spec: string; baseUrl?: string; authEnvVar?: string } {
export function resolveConfig(config: RcConfig, envName?: string): { spec: string; baseUrl?: string; authType?: string; authToken?: string; authEnvVar?: string } {
let spec = config.spec;
let baseUrl = config.baseUrl;
let authType = config.auth?.type;
let authToken = config.auth?.token;
let authEnvVar = config.auth?.envVar;

if (envName && config.environments?.[envName]) {
const env = config.environments[envName];
if (env.baseUrl) baseUrl = env.baseUrl;
if (env.auth?.type) authType = env.auth.type;
if (env.auth?.token) authToken = env.auth.token;
if (env.auth?.envVar) authEnvVar = env.auth.envVar;
}

// Resolve env vars in values
spec = resolveEnvVars(spec);
if (baseUrl) baseUrl = resolveEnvVars(baseUrl);
if (authToken) authToken = resolveEnvVars(authToken);

return { spec, baseUrl, authEnvVar };
return { spec, baseUrl, authType, authToken, authEnvVar };
}

async function findRcFile(dir: string): Promise<string | null> {
Expand Down
29 changes: 20 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { extractOperations } from "./parser/extractor.js";
import { executeRequest } from "./executor/http.js";
import { formatOutput } from "./output/formatters.js";
import { registerAuthCommands } from "./auth/commands.js";
import { resolveAuth as resolveAuthFromFlags } from "./auth/flags.js";
import { registerInitCommand } from "./config/init.js";
import { registerUseCommand } from "./templates/commands.js";
import { loadConfig, resolveConfig } from "./config/rc.js";
Expand Down Expand Up @@ -59,13 +60,19 @@ async function main() {
// Resolve spec: --spec flag > .toclirc config
let specPath = getFlagValue(rawArgs, "--spec");
let configBaseUrl: string | undefined;
let rcAuthType: string | undefined;
let rcAuthToken: string | undefined;
let rcAuthEnvVar: string | undefined;

if (!specPath) {
const rc = await loadConfig();
if (rc) {
const resolved = resolveConfig(rc, envName);
specPath = resolved.spec;
configBaseUrl = resolved.baseUrl;
rcAuthType = resolved.authType;
rcAuthToken = resolved.authToken;
rcAuthEnvVar = resolved.authEnvVar;
}
}

Expand Down Expand Up @@ -93,10 +100,22 @@ async function main() {
return;
}

const auth = await resolveAuthFromFlags(
{
token: getFlagValue(rawArgs, "--token"),
apiKey: getFlagValue(rawArgs, "--api-key"),
profile: getFlagValue(rawArgs, "--profile"),
rcAuthType,
rcAuthToken,
rcAuthEnvVar,
},
spec
);

const config: RuntimeConfig = {
specPath,
baseUrl: getFlagValue(rawArgs, "--base-url") ?? configBaseUrl ?? resolveBaseUrl(spec, specPath),
auth: resolveAuth(rawArgs),
auth,
output: getFlagValue(rawArgs, "--output") ?? (process.stdout.isTTY ? "pretty" : "json"),
maxItems: getFlagValue(rawArgs, "--max-items") ? parseInt(getFlagValue(rawArgs, "--max-items")!) : undefined,
verbose: rawArgs.includes("--verbose"),
Expand Down Expand Up @@ -360,14 +379,6 @@ function simplifyName(operationId: string, tag: string): string {
return operationId.toLowerCase();
}

function resolveAuth(args: string[]): RuntimeConfig["auth"] {
const token = getFlagValue(args, "--token");
if (token) return { type: "bearer", value: token };
const apiKey = getFlagValue(args, "--api-key");
if (apiKey) return { type: "apiKey", value: apiKey, headerName: "X-API-Key" };
return { type: "none", value: "" };
}

function getFlagValue(args: string[], flag: string): string | undefined {
const idx = args.indexOf(flag);
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
Expand Down