1import {
2 DEFAULT_API_BASE_URL,
3 normalizeApiBaseUrl,
4} from "@distilled.cloud/fly-io";
5import * as Effect from "effect/Effect";
6import * as Redacted from "effect/Redacted";
7import { getEnv, getEnvRedactedRequired } from "../Auth/Env.ts";
8import {
9 makeStoredAuthProvider,
10 storedSecret,
11 storedValueText,
12 type StoredAuthConfig,
13} from "../Auth/StoredAuthProvider.ts";
14
15export const FLY_AUTH_PROVIDER_NAME = "Fly";
16export const FLY_API_TOKEN_ENV = "FLY_API_TOKEN";
17export const FLY_API_HOSTNAME_ENV = "FLY_API_HOSTNAME";
18
19export type FlyAuthConfig = StoredAuthConfig;
20
21export type FlyResolvedCredentials = {
22 type: "token";
23 apiKey: Redacted.Redacted<string>;
24 apiBaseUrl: string;
25 source: { type: FlyAuthConfig["method"] | "env"; details?: string };
26};
27
28const resolveApiBaseUrl = (explicit?: string) =>
29 getEnv(FLY_API_HOSTNAME_ENV).pipe(
30 Effect.map((fromEnv) => normalizeApiBaseUrl(explicit ?? fromEnv)),
31 );
32
33const flyAuth = makeStoredAuthProvider<FlyResolvedCredentials>({
34 provider: FLY_AUTH_PROVIDER_NAME,
35 fields: [
36 { name: "apiKey", label: "Fly.io API Token", secret: true },
37 {
38 name: "apiBaseUrl",
39 label: "Fly API hostname",
40 placeholder: DEFAULT_API_BASE_URL,
41 optional: true,
42 },
43 ],
44 toResolved: (values) => ({
45 type: "token",
46 apiKey: storedSecret(values.apiKey) ?? Redacted.make(""),
47 apiBaseUrl: normalizeApiBaseUrl(storedValueText(values.apiBaseUrl)),
48 source: { type: "stored" },
49 }),
50 readEnvironment: Effect.gen(function* () {
51 const apiKey = yield* getEnvRedactedRequired(FLY_API_TOKEN_ENV);
52 const apiBaseUrl = yield* resolveApiBaseUrl();
53 return {
54 type: "token" as const,
55 apiKey,
56 apiBaseUrl,
57 source: { type: "env" as const, details: FLY_API_TOKEN_ENV },
58 };
59 }),
60 environment: [
61 { name: FLY_API_TOKEN_ENV, required: true, secret: true },
62 { name: FLY_API_HOSTNAME_ENV, required: false },
63 ],
64});
65
66/**
67 * Layer that registers the Fly {@link AuthProvider} into the
68 * {@link AuthProviders} registry. Fly uses a stored API token locally and
69 * `FLY_API_TOKEN` in CI; `FLY_API_HOSTNAME` optionally overrides the API root.
70 */
71export const FlyAuth = flyAuth.layer;