Skip to content

Commit 9667a14

Browse files
committed
wip: working on applying schema before requesting
[ci skip]
1 parent b3c6f90 commit 9667a14

File tree

1 file changed

+89
-32
lines changed

1 file changed

+89
-32
lines changed

src/secrets/CommandEnv.ts

Lines changed: 89 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import type PolykeyClient from 'polykey/PolykeyClient.js';
2-
import type { JSONSchema, ParsedSecretPathValue } from '../types.js';
2+
import type {
3+
JSONSchema,
4+
JSONSchemaInfo,
5+
ParsedSecretPathValue,
6+
} from '../types.js';
37
import path from 'node:path';
48
import os from 'node:os';
59
import $RefParser from '@apidevtools/json-schema-ref-parser';
610
import { Ajv2019 as Ajv } from 'ajv/dist/2019.js';
711
import { InvalidArgumentError } from 'commander';
8-
import * as utils from 'polykey/utils/index.js';
912
import CommandPolykey from '../CommandPolykey.js';
1013
import * as binProcessors from '../utils/processors.js';
1114
import * as binUtils from '../utils/index.js';
@@ -37,6 +40,7 @@ class CommandEnv extends CommandPolykey {
3740
const { default: PolykeyClient } = await import(
3841
'polykey/PolykeyClient.js'
3942
);
43+
const utils = await import('polykey/utils/index.js');
4044
const {
4145
envInvalid,
4246
envDuplicate,
@@ -122,27 +126,66 @@ class CommandEnv extends CommandPolykey {
122126
logger: this.logger.getChild(PolykeyClient.name),
123127
});
124128

129+
let schema: JSONSchema | undefined = undefined;
130+
let unwrappedSchema: JSONSchemaInfo | undefined = undefined;
131+
if (options.egressSchema != null) {
132+
schema = (await $RefParser.bundle(
133+
options.egressSchema,
134+
)) satisfies JSONSchema;
135+
unwrappedSchema = binUtils.loadSchema(schema!);
136+
}
137+
125138
// Getting envs
126139
const [envp] = await binUtils.retryAuthentication(async (auth) => {
127140
const responseStream =
128141
await pkClient.rpcClient.methods.vaultsSecretsEnv();
142+
129143
// Writing desired secrets
130144
const secretRenameMap = new Map<string, string | undefined>();
131-
const writeP = (async () => {
132-
const writer = responseStream.writable.getWriter();
133-
let first = true;
134-
for (const envVariable of envVariables) {
135-
const [nameOrId, secretName, secretNameNew] = envVariable;
136-
secretRenameMap.set(secretName ?? '/', secretNameNew);
145+
const writer = responseStream.writable.getWriter();
146+
let first = true;
147+
for (const envVariable of envVariables) {
148+
const [nameOrId, secretName, secretNameNew] = envVariable;
149+
secretRenameMap.set(secretName ?? '/', secretNameNew);
150+
151+
// If there is no secret name provided, then attempt to export the
152+
// secrets from the entire vault. Otherwise, check if the selected
153+
// secret exists in the schema before requesting it. This will
154+
// only run if a schema has been specified.
155+
if (schema != null && unwrappedSchema != null) {
156+
const { allKeys } = unwrappedSchema;
157+
if (nameOrId != null && secretName == null) {
158+
// Only vault specified
159+
for (const key of allKeys) {
160+
// TODO: handle secret renames, allKeys key might not be the same in vault
161+
await writer.write({
162+
nameOrId: nameOrId,
163+
secretName: key,
164+
metadata: first ? auth : undefined,
165+
});
166+
}
167+
} else {
168+
// Individual secret name specified
169+
const name: string = secretNameNew != null ? secretNameNew : secretName!;
170+
if (allKeys.includes(name)) {
171+
await writer.write({
172+
nameOrId: nameOrId,
173+
secretName: name,
174+
metadata: first ? auth : undefined,
175+
});
176+
}
177+
}
178+
} else {
179+
// No schema specified
137180
await writer.write({
138181
nameOrId: nameOrId,
139182
secretName: secretName ?? '/',
140183
metadata: first ? auth : undefined,
141184
});
142-
first = false;
143185
}
144-
await writer.close();
145-
})();
186+
first = false;
187+
}
188+
await writer.close();
146189

147190
const envp: Record<string, string> = {};
148191
const envpPath: Record<
@@ -153,6 +196,34 @@ class CommandEnv extends CommandPolykey {
153196
}
154197
> = {};
155198
for await (const value of responseStream.readable) {
199+
if (value.type === 'ErrorMessage') {
200+
switch (value.code) {
201+
case 'EINVAL':
202+
// It is expected for the data to be populated with the offending
203+
// vault name if the vault was not found.
204+
process.stderr.write(
205+
binUtils.outputFormatterError(
206+
`Vault "${value.data?.nameOrId}" does not exist`,
207+
),
208+
);
209+
break;
210+
case 'ENOENT':
211+
// It is expected for the data to be populated with the offending
212+
// secret and vault name if a secret was not found.
213+
process.stderr.write(
214+
binUtils.outputFormatterError(
215+
`Secret "${value.data?.secretName}" does not exist in vault "${value.data?.nameOrId}"`,
216+
),
217+
);
218+
break;
219+
default:
220+
utils.never(
221+
`Expected code to be one of EINVAL, ENOENT, received ${value.code}`,
222+
);
223+
}
224+
continue;
225+
}
226+
156227
const { nameOrId, secretName, secretContent } = value;
157228
let newName = secretRenameMap.get(secretName);
158229
if (newName == null) {
@@ -229,30 +300,16 @@ class CommandEnv extends CommandPolykey {
229300
secretName,
230301
};
231302
}
232-
await writeP;
233303

234-
// Apply validation using the schema
235-
// TODO: filter before pulling instead of after
304+
// Apply defaults using the schema
236305
const filteredEnvp: Record<string, string> = {};
237-
if (options.egressSchema != null) {
238-
// Resolve references and bundle schema
239-
const schema: JSONSchema = await $RefParser.bundle(
240-
options.egressSchema,
241-
);
242-
243-
// Validate the incoming secrets against the schema
244-
const ajv = new Ajv({
245-
coerceTypes: true,
246-
useDefaults: false,
247-
allErrors: true,
248-
});
249-
const validate = ajv.compile(schema);
250-
validate(envp);
251-
252-
// Extract relevant keys, discarding the rest
253-
const { requiredKeys, allKeys, defaults } =
254-
binUtils.loadSchema(schema);
306+
if (unwrappedSchema != null) {
307+
// Parse the schema for manual filtering
308+
const { requiredKeys, allKeys, defaults } = unwrappedSchema;
255309

310+
// Add allowed secrets to a filtered set of secrets. This runs after
311+
// the duplication is processed, so all secrets here are guaranteed
312+
// to be unique.
256313
for (const key of allKeys) {
257314
let value = envp[key];
258315
if (value == null && defaults[key] != null) {

0 commit comments

Comments
 (0)