| 1 | import { DEFAULT_API_BASE_URL } from "@distilled.cloud/axiom/Credentials"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as Redacted from "effect/Redacted"; |
| 4 | import { AuthError } from "../Auth/AuthProvider.ts"; |
| 5 | import { getEnv, getEnvRedacted } from "../Auth/Env.ts"; |
| 6 | import { |
| 7 | makeStoredAuthProvider, |
| 8 | storedSecret, |
| 9 | storedValueText, |
| 10 | type StoredAuthConfig, |
| 11 | } from "../Auth/StoredAuthProvider.ts"; |
| 12 | |
| 13 | export const AXIOM_AUTH_PROVIDER_NAME = "Axiom"; |
| 14 | |
| 15 | export type AxiomAuthConfig = StoredAuthConfig; |
| 16 | |
| 17 | /** |
| 18 | * Resolved Axiom credentials. The provider values are a flat record |
| 19 | * (token + optional orgId/apiBaseUrl); the `apiToken`/`pat` distinction is |
| 20 | * derived at resolution time from orgId presence — an org id means the token |
| 21 | * is treated as a personal access token, mirroring how the CI environment |
| 22 | * resolution has always classified `AXIOM_ORG_ID`. |
| 23 | */ |
| 24 | export type AxiomResolvedCredentials = |
| 25 | | { |
| 26 | type: "apiToken"; |
| 27 | apiToken: Redacted.Redacted<string>; |
| 28 | apiBaseUrl: string; |
| 29 | orgId?: string; |
| 30 | source: { type: AxiomAuthConfig["method"] | "env"; details?: string }; |
| 31 | } |
| 32 | | { |
| 33 | type: "pat"; |
| 34 | apiToken: Redacted.Redacted<string>; |
| 35 | apiBaseUrl: string; |
| 36 | orgId: string; |
| 37 | source: { type: AxiomAuthConfig["method"] | "env"; details?: string }; |
| 38 | }; |
| 39 | |
| 40 | const readEnvironment = Effect.gen(function* () { |
| 41 | const apiToken = |
| 42 | (yield* getEnvRedacted("AXIOM_TOKEN")) ?? |
| 43 | (yield* getEnvRedacted("AXIOM_API_KEY")); |
| 44 | if (!apiToken) { |
| 45 | return yield* new AuthError({ |
| 46 | message: |
| 47 | "Axiom CI credentials not found. Set AXIOM_TOKEN or AXIOM_API_KEY.", |
| 48 | }); |
| 49 | } |
| 50 | const apiBaseUrl = (yield* getEnv("AXIOM_URL")) ?? DEFAULT_API_BASE_URL; |
| 51 | const orgId = yield* getEnv("AXIOM_ORG_ID"); |
| 52 | return orgId |
| 53 | ? { |
| 54 | type: "pat" as const, |
| 55 | apiToken, |
| 56 | apiBaseUrl, |
| 57 | orgId, |
| 58 | source: { type: "env" as const }, |
| 59 | } |
| 60 | : { |
| 61 | type: "apiToken" as const, |
| 62 | apiToken, |
| 63 | apiBaseUrl, |
| 64 | source: { type: "env" as const }, |
| 65 | }; |
| 66 | }); |
| 67 | |
| 68 | const axiomAuth = makeStoredAuthProvider<AxiomResolvedCredentials>({ |
| 69 | provider: AXIOM_AUTH_PROVIDER_NAME, |
| 70 | fields: [ |
| 71 | { |
| 72 | name: "token", |
| 73 | label: "Axiom API Token or Personal Access Token", |
| 74 | secret: true, |
| 75 | }, |
| 76 | { |
| 77 | name: "orgId", |
| 78 | label: "Axiom Org ID (required for personal access tokens)", |
| 79 | optional: true, |
| 80 | }, |
| 81 | { |
| 82 | name: "apiBaseUrl", |
| 83 | label: "Axiom API Base URL", |
| 84 | optional: true, |
| 85 | placeholder: DEFAULT_API_BASE_URL, |
| 86 | }, |
| 87 | ], |
| 88 | toResolved: (values, source) => { |
| 89 | const apiToken = storedSecret(values.token) ?? Redacted.make(""); |
| 90 | const apiBaseUrl = |
| 91 | storedValueText(values.apiBaseUrl) ?? DEFAULT_API_BASE_URL; |
| 92 | return values.orgId !== undefined |
| 93 | ? { |
| 94 | type: "pat", |
| 95 | apiToken, |
| 96 | apiBaseUrl, |
| 97 | orgId: storedValueText(values.orgId) ?? "", |
| 98 | source: { type: source }, |
| 99 | } |
| 100 | : { |
| 101 | type: "apiToken", |
| 102 | apiToken, |
| 103 | apiBaseUrl, |
| 104 | source: { type: source }, |
| 105 | }; |
| 106 | }, |
| 107 | readEnvironment, |
| 108 | environment: [ |
| 109 | { |
| 110 | name: "AXIOM_TOKEN", |
| 111 | required: true, |
| 112 | secret: true, |
| 113 | alternatives: ["AXIOM_API_KEY"], |
| 114 | description: "Personal access token or API token.", |
| 115 | }, |
| 116 | { |
| 117 | name: "AXIOM_URL", |
| 118 | required: false, |
| 119 | description: "API base URL for self-hosted or regional deployments.", |
| 120 | }, |
| 121 | { |
| 122 | name: "AXIOM_ORG_ID", |
| 123 | required: false, |
| 124 | description: |
| 125 | "Organization id; required when the token is a personal access token.", |
| 126 | }, |
| 127 | ], |
| 128 | }); |
| 129 | |
| 130 | /** |
| 131 | * Layer that registers the Axiom {@link AuthProvider} into the |
| 132 | * {@link AuthProviders} registry when built. Include this in the Axiom |
| 133 | * `providers()` layer so the alchemy CLI can discover it. |
| 134 | */ |
| 135 | export const AxiomAuth = axiomAuth.layer; |
| 136 | |
| 137 | /** Schema of Axiom's inline static-token values. */ |
| 138 | export const AxiomStoredCredentials = axiomAuth.storedSchema; |
| 139 | export type AxiomStoredCredentials = typeof AxiomStoredCredentials.Type; |