Repository navigation
Expand file tree
/
Copy pathProviderSecretReference.ts
More file actions
109 lines (103 loc) · 3.91 KB
/
Copy pathProviderSecretReference.ts
File metadata and controls
109 lines (103 loc) · 3.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
/**
* Provider environment values that name a secret instead of carrying one.
*
* A user who keeps a provider credential in a secret store configures the
* variable with a secret source instead of the secret itself: either a named
* secret provider from `settings.secretProviders`
* (`{ kind: "secret-provider", provider, reference }`) or the older inline
* 1Password source (`{ kind: "1password", reference, account }`).
* `ProviderSecretResolver` swaps the source for the real value on the way into
* the provider process, so the credential never lands in `settings.json` or
* the on-disk secret store, and rotating it in the store rotates it here.
*
* These helpers are pure so the instance registry can ask "does this
* environment read from a secret store?" without depending on the resolver.
*
* @module provider/ProviderSecretReference
*/
import type {
OnePasswordAccount,
ProviderInstanceEnvironment,
ProviderInstanceEnvironmentVariable,
SecretProviderId,
} from "@t3tools/contracts";
import * as Data from "effect/Data";
import * as Equal from "effect/Equal";
/**
* One secret to read from 1Password. Structurally comparable, so it doubles as
* the resolver's cache key: the same reference in two accounts is two secrets.
*/
export class OnePasswordSecretReference extends Data.TaggedClass("1password")<{
readonly reference: string;
readonly account: OnePasswordAccount;
}> {}
/**
* One secret to read through a named provider. The provider id is part of the
* cache key, so the same reference behind two providers is two secrets. What
* the reference means depends on the provider's kind, which is looked up when
* the secret is read.
*/
export class NamedProviderSecretReference extends Data.TaggedClass("secret-provider")<{
readonly provider: SecretProviderId;
readonly reference: string;
}> {}
/** Every secret a provider environment value can name. */
export type ProviderSecretReference = OnePasswordSecretReference | NamedProviderSecretReference;
/**
* The secret an environment value names, or `undefined` when the value is a
* literal.
*/
export function providerSecretReference(
value: ProviderInstanceEnvironmentVariable["value"],
): ProviderSecretReference | undefined {
if (typeof value === "string") {
return undefined;
}
switch (value.kind) {
case "1password":
return new OnePasswordSecretReference({
reference: value.reference,
account: value.account,
});
case "secret-provider":
return new NamedProviderSecretReference({
provider: value.provider,
reference: value.reference,
});
}
}
/**
* Whether any variable in the environment reads from a secret store. Drives
* the "rebuild this instance on refresh" decision, so instances configured
* entirely with literals keep the process they already have.
*/
export function hasProviderSecretReference(
environment: ProviderInstanceEnvironment | undefined,
): boolean {
return (
environment?.some((variable) => providerSecretReference(variable.value) !== undefined) ?? false
);
}
/**
* Every distinct secret across a set of environments, in the order they were
* first seen.
*
* Resolution is per secret but unlocking is per `op` invocation, so the caller
* that is about to build many instances wants the whole list up front: one
* call covering every secret costs one authorization, where one call per
* instance costs one each.
*/
export function collectProviderSecretReferences(
environments: Iterable<ProviderInstanceEnvironment | undefined>,
): ReadonlyArray<ProviderSecretReference> {
const references: Array<ProviderSecretReference> = [];
for (const environment of environments) {
for (const variable of environment ?? []) {
const reference = providerSecretReference(variable.value);
if (reference !== undefined && !references.some((seen) => Equal.equals(seen, reference))) {
references.push(reference);
}
}
}
return references;
}