---
source: tests/golden_typescript.rs
expression: contents
---
// Multi Scheme API MCP server — generated by mcpify. Do not hand-edit.

import type { AuthMethod, Config } from '../core/config-schema.js';
import { loadCredential, saveCredential } from '../core/credential-storage.js';
import type { AuthConfig, AuthStrategy, Credentials } from './auth-strategy.js';
import { toCredentialsMap, type RequestCredentials } from './request-credentials.js';
import { BasicAuthStrategy } from './strategies/basic.js';
import { PatAuthStrategy } from './strategies/pat.js';
import { OAuth2Strategy } from './strategies/oauth2.js';
import { OAuth1Strategy } from './strategies/oauth1.js';
import { StubAuthStrategy } from './strategies/stub.js';
import { NoActiveCredentialsError } from './errors.js';

const strategies: Record<AuthMethod, AuthStrategy> = {
  basic: new BasicAuthStrategy(),
  pat: new PatAuthStrategy(),
  oauth2: new OAuth2Strategy(),
  oauth1: new OAuth1Strategy(),
};

const CREDENTIAL_ACCOUNT = 'active-credentials';

const HEADER_LOCATIONS: Record<AuthMethod, [string, string]> = {
  basic: ['header', 'Authorization'],
  pat: ['header', 'Authorization'],
  oauth2: ['header', 'Authorization'],
  oauth1: ['none', ''],
};

/**
 * Where `authMethod`'s credential value travels on the wire — used only by
 * the HTTP-transport auth-gate/extractor (`http/auth-extractor.ts`) to know
 * which incoming header to read; stdio never calls this.
 */
export function headerLocationFor(authMethod: AuthMethod): [string, string] {
  return HEADER_LOCATIONS[authMethod];
}

/**
 * Selects exactly one active auth strategy per deployment, chosen via the
 * `auth_method` config value (REQ-1.2.3) — there is no runtime engine for
 * resolving multiple simultaneously-required schemes.
 */
export class AuthManager {
  private cachedCredentials?: Credentials;

  constructor(private readonly authMethod: AuthMethod) {}

  private get strategy(): AuthStrategy {
    return strategies[this.authMethod] ?? new StubAuthStrategy();
  }

  async login(config: AuthConfig): Promise<Credentials> {
    const credentials = await this.strategy.authenticate(config);
    this.cachedCredentials = credentials;
    await saveCredential(CREDENTIAL_ACCOUNT, JSON.stringify(credentials));
    return credentials;
  }

  /**
   * Seeds in-memory credentials directly, bypassing OS-keychain/file
   * storage entirely — for tests (which must never touch the real OS
   * keychain as a side effect of running) and for callers that already
   * hold validated credentials from elsewhere.
   */
  setCredentials(credentials: Credentials): void {
    this.cachedCredentials = credentials;
  }

  async getCredentials(): Promise<Credentials> {
    if (this.cachedCredentials && this.strategy.validateCredentials(this.cachedCredentials)) {
      return this.cachedCredentials;
    }

    const stored = await loadCredential(CREDENTIAL_ACCOUNT);
    if (stored) {
      const parsed = JSON.parse(stored) as Credentials;
      if (this.strategy.validateCredentials(parsed)) {
        this.cachedCredentials = parsed;
        return parsed;
      }
      if (this.strategy.refreshToken) {
        const refreshed = await this.strategy.refreshToken(parsed);
        this.cachedCredentials = refreshed;
        await saveCredential(CREDENTIAL_ACCOUNT, JSON.stringify(refreshed));
        return refreshed;
      }
    }

    throw new NoActiveCredentialsError(this.authMethod);
  }

  /**
   * Decorates outgoing request headers with credentials for `transport`:
   * on `'http'`, `requestOverride` (extracted per-request from the
   * caller's own headers — see `http/auth-extractor.ts`) is the *only*
   * source ever consulted, since HTTP deployments must never leak the
   * operator's local config/keychain secrets to a caller who didn't
   * supply their own; a missing override is a hard error, not a
   * fallback. On `'stdio'`, `requestOverride` is ignored and behavior is
   * unchanged from before HTTP-transport support existed:
   * `getCredentials()` (config cascade + keychain).
   *
   * API-key placement (header vs. query, and the header/param name)
   * defaults to an `X-Api-Key` header here; a scheme declaring a
   * different location is one of Story 12's api-client responsibilities
   * to refine, not this manager's — `requestOverride`'s
   * `requestHeaderName`/`requestHeaderValue` pair is the one exception,
   * since the HTTP path already knows the scheme's declared header name
   * (`TsAuthSchemeView.headerName`) at generation time.
   */
  async applyAuthHeaders(
    headers: Record<string, string>,
    method: string,
    url: string,
    transport: Config['transport'],
    requestOverride?: RequestCredentials,
  ): Promise<Record<string, string>> {
    if (transport === 'http' && this.authMethod === 'oauth1') {
      throw new Error(
        "OAuth1 is not supported over HTTP transport: its signature must be recomputed per " +
          'request against the destination URL using the server\'s consumer key/secret, which a ' +
          'simple incoming-header relay cannot provide. Use stdio transport for OAuth1-authenticated APIs.',
      );
    }

    let credentials: Credentials;
    if (transport === 'http') {
      if (!requestOverride) {
        throw new Error(
          'no credentials were supplied on this HTTP request; local config/env/keychain is ' +
            'never used as a fallback over HTTP transport',
        );
      }
      credentials = toCredentialsMap(requestOverride);
    } else {
      credentials = await this.getCredentials();
    }
    if (this.authMethod === 'oauth1') {
      const oauth1 = strategies.oauth1 as OAuth1Strategy;
      return { ...headers, Authorization: oauth1.buildAuthorizationHeader(credentials, method, url) };
    }
    if (credentials.requestHeaderName) {
      return { ...headers, [credentials.requestHeaderName]: credentials.requestHeaderValue ?? '' };
    }
    if (credentials.authorizationHeader) {
      return { ...headers, Authorization: credentials.authorizationHeader };
    }
    if (credentials.apiKey) {
      return { ...headers, 'X-Api-Key': credentials.apiKey };
    }
    return headers;
  }
}
