@alchemy.run/infisical 0.0.0 → 2.0.0-beta.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +40 -4
- package/lib/AuthProvider.d.ts +53 -0
- package/lib/AuthProvider.d.ts.map +1 -0
- package/lib/AuthProvider.js +243 -0
- package/lib/AuthProvider.js.map +1 -0
- package/lib/Contributions.d.ts +7 -0
- package/lib/Contributions.d.ts.map +1 -0
- package/lib/Contributions.js +6 -0
- package/lib/Contributions.js.map +1 -0
- package/lib/SecretsProvider.d.ts +81 -0
- package/lib/SecretsProvider.d.ts.map +1 -0
- package/lib/SecretsProvider.js +128 -0
- package/lib/SecretsProvider.js.map +1 -0
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -0
- package/package.json +52 -3
- package/src/AuthProvider.ts +334 -0
- package/src/Contributions.ts +9 -0
- package/src/SecretsProvider.ts +202 -0
- package/src/index.ts +19 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type SecretsLayer, type SecretsOption } from "alchemy/Secrets/Provider";
|
|
2
|
+
import { UserFacingError } from "alchemy/UserFacingError";
|
|
3
|
+
import { type InfisicalResolvedCredentials } from "./AuthProvider.ts";
|
|
4
|
+
export interface InfisicalOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Project slug, or the project's UUID. Infisical only resolves slugs for
|
|
7
|
+
* machine identities, so a user token must pass the UUID.
|
|
8
|
+
*/
|
|
9
|
+
project: string;
|
|
10
|
+
/** Environment slug, e.g. `dev` or `prod`. */
|
|
11
|
+
environment: string;
|
|
12
|
+
/**
|
|
13
|
+
* Folder to read from.
|
|
14
|
+
* @default "/"
|
|
15
|
+
*/
|
|
16
|
+
path?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Also read every sub-folder of `path`.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
recursive?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Include secrets imported into the folder from elsewhere. Directly
|
|
24
|
+
* defined secrets win over imported ones.
|
|
25
|
+
* @default true
|
|
26
|
+
*/
|
|
27
|
+
includeImports?: boolean;
|
|
28
|
+
}
|
|
29
|
+
interface InfisicalCredentials extends InfisicalResolvedCredentials {
|
|
30
|
+
/** Set when the credentials came from a stored Alchemy profile. */
|
|
31
|
+
readonly profileName?: string;
|
|
32
|
+
}
|
|
33
|
+
declare const InfisicalSecretsError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
34
|
+
readonly _tag: "InfisicalSecretsError";
|
|
35
|
+
} & Readonly<A>;
|
|
36
|
+
/**
|
|
37
|
+
* Infisical could not serve the requested secrets. The message says
|
|
38
|
+
* "Infisical" up front and names the selection, so a stack trace never has
|
|
39
|
+
* to be read to know which secrets source broke.
|
|
40
|
+
*/
|
|
41
|
+
export declare class InfisicalSecretsError extends InfisicalSecretsError_base<{
|
|
42
|
+
readonly selection: InfisicalOptions;
|
|
43
|
+
readonly credentials: InfisicalCredentials;
|
|
44
|
+
/** What the Infisical SDK failed with. */
|
|
45
|
+
readonly cause: {
|
|
46
|
+
readonly _tag: string;
|
|
47
|
+
readonly message: string;
|
|
48
|
+
};
|
|
49
|
+
}> {
|
|
50
|
+
readonly [UserFacingError] = true;
|
|
51
|
+
get message(): string;
|
|
52
|
+
}
|
|
53
|
+
declare const InfisicalSecretsProvider_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => Readonly<A> & {
|
|
54
|
+
readonly _tag: "alchemy/SecretProvider::Infisical";
|
|
55
|
+
} & import("effect/Pipeable").Pipeable;
|
|
56
|
+
/** A `secrets` entry that loads an Infisical environment. */
|
|
57
|
+
export declare class InfisicalSecretsProvider extends InfisicalSecretsProvider_base<{
|
|
58
|
+
readonly layer: SecretsLayer;
|
|
59
|
+
}> {
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Load Infisical secrets into Effect Config without touching `process.env`.
|
|
63
|
+
*
|
|
64
|
+
* Authenticate locally with `alchemy profile edit --add Infisical` and
|
|
65
|
+
* paste a machine identity's universal-auth client id and secret; Alchemy
|
|
66
|
+
* mints a fresh access token on every run. The client secret must remain valid. In
|
|
67
|
+
* CI, set `INFISICAL_TOKEN`, or `INFISICAL_IDENTITY_ID` to log in with the
|
|
68
|
+
* platform's OIDC token.
|
|
69
|
+
*
|
|
70
|
+
* ```ts
|
|
71
|
+
* secrets: [
|
|
72
|
+
* Infisical.Secrets(({ stage }) => ({
|
|
73
|
+
* project: "my-app",
|
|
74
|
+
* environment: stage === "prod" ? "prod" : "dev",
|
|
75
|
+
* })),
|
|
76
|
+
* ]
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export declare const Secrets: (options: SecretsOption<InfisicalOptions>) => InfisicalSecretsProvider;
|
|
80
|
+
export {};
|
|
81
|
+
//# sourceMappingURL=SecretsProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecretsProvider.d.ts","sourceRoot":"","sources":["../src/SecretsProvider.ts"],"names":[],"mappings":"AAMA,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,aAAa,EACnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAM1D,OAAO,EAGL,KAAK,4BAA4B,EAClC,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,WAAW,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,UAAU,oBAAqB,SAAQ,4BAA4B;IACjE,mEAAmE;IACnE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;;;;AAQD;;;;GAIG;AACH,qBAAa,qBAAsB,SAAQ,2BAA0C;IACnF,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,QAAQ,CAAC,WAAW,EAAE,oBAAoB,CAAC;IAC3C,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACrE,CAAC;IACA,QAAQ,CAAC,CAAC,eAAe,CAAC,QAAQ;IAElC,IAAa,OAAO,WAanB;CACF;;;;AAwED,6DAA6D;AAC7D,qBAAa,wBAAyB,SAAQ,8BAE5C;IAAE,QAAQ,CAAC,KAAK,EAAE,YAAY,CAAA;CAAE,CAAC;CAAG;AAEtC;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,OAAO,YAAa,aAAa,CAAC,gBAAgB,CAAC,6BAkB5D,CAAC"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { fromApiKey, getProjectBySlug, listSecretsV4 } from "@distilled.cloud/infisical";
|
|
2
|
+
import * as Retry from "@distilled.cloud/infisical/Retry";
|
|
3
|
+
import { refreshHint } from "alchemy/Auth/AuthProvider";
|
|
4
|
+
import { SuppressMissingProviderConfig } from "alchemy/Auth/Profile";
|
|
5
|
+
import { resolveProviderConfig } from "alchemy/Auth/Resolve";
|
|
6
|
+
import { logLoadedKeys } from "alchemy/Secrets/Log";
|
|
7
|
+
import { CredentialsConfig, resolveSecretsOption, } from "alchemy/Secrets/Provider";
|
|
8
|
+
import { UserFacingError } from "alchemy/UserFacingError";
|
|
9
|
+
import * as ConfigProvider from "effect/ConfigProvider";
|
|
10
|
+
import * as Data from "effect/Data";
|
|
11
|
+
import * as Effect from "effect/Effect";
|
|
12
|
+
import * as Redacted from "effect/Redacted";
|
|
13
|
+
import * as Schema from "effect/Schema";
|
|
14
|
+
import { InfisicalAuth, } from "./AuthProvider.js";
|
|
15
|
+
/** Human description of which secrets were asked for, for error messages. */
|
|
16
|
+
const describeSelection = (options) => {
|
|
17
|
+
const folder = options.path === undefined ? "" : ` path '${options.path}'`;
|
|
18
|
+
return `project '${options.project}' environment '${options.environment}'${folder}`;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Infisical could not serve the requested secrets. The message says
|
|
22
|
+
* "Infisical" up front and names the selection, so a stack trace never has
|
|
23
|
+
* to be read to know which secrets source broke.
|
|
24
|
+
*/
|
|
25
|
+
export class InfisicalSecretsError extends Data.TaggedError("InfisicalSecretsError") {
|
|
26
|
+
[UserFacingError] = true;
|
|
27
|
+
get message() {
|
|
28
|
+
const selection = describeSelection(this.selection);
|
|
29
|
+
switch (this.cause._tag) {
|
|
30
|
+
case "Unauthorized":
|
|
31
|
+
return this.credentials.profileName === undefined
|
|
32
|
+
? "Infisical rejected the token. Check INFISICAL_TOKEN; to set up a machine identity locally run `alchemy profile edit --add Infisical`."
|
|
33
|
+
: `Infisical credentials were rejected. ${refreshHint("Infisical", this.credentials.profileName)}`;
|
|
34
|
+
case "NotFound":
|
|
35
|
+
case "Forbidden":
|
|
36
|
+
return `Infisical could not read ${selection}: ${this.cause.message}. Check Infisical.Secrets({ project, environment }) and that the identity has read access to it.`;
|
|
37
|
+
default:
|
|
38
|
+
return `Infisical could not download secrets for ${selection}: ${this.cause.message}`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve credentials through the Infisical auth provider: `INFISICAL_TOKEN`
|
|
44
|
+
* when present, otherwise the selected profile (which mints a token from
|
|
45
|
+
* the stored machine identity).
|
|
46
|
+
*/
|
|
47
|
+
const resolveCredentials = Effect.gen(function* () {
|
|
48
|
+
const resolved = yield* resolveProviderConfig("Infisical").pipe(Effect.provide(InfisicalAuth));
|
|
49
|
+
const { token, apiBaseUrl } = yield* resolved.resolve;
|
|
50
|
+
const credentials = {
|
|
51
|
+
token,
|
|
52
|
+
apiBaseUrl,
|
|
53
|
+
profileName: resolved.profileName,
|
|
54
|
+
};
|
|
55
|
+
return credentials;
|
|
56
|
+
}).pipe(Effect.provide(CredentialsConfig));
|
|
57
|
+
const isUUID = Schema.is(Schema.String.check(Schema.isUUID()));
|
|
58
|
+
const text = (value) => Redacted.isRedacted(value) ? Redacted.value(value) : value;
|
|
59
|
+
/** Download the selected secrets (plus imports) as a flat env map. */
|
|
60
|
+
const downloadSecrets = Effect.fn("downloadInfisicalSecrets")(function* (options, credentials) {
|
|
61
|
+
const includeImports = options.includeImports ?? true;
|
|
62
|
+
const response = yield* Effect.gen(function* () {
|
|
63
|
+
// The v4 secrets API only accepts project ids, so resolve slugs first.
|
|
64
|
+
const projectId = isUUID(options.project)
|
|
65
|
+
? options.project
|
|
66
|
+
: (yield* getProjectBySlug({ slug: options.project })).id;
|
|
67
|
+
return yield* listSecretsV4({
|
|
68
|
+
projectId,
|
|
69
|
+
environment: options.environment,
|
|
70
|
+
secretPath: options.path,
|
|
71
|
+
recursive: options.recursive,
|
|
72
|
+
includeImports,
|
|
73
|
+
viewSecretValue: true,
|
|
74
|
+
expandSecretReferences: true,
|
|
75
|
+
});
|
|
76
|
+
}).pipe(Retry.none, Effect.provide(fromApiKey({
|
|
77
|
+
apiKey: credentials.token,
|
|
78
|
+
apiBaseUrl: credentials.apiBaseUrl,
|
|
79
|
+
})), Effect.timeout("30 seconds"), Effect.mapError((cause) => new InfisicalSecretsError({ selection: options, credentials, cause })));
|
|
80
|
+
// Imports first so a directly defined secret overwrites an imported one.
|
|
81
|
+
const env = {};
|
|
82
|
+
for (const imported of response.imports ?? []) {
|
|
83
|
+
for (const secret of imported.secrets) {
|
|
84
|
+
env[text(secret.secretKey)] = secret.secretValue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const secret of response.secrets) {
|
|
88
|
+
env[text(secret.secretKey)] = secret.secretValue;
|
|
89
|
+
}
|
|
90
|
+
return env;
|
|
91
|
+
});
|
|
92
|
+
/** A `secrets` entry that loads an Infisical environment. */
|
|
93
|
+
export class InfisicalSecretsProvider extends Data.TaggedClass("alchemy/SecretProvider::Infisical") {
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Load Infisical secrets into Effect Config without touching `process.env`.
|
|
97
|
+
*
|
|
98
|
+
* Authenticate locally with `alchemy profile edit --add Infisical` and
|
|
99
|
+
* paste a machine identity's universal-auth client id and secret; Alchemy
|
|
100
|
+
* mints a fresh access token on every run. The client secret must remain valid. In
|
|
101
|
+
* CI, set `INFISICAL_TOKEN`, or `INFISICAL_IDENTITY_ID` to log in with the
|
|
102
|
+
* platform's OIDC token.
|
|
103
|
+
*
|
|
104
|
+
* ```ts
|
|
105
|
+
* secrets: [
|
|
106
|
+
* Infisical.Secrets(({ stage }) => ({
|
|
107
|
+
* project: "my-app",
|
|
108
|
+
* environment: stage === "prod" ? "prod" : "dev",
|
|
109
|
+
* })),
|
|
110
|
+
* ]
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export const Secrets = (options) => new InfisicalSecretsProvider({
|
|
114
|
+
layer: ConfigProvider.layerAdd(Effect.gen(function* () {
|
|
115
|
+
// Auth-provider discovery builds stack layers just to find out which
|
|
116
|
+
// providers are used. It must work offline and with broken
|
|
117
|
+
// credentials, so the user can configure the very identity this
|
|
118
|
+
// layer needs.
|
|
119
|
+
if (yield* SuppressMissingProviderConfig)
|
|
120
|
+
return ConfigProvider.fromEnv({ env: {} });
|
|
121
|
+
const resolved = yield* resolveSecretsOption(options);
|
|
122
|
+
const credentials = yield* resolveCredentials;
|
|
123
|
+
const env = yield* downloadSecrets(resolved, credentials);
|
|
124
|
+
yield* logLoadedKeys(`Infisical (${describeSelection(resolved)})`, Object.keys(env));
|
|
125
|
+
return ConfigProvider.fromEnv({ env, preserveEmptyStrings: true });
|
|
126
|
+
}), { asPrimary: true }),
|
|
127
|
+
});
|
|
128
|
+
//# sourceMappingURL=SecretsProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecretsProvider.js","sourceRoot":"","sources":["../src/SecretsProvider.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { InfisicalAuth, InfisicalAuthConfigSchema, INFISICAL_API_URL_ENV, INFISICAL_IDENTITY_ID_ENV, INFISICAL_OIDC_AUDIENCE_ENV, INFISICAL_OIDC_TOKEN_ENV, INFISICAL_TOKEN_ENV, mintAccessToken, mintAccessTokenFromOidc, type InfisicalAuthConfig, type InfisicalResolvedCredentials, } from "./AuthProvider.ts";
|
|
2
|
+
export { Secrets, InfisicalSecretsError, InfisicalSecretsProvider, type InfisicalOptions, } from "./SecretsProvider.ts";
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,yBAAyB,EACzB,qBAAqB,EACrB,yBAAyB,EACzB,2BAA2B,EAC3B,wBAAwB,EACxB,mBAAmB,EACnB,eAAe,EACf,uBAAuB,EACvB,KAAK,mBAAmB,EACxB,KAAK,4BAA4B,GAClC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,OAAO,EACP,qBAAqB,EACrB,wBAAwB,EACxB,KAAK,gBAAgB,GACtB,MAAM,sBAAsB,CAAC"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { InfisicalAuth, InfisicalAuthConfigSchema, INFISICAL_API_URL_ENV, INFISICAL_IDENTITY_ID_ENV, INFISICAL_OIDC_AUDIENCE_ENV, INFISICAL_OIDC_TOKEN_ENV, INFISICAL_TOKEN_ENV, mintAccessToken, mintAccessTokenFromOidc, } from "./AuthProvider.js";
|
|
2
|
+
export { Secrets, InfisicalSecretsError, InfisicalSecretsProvider, } from "./SecretsProvider.js";
|
|
3
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,yBAAyB,EACzB,qBAAqB,EACrB,yBAAyB,EACzB,2BAA2B,EAC3B,wBAAwB,EACxB,mBAAmB,EACnB,eAAe,EACf,uBAAuB,GAGxB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,OAAO,EACP,qBAAqB,EACrB,wBAAwB,GAEzB,MAAM,sBAAsB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alchemy.run/infisical",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "2.0.0-beta.84",
|
|
4
4
|
"description": "Infisical secrets and auth for Alchemy.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"alchemy",
|
|
@@ -9,12 +9,61 @@
|
|
|
9
9
|
"infrastructure-as-code",
|
|
10
10
|
"secrets"
|
|
11
11
|
],
|
|
12
|
+
"homepage": "https://alchemy.run",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/alchemy-run/alchemy/issues"
|
|
15
|
+
},
|
|
12
16
|
"license": "Apache-2.0",
|
|
13
17
|
"author": "Sam Goodwin <sam@alchemy.run>",
|
|
14
|
-
"homepage": "https://alchemy.run",
|
|
15
18
|
"repository": {
|
|
16
19
|
"type": "git",
|
|
17
20
|
"url": "git+https://github.com/alchemy-run/alchemy.git",
|
|
18
21
|
"directory": "packages/infisical"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"lib",
|
|
25
|
+
"src"
|
|
26
|
+
],
|
|
27
|
+
"type": "module",
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./lib/index.d.ts",
|
|
32
|
+
"bun": "./src/index.ts",
|
|
33
|
+
"worker": "./src/index.ts",
|
|
34
|
+
"default": "./lib/index.js"
|
|
35
|
+
},
|
|
36
|
+
"./*": {
|
|
37
|
+
"types": "./lib/*.d.ts",
|
|
38
|
+
"bun": "./src/*.ts",
|
|
39
|
+
"worker": "./src/*.ts",
|
|
40
|
+
"default": "./lib/*.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@distilled.cloud/infisical": "1.0.0-rc.17"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/bun": "1.4.3",
|
|
52
|
+
"alchemy": "2.0.0-beta.84",
|
|
53
|
+
"effect": "^4.0.5",
|
|
54
|
+
"typescript": "^7.0.2"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"alchemy": "2.0.0-beta.84",
|
|
58
|
+
"effect": "^4.0.5"
|
|
59
|
+
},
|
|
60
|
+
"alchemy": {
|
|
61
|
+
"contributes": [
|
|
62
|
+
"@alchemy.run/infisical/Contributions"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
"scripts": {
|
|
66
|
+
"build": "pnpm -r --filter \"{.}...\" run build:package",
|
|
67
|
+
"build:package": "node scripts/build.ts"
|
|
19
68
|
}
|
|
20
|
-
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import {
|
|
2
|
+
anonymous,
|
|
3
|
+
DEFAULT_API_BASE_URL,
|
|
4
|
+
loginWithOidcAuth,
|
|
5
|
+
loginWithUniversalAuth,
|
|
6
|
+
} from "@distilled.cloud/infisical";
|
|
7
|
+
import * as Retry from "@distilled.cloud/infisical/Retry";
|
|
8
|
+
import { AuthError, AuthProviderLayer, type ConfigureField } from "alchemy/Auth/AuthProvider";
|
|
9
|
+
import { displayRedacted } from "alchemy/Auth/Credentials";
|
|
10
|
+
import { getEnv, getEnvRedacted, mapPromptCancellation } from "alchemy/Auth/Env";
|
|
11
|
+
import { detectOidcToken, SUPPORTED_OIDC_PLATFORMS } from "alchemy/Auth/OidcToken";
|
|
12
|
+
import {
|
|
13
|
+
collectFieldValues,
|
|
14
|
+
storedValueText,
|
|
15
|
+
validateFieldValues,
|
|
16
|
+
type StoredValues,
|
|
17
|
+
} from "alchemy/Auth/StoredAuthProvider";
|
|
18
|
+
import * as Interaction from "alchemy/Interaction";
|
|
19
|
+
import * as Duration from "effect/Duration";
|
|
20
|
+
import * as Effect from "effect/Effect";
|
|
21
|
+
import type * as HttpClient from "effect/http/HttpClient";
|
|
22
|
+
import * as Redacted from "effect/Redacted";
|
|
23
|
+
import * as Schema from "effect/Schema";
|
|
24
|
+
|
|
25
|
+
export const INFISICAL_TOKEN_ENV = "INFISICAL_TOKEN";
|
|
26
|
+
/** The machine identity to log into with a platform OIDC token. */
|
|
27
|
+
export const INFISICAL_IDENTITY_ID_ENV = "INFISICAL_IDENTITY_ID";
|
|
28
|
+
/** Same name the Infisical CLI uses for self-hosted instances. */
|
|
29
|
+
export const INFISICAL_API_URL_ENV = "INFISICAL_API_URL";
|
|
30
|
+
/** Explicit OIDC token for platforms that are not auto-detected. */
|
|
31
|
+
export const INFISICAL_OIDC_TOKEN_ENV = "INFISICAL_OIDC_TOKEN";
|
|
32
|
+
/** Optional audience to request in the platform's OIDC token. */
|
|
33
|
+
export const INFISICAL_OIDC_AUDIENCE_ENV = "INFISICAL_OIDC_AUDIENCE";
|
|
34
|
+
|
|
35
|
+
const PROVIDER_NAME = "Infisical";
|
|
36
|
+
const API_TIMEOUT = Duration.seconds(30);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A machine identity's client id + secret. The secret never expires unless
|
|
40
|
+
* given a TTL in Infisical, and Alchemy exchanges it for a short-lived
|
|
41
|
+
* access token on every read, so this is the method for local development.
|
|
42
|
+
*/
|
|
43
|
+
const UniversalAuthConfig = Schema.Struct({
|
|
44
|
+
method: Schema.Literal("universal-auth"),
|
|
45
|
+
clientId: Schema.NonEmptyString,
|
|
46
|
+
clientSecret: Schema.NonEmptyString,
|
|
47
|
+
apiBaseUrl: Schema.optional(Schema.String),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/** A user token from the dashboard's "Copy Token" menu item. Valid for 10 days. */
|
|
51
|
+
const AccessTokenConfig = Schema.Struct({
|
|
52
|
+
method: Schema.Literal("access-token"),
|
|
53
|
+
token: Schema.NonEmptyString,
|
|
54
|
+
apiBaseUrl: Schema.optional(Schema.String),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const InfisicalAuthConfigSchema = Schema.Union([UniversalAuthConfig, AccessTokenConfig]);
|
|
58
|
+
export type InfisicalAuthConfig = typeof InfisicalAuthConfigSchema.Type;
|
|
59
|
+
|
|
60
|
+
export interface InfisicalResolvedCredentials {
|
|
61
|
+
readonly token: Redacted.Redacted<string>;
|
|
62
|
+
readonly apiBaseUrl: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const apiBaseUrlField: ConfigureField = {
|
|
66
|
+
name: "apiBaseUrl",
|
|
67
|
+
label: "Infisical API URL",
|
|
68
|
+
description: "For the EU cloud (https://eu.infisical.com) or a self-hosted instance.",
|
|
69
|
+
placeholder: DEFAULT_API_BASE_URL,
|
|
70
|
+
optional: true,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const universalAuthFields: ReadonlyArray<ConfigureField> = [
|
|
74
|
+
{
|
|
75
|
+
name: "clientId",
|
|
76
|
+
label: "Machine identity client ID",
|
|
77
|
+
description:
|
|
78
|
+
"From the machine identity's Universal Auth settings in Infisical. See https://infisical.com/docs/documentation/platform/identities/universal-auth",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "clientSecret",
|
|
82
|
+
label: "Machine identity client secret",
|
|
83
|
+
secret: true,
|
|
84
|
+
},
|
|
85
|
+
apiBaseUrlField,
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const accessTokenFields: ReadonlyArray<ConfigureField> = [
|
|
89
|
+
{
|
|
90
|
+
name: "token",
|
|
91
|
+
label: "Infisical user token",
|
|
92
|
+
description:
|
|
93
|
+
'From the account menu in the Infisical dashboard ("Copy Token"). Valid for 10 days.',
|
|
94
|
+
secret: true,
|
|
95
|
+
},
|
|
96
|
+
apiBaseUrlField,
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
const toCredentials = (
|
|
100
|
+
response: { readonly accessToken: string | Redacted.Redacted<string> },
|
|
101
|
+
apiBaseUrl: string | undefined,
|
|
102
|
+
): InfisicalResolvedCredentials => ({
|
|
103
|
+
token: Redacted.isRedacted(response.accessToken)
|
|
104
|
+
? response.accessToken
|
|
105
|
+
: Redacted.make(response.accessToken),
|
|
106
|
+
apiBaseUrl: apiBaseUrl ?? DEFAULT_API_BASE_URL,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Exchange a machine identity's client credentials for an access token.
|
|
111
|
+
* Infisical answers 401 when either half is wrong or the secret is spent.
|
|
112
|
+
*/
|
|
113
|
+
export const mintAccessToken = (config: {
|
|
114
|
+
readonly clientId: string;
|
|
115
|
+
readonly clientSecret: string;
|
|
116
|
+
readonly apiBaseUrl?: string;
|
|
117
|
+
}) =>
|
|
118
|
+
loginWithUniversalAuth({
|
|
119
|
+
clientId: config.clientId,
|
|
120
|
+
clientSecret: Redacted.make(config.clientSecret),
|
|
121
|
+
}).pipe(
|
|
122
|
+
Retry.none,
|
|
123
|
+
Effect.provide(anonymous({ apiBaseUrl: config.apiBaseUrl })),
|
|
124
|
+
Effect.timeout(API_TIMEOUT),
|
|
125
|
+
Effect.map((response) => toCredentials(response, config.apiBaseUrl)),
|
|
126
|
+
Effect.mapError((cause) =>
|
|
127
|
+
AuthError.make({
|
|
128
|
+
message:
|
|
129
|
+
"Infisical rejected the machine identity credentials. Check the client ID and secret, and that the secret has not expired or hit its use limit.",
|
|
130
|
+
cause,
|
|
131
|
+
}),
|
|
132
|
+
),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Exchange a platform-issued OIDC token for an access token of the machine
|
|
137
|
+
* identity whose OIDC auth trusts that platform's issuer.
|
|
138
|
+
*/
|
|
139
|
+
export const mintAccessTokenFromOidc = (config: {
|
|
140
|
+
readonly identityId: string;
|
|
141
|
+
readonly jwt: Redacted.Redacted<string>;
|
|
142
|
+
readonly apiBaseUrl?: string;
|
|
143
|
+
}) =>
|
|
144
|
+
loginWithOidcAuth({
|
|
145
|
+
identityId: config.identityId,
|
|
146
|
+
jwt: Redacted.value(config.jwt),
|
|
147
|
+
}).pipe(
|
|
148
|
+
Retry.none,
|
|
149
|
+
Effect.provide(anonymous({ apiBaseUrl: config.apiBaseUrl })),
|
|
150
|
+
Effect.timeout(API_TIMEOUT),
|
|
151
|
+
Effect.map((response) => toCredentials(response, config.apiBaseUrl)),
|
|
152
|
+
Effect.mapError((cause) =>
|
|
153
|
+
AuthError.make({
|
|
154
|
+
message: `Infisical rejected the OIDC login for identity '${config.identityId}': ${cause.message}. Check that the identity's OIDC auth trusts this platform's issuer, subject, and audience.`,
|
|
155
|
+
cause,
|
|
156
|
+
}),
|
|
157
|
+
),
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Resolve the token a stored configuration grants. Annotated because the two
|
|
162
|
+
* branches are differently shaped Effects and a union of Effects does not
|
|
163
|
+
* flow through `flatMap`.
|
|
164
|
+
*/
|
|
165
|
+
const resolve = (
|
|
166
|
+
config: InfisicalAuthConfig,
|
|
167
|
+
): Effect.Effect<InfisicalResolvedCredentials, AuthError, HttpClient.HttpClient> =>
|
|
168
|
+
config.method === "universal-auth"
|
|
169
|
+
? mintAccessToken(config)
|
|
170
|
+
: Effect.succeed({
|
|
171
|
+
token: Redacted.make(config.token),
|
|
172
|
+
apiBaseUrl: config.apiBaseUrl ?? DEFAULT_API_BASE_URL,
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
/** Turn collected field values into a stored configuration. */
|
|
176
|
+
const toConfig = (
|
|
177
|
+
method: InfisicalAuthConfig["method"],
|
|
178
|
+
values: StoredValues,
|
|
179
|
+
): InfisicalAuthConfig => {
|
|
180
|
+
const apiBaseUrl = storedValueText(values.apiBaseUrl);
|
|
181
|
+
return method === "access-token"
|
|
182
|
+
? { method, token: storedValueText(values.token)!, apiBaseUrl }
|
|
183
|
+
: {
|
|
184
|
+
method,
|
|
185
|
+
clientId: storedValueText(values.clientId)!,
|
|
186
|
+
clientSecret: storedValueText(values.clientSecret)!,
|
|
187
|
+
apiBaseUrl,
|
|
188
|
+
};
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Verify Universal Auth with a login before storing it. Pasted access
|
|
193
|
+
* tokens are used as supplied and checked when secrets are requested.
|
|
194
|
+
*/
|
|
195
|
+
const verify = (config: InfisicalAuthConfig) => resolve(config).pipe(Effect.as(config));
|
|
196
|
+
|
|
197
|
+
const fieldsFor = (method: InfisicalAuthConfig["method"]) =>
|
|
198
|
+
method === "universal-auth" ? universalAuthFields : accessTokenFields;
|
|
199
|
+
|
|
200
|
+
const chooseMethod = Interaction.accessors.prompt
|
|
201
|
+
.select({
|
|
202
|
+
message: "Infisical authentication method",
|
|
203
|
+
options: [
|
|
204
|
+
{
|
|
205
|
+
value: "universal-auth" as const,
|
|
206
|
+
label: "Machine identity",
|
|
207
|
+
description: "Client ID and secret; mints a fresh access token on each run",
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
value: "access-token" as const,
|
|
211
|
+
label: "User token",
|
|
212
|
+
description: "Copied from the dashboard; valid for 10 days",
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
})
|
|
216
|
+
.pipe(mapPromptCancellation);
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* How CI authenticates, decided from the environment: a ready token, or a
|
|
220
|
+
* machine identity to log into with the platform's OIDC token. Environment
|
|
221
|
+
* credentials take precedence over stored profiles, including outside CI.
|
|
222
|
+
*/
|
|
223
|
+
const readEnvironment = Effect.gen(function* () {
|
|
224
|
+
const apiBaseUrl = yield* getEnv(INFISICAL_API_URL_ENV);
|
|
225
|
+
const token = yield* getEnvRedacted(INFISICAL_TOKEN_ENV);
|
|
226
|
+
if (token !== undefined && Redacted.value(token).length > 0) {
|
|
227
|
+
return { token, apiBaseUrl: apiBaseUrl ?? DEFAULT_API_BASE_URL };
|
|
228
|
+
}
|
|
229
|
+
const identityId = yield* getEnv(INFISICAL_IDENTITY_ID_ENV);
|
|
230
|
+
if (!identityId) {
|
|
231
|
+
return yield* AuthError.make({
|
|
232
|
+
message: `Infisical credentials are missing. In CI set ${INFISICAL_TOKEN_ENV}, or set ${INFISICAL_IDENTITY_ID_ENV} to log in with the platform's OIDC token; locally run \`alchemy profile edit --add Infisical\`.`,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
const oidc = yield* detectOidcToken({
|
|
236
|
+
token: INFISICAL_OIDC_TOKEN_ENV,
|
|
237
|
+
audience: INFISICAL_OIDC_AUDIENCE_ENV,
|
|
238
|
+
});
|
|
239
|
+
if (oidc === undefined) {
|
|
240
|
+
return yield* AuthError.make({
|
|
241
|
+
message: `${INFISICAL_IDENTITY_ID_ENV} is set but no platform OIDC token was found. Supported: ${SUPPORTED_OIDC_PLATFORMS}; elsewhere pass the token in ${INFISICAL_OIDC_TOKEN_ENV}.`,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return yield* mintAccessTokenFromOidc({
|
|
245
|
+
identityId,
|
|
246
|
+
jwt: oidc.token,
|
|
247
|
+
apiBaseUrl,
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Infisical profile authentication: a machine identity's universal-auth
|
|
253
|
+
* credentials (recommended) or a pasted access token.
|
|
254
|
+
*/
|
|
255
|
+
export const InfisicalAuth = AuthProviderLayer<InfisicalAuthConfig, InfisicalResolvedCredentials>()(
|
|
256
|
+
PROVIDER_NAME,
|
|
257
|
+
{
|
|
258
|
+
configSchema: InfisicalAuthConfigSchema,
|
|
259
|
+
configure: () =>
|
|
260
|
+
chooseMethod.pipe(
|
|
261
|
+
Effect.flatMap((method) =>
|
|
262
|
+
collectFieldValues(fieldsFor(method)).pipe(
|
|
263
|
+
Effect.map((values) => toConfig(method, values)),
|
|
264
|
+
),
|
|
265
|
+
),
|
|
266
|
+
Effect.flatMap(verify),
|
|
267
|
+
),
|
|
268
|
+
configureMethods: [
|
|
269
|
+
{ method: "universal-auth", fields: universalAuthFields },
|
|
270
|
+
{ method: "access-token", fields: accessTokenFields },
|
|
271
|
+
],
|
|
272
|
+
configureWith: (_, input) => {
|
|
273
|
+
if (input.method !== "universal-auth" && input.method !== "access-token") {
|
|
274
|
+
return Effect.fail(
|
|
275
|
+
AuthError.make({
|
|
276
|
+
message: `Infisical: unknown method '${input.method}'. Valid methods: universal-auth, access-token.`,
|
|
277
|
+
}),
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
const method = input.method;
|
|
281
|
+
return validateFieldValues(PROVIDER_NAME, fieldsFor(method), input.values).pipe(
|
|
282
|
+
Effect.map((values) => toConfig(method, values)),
|
|
283
|
+
Effect.flatMap(verify),
|
|
284
|
+
);
|
|
285
|
+
},
|
|
286
|
+
// Nothing to re-authenticate: stored credentials are long-lived (or, for
|
|
287
|
+
// access tokens, replaced by reconfiguring). Verifying them is enough.
|
|
288
|
+
login: (_, config) => verify(config),
|
|
289
|
+
// Client secrets are revoked from the Infisical dashboard by an admin;
|
|
290
|
+
// there is nothing session-like to tear down.
|
|
291
|
+
logout: () => Effect.void,
|
|
292
|
+
read: (_, config) => resolve(config),
|
|
293
|
+
details: (_, config) =>
|
|
294
|
+
Effect.succeed({
|
|
295
|
+
lines: [
|
|
296
|
+
{ key: "method", value: config.method },
|
|
297
|
+
config.method === "universal-auth"
|
|
298
|
+
? { key: "clientId", value: config.clientId }
|
|
299
|
+
: {
|
|
300
|
+
key: "token",
|
|
301
|
+
value: displayRedacted(Redacted.make(config.token)),
|
|
302
|
+
},
|
|
303
|
+
{ key: "apiBaseUrl", value: config.apiBaseUrl ?? DEFAULT_API_BASE_URL },
|
|
304
|
+
],
|
|
305
|
+
}),
|
|
306
|
+
readEnvironment,
|
|
307
|
+
environment: [
|
|
308
|
+
{
|
|
309
|
+
name: INFISICAL_TOKEN_ENV,
|
|
310
|
+
required: true,
|
|
311
|
+
secret: true,
|
|
312
|
+
alternatives: [INFISICAL_IDENTITY_ID_ENV],
|
|
313
|
+
description:
|
|
314
|
+
"A machine identity access token; or set INFISICAL_IDENTITY_ID instead to log in with the platform's OIDC token (Vercel, GitHub Actions, GitLab CI, GCP)",
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: INFISICAL_OIDC_TOKEN_ENV,
|
|
318
|
+
required: false,
|
|
319
|
+
secret: true,
|
|
320
|
+
description: "Explicit OIDC token for platforms that are not auto-detected",
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: INFISICAL_OIDC_AUDIENCE_ENV,
|
|
324
|
+
required: false,
|
|
325
|
+
description: "Audience to request in the platform's OIDC token",
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
name: INFISICAL_API_URL_ENV,
|
|
329
|
+
required: false,
|
|
330
|
+
description: "Base URL of a self-hosted Infisical instance",
|
|
331
|
+
},
|
|
332
|
+
],
|
|
333
|
+
},
|
|
334
|
+
);
|