feat: add profile auth method for bedrock (#695)

This commit is contained in:
Ari Webb
2026-01-28 11:33:43 -08:00
committed by GitHub
parent b304107a60
commit ebdf78302d
4 changed files with 601 additions and 47 deletions

View File

@@ -0,0 +1,120 @@
/**
* Utility to parse AWS credentials from ~/.aws/credentials
*/
import { readFile } from "node:fs/promises";
import { homedir } from "node:os";
import { join } from "node:path";
export interface AwsProfile {
name: string;
accessKeyId?: string;
secretAccessKey?: string;
region?: string;
}
/**
* Parse AWS credentials file and return list of profiles
*/
export async function parseAwsCredentials(): Promise<AwsProfile[]> {
const credentialsPath = join(homedir(), ".aws", "credentials");
const configPath = join(homedir(), ".aws", "config");
const profiles: Map<string, AwsProfile> = new Map();
// Parse credentials file
try {
const content = await readFile(credentialsPath, "utf-8");
parseIniFile(content, profiles, false);
} catch {
// Credentials file doesn't exist or can't be read
}
// Parse config file for region info
try {
const content = await readFile(configPath, "utf-8");
parseIniFile(content, profiles, true);
} catch {
// Config file doesn't exist or can't be read
}
return Array.from(profiles.values());
}
/**
* Parse INI-style AWS config/credentials file
*/
function parseIniFile(
content: string,
profiles: Map<string, AwsProfile>,
isConfig: boolean,
): void {
const lines = content.split("\n");
let currentProfile: string | null = null;
for (const line of lines) {
const trimmed = line.trim();
// Skip empty lines and comments
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith(";")) {
continue;
}
// Check for profile header
const headerMatch = trimmed.match(/^\[(.+)\]$/);
if (headerMatch?.[1]) {
let profileName: string = headerMatch[1];
// In config file, profiles are prefixed with "profile " (except default)
if (isConfig && profileName.startsWith("profile ")) {
profileName = profileName.slice(8);
}
currentProfile = profileName;
if (!profiles.has(profileName)) {
profiles.set(profileName, { name: profileName });
}
continue;
}
// Parse key=value pairs
if (currentProfile) {
const kvMatch = trimmed.match(/^([^=]+)=(.*)$/);
if (kvMatch?.[1] && kvMatch[2] !== undefined) {
const key = kvMatch[1].trim();
const value = kvMatch[2].trim();
const profile = profiles.get(currentProfile);
if (!profile) continue;
switch (key) {
case "aws_access_key_id":
profile.accessKeyId = value;
break;
case "aws_secret_access_key":
profile.secretAccessKey = value;
break;
case "region":
profile.region = value;
break;
}
}
}
}
}
/**
* Get a specific profile by name
*/
export async function getAwsProfile(
profileName: string,
): Promise<AwsProfile | null> {
const profiles = await parseAwsCredentials();
return profiles.find((p) => p.name === profileName) || null;
}
/**
* Get list of available profile names
*/
export async function getAwsProfileNames(): Promise<string[]> {
const profiles = await parseAwsCredentials();
return profiles.map((p) => p.name);
}