Repository navigation
Expand file tree
/
Copy pathsourceControlHostSecrets.ts
More file actions
144 lines (130 loc) · 5.43 KB
/
Copy pathsourceControlHostSecrets.ts
File metadata and controls
144 lines (130 loc) · 5.43 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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import {
isSecretReference,
secretReferencesInSettings,
secretSettingsFields,
type SecretReference,
type ServerSettings,
} from "@t3tools/contracts";
import type * as SecretResolver from "@t3tools/secret-provider-core/server/SecretResolver";
import * as Effect from "effect/Effect";
import { resolveSettingsSecrets } from "../secrets/settingsSecretValue.ts";
import { sourceControlDefinitions } from "./sourceControlDefinitions.ts";
export const SOURCE_CONTROL_HOST_SECRET_FIELDS: ReadonlyMap<
string,
ReadonlyArray<string>
> = new Map(
sourceControlDefinitions.definitions.flatMap((definition) => {
const fields = definition.settings ? secretSettingsFields(definition.settings) : [];
return fields.length > 0 ? [[definition.kind, fields] as const] : [];
}),
);
export interface SourceControlHostSecretSlot {
readonly kind: string;
readonly field: string;
readonly serverHost: string | null;
}
/** The secret store entry for one slot; names stay stable across releases. */
export function sourceControlHostSecretName(slot: SourceControlHostSecretSlot): string {
const encode = (value: string) => Buffer.from(value, "utf8").toString("base64url");
return slot.serverHost === null
? `source-control-${encode(slot.kind)}-${encode(slot.field)}`
: `source-control-${encode(slot.kind)}-${encode(slot.field)}-${encode(slot.serverHost.trim().toLowerCase())}`;
}
export function readSecretFieldValues(
value: unknown,
): ReadonlyArray<readonly [serverHost: string | null, value: string]> {
if (typeof value === "string") return [[null, value]];
if (isSecretReference(value)) return [];
if (value === null || typeof value !== "object" || Array.isArray(value)) return [];
return Object.entries(value).flatMap(([serverHost, entry]) =>
typeof entry === "string" ? [[serverHost.trim().toLowerCase(), entry] as const] : [],
);
}
export function writeSecretFieldValues(
original: unknown,
values: ReadonlyArray<readonly [serverHost: string | null, value: string]>,
): unknown {
if (typeof original === "string") return values[0]?.[1] ?? "";
if (isSecretReference(original)) return original;
const sources =
original !== null && typeof original === "object" && !Array.isArray(original)
? Object.entries(original).flatMap(([serverHost, entry]) =>
isSecretReference(entry) ? [[serverHost.trim().toLowerCase(), entry] as const] : [],
)
: [];
return Object.fromEntries([
...sources,
...values.map(([serverHost, value]) => [serverHost ?? "", value] as const),
]);
}
type SourceControlHostsSettings = Readonly<Record<string, Readonly<Record<string, unknown>>>>;
const hostSettingsSchemas = new Map(
sourceControlDefinitions.definitions.flatMap((definition) =>
definition.settings ? [[definition.kind as string, definition.settings] as const] : [],
),
);
/** Every secret provider reference held in any host kind's credential fields. */
export function collectSourceControlHostSecretReferences(
hosts: SourceControlHostsSettings,
): ReadonlyArray<SecretReference> {
return [...hostSettingsSchemas].flatMap(([kind, schema]) =>
secretReferencesInSettings(schema, hosts[kind]).map(({ reference }) => reference),
);
}
/** Replaces each secret provider reference in one host kind's credential fields with the secret it names. */
export const resolveSourceControlHostSecrets = Effect.fn("SourceControlHost.resolveSecrets")(
function* (
resolver: SecretResolver.SecretResolver["Service"],
hosts: SourceControlHostsSettings,
kind: string,
) {
const schema = hostSettingsSchemas.get(kind);
const fields = hosts[kind];
if (schema === undefined || fields === undefined) return hosts;
const resolved = yield* resolveSettingsSecrets(resolver, schema, fields);
return resolved === fields ? hosts : { ...hosts, [kind]: resolved };
},
);
const collectUsageLimitSecretReferences = (
sources: ServerSettings["usageLimitSources"],
): ReadonlyArray<SecretReference> =>
Object.values(sources).flatMap(({ managementKey }) =>
isSecretReference(managementKey) ? [managementKey] : [],
);
const collectDeviceHostSecretReferences = (
settings: Pick<ServerSettings, "easDeviceHosts" | "simlockDeviceHosts">,
): ReadonlyArray<SecretReference> =>
[
...settings.easDeviceHosts.map(({ accessToken }) => accessToken),
...settings.simlockDeviceHosts.map(({ agentToken }) => agentToken),
].filter(isSecretReference);
/** Settings that cannot be read clear every cached secret, so a refresh never keeps a stale one. */
export const invalidateSettingsSecrets = Effect.fn("SettingsSecrets.invalidate")(function* <E>(
resolver: SecretResolver.SecretResolver["Service"],
settings: Effect.Effect<
Pick<
ServerSettings,
| "sourceControlHosts"
| "usageLimitSources"
| "textGenerators"
| "easDeviceHosts"
| "simlockDeviceHosts"
| "cloudSpeechCredentials"
>,
E
>,
) {
const references = yield* settings.pipe(
Effect.map((current) => [
...collectSourceControlHostSecretReferences(current.sourceControlHosts),
...collectUsageLimitSecretReferences(current.usageLimitSources),
...Object.values(current.textGenerators).flatMap(({ apiKey }) =>
isSecretReference(apiKey) ? [apiKey] : [],
),
...collectDeviceHostSecretReferences(current),
...Object.values(current.cloudSpeechCredentials).filter(isSecretReference),
]),
Effect.orElseSucceed(() => undefined),
);
yield* resolver.invalidate(references);
});