11import type PolykeyClient from 'polykey/PolykeyClient.js' ;
2- import type {
3- JSONSchema ,
4- JSONSchemaInfo ,
5- ParsedSecretPathValue ,
6- } from '../types.js' ;
2+ import type { ParsedSecretPathValue } from '../types.js' ;
73import path from 'node:path' ;
84import os from 'node:os' ;
95import $RefParser from '@apidevtools/json-schema-ref-parser' ;
10- import { Ajv2019 as Ajv } from 'ajv/dist/2019.js' ;
6+ import { Ajv2019 } from 'ajv/dist/2019.js' ;
117import { InvalidArgumentError } from 'commander' ;
128import CommandPolykey from '../CommandPolykey.js' ;
139import * as binProcessors from '../utils/processors.js' ;
@@ -126,65 +122,25 @@ class CommandEnv extends CommandPolykey {
126122 logger : this . logger . getChild ( PolykeyClient . name ) ,
127123 } ) ;
128124
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-
138125 // Getting envs
139126 const [ envp ] = await binUtils . retryAuthentication ( async ( auth ) => {
140127 const responseStream =
141128 await pkClient . rpcClient . methods . vaultsSecretsEnv ( ) ;
142129
143- // Writing desired secrets
130+ // Writing desired secrets. Attempt to get all the required secrets.
131+ // If the schema is provided, then the resulting variables will be
132+ // validated.
144133 const secretRenameMap = new Map < string , string | undefined > ( ) ;
145134 const writer = responseStream . writable . getWriter ( ) ;
146135 let first = true ;
147136 for ( const envVariable of envVariables ) {
148137 const [ nameOrId , secretName , secretNameNew ] = envVariable ;
149138 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- // When exporting secrets from a vault, it is impossible to
161- // rename the resulting secrets.
162- await writer . write ( {
163- nameOrId : nameOrId ,
164- secretName : key ,
165- metadata : first ? auth : undefined ,
166- } ) ;
167- }
168- } else {
169- // Individual secret name specified
170- const name : string =
171- secretNameNew != null ? secretNameNew : secretName ! ;
172- if ( allKeys . includes ( name ) ) {
173- await writer . write ( {
174- nameOrId : nameOrId ,
175- secretName : secretName ! ,
176- metadata : first ? auth : undefined ,
177- } ) ;
178- }
179- }
180- } else {
181- // No schema specified
182- await writer . write ( {
183- nameOrId : nameOrId ,
184- secretName : secretName ?? '/' ,
185- metadata : first ? auth : undefined ,
186- } ) ;
187- }
139+ await writer . write ( {
140+ nameOrId : nameOrId ,
141+ secretName : secretName ?? '/' ,
142+ metadata : first ? auth : undefined ,
143+ } ) ;
188144 first = false ;
189145 }
190146 await writer . close ( ) ;
@@ -207,10 +163,6 @@ class CommandEnv extends CommandPolykey {
207163 `TMP Vault "${ value . data ?. nameOrId } " does not exist` ,
208164 ) ;
209165 case 'ENOENT' :
210- // If we are working with schemas, then missing keys will be
211- // validated later.
212- if ( unwrappedSchema != null ) break ;
213-
214166 // It is expected for the data to be populated with the
215167 // offending secret and vault name if a secret was not found.
216168 throw new Error (
@@ -301,44 +253,34 @@ class CommandEnv extends CommandPolykey {
301253 } ;
302254 }
303255
304- // Apply defaults using the schema
305- const filteredEnvp : Record < string , string > = { } ;
306- if ( schema != null && unwrappedSchema != null ) {
307- // Parse the schema for manual filtering
308- const { requiredKeys, allKeys, defaults } = unwrappedSchema ;
256+ // Validate the schema
257+ if ( options . egressSchema != null ) {
258+ // Compose the schema as ajv cannot parse cross-schema refs
259+ const schema = await $RefParser . bundle ( options . egressSchema ) ;
309260
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.
313- for ( const key of allKeys ) {
314- let value = envp [ key ] ;
315- if ( value == null && defaults [ key ] != null ) {
316- value = defaults [ key ] ;
317- }
318- if (
319- requiredKeys . includes ( key ) &&
320- ( value == null || value === '' )
321- ) {
322- throw new binErrors . ErrorPolykeyCLIMissingRequiredEnvName (
323- `Expected definition for ${ key } ` ,
324- ) ;
325- }
326- if ( value != null ) {
327- filteredEnvp [ key ] = value . toString ( ) ;
328- }
329- }
330-
331- // Validate the schema using ajv. All defaults have already been
332- // applied. This is now the final state of the exported variables.
333- const ajv = new Ajv ( { allErrors : true } ) ;
261+ // Validate the schema using ajv. This will also apply defaults and
262+ // coerce types as necessary.
263+ const ajv = new Ajv2019 ( {
264+ strict : true ,
265+ allErrors : true ,
266+ useDefaults : true ,
267+ coerceTypes : true ,
268+ } ) ;
334269 const validate = ajv . compile ( schema ) ;
335- validate ( envp ) ;
270+ const valid = validate ( envp ) ;
271+ if ( ! valid && validate . errors != null ) {
272+ throw new binErrors . ErrorPolykeyCLISchemaInvalid (
273+ 'JSON schema validation failed' ,
274+ {
275+ data : {
276+ errors : [ ...validate . errors ] ,
277+ } ,
278+ } ,
279+ ) ;
280+ }
336281 }
337282
338- return [
339- utils . isEmptyObject ( filteredEnvp ) ? envp : filteredEnvp ,
340- envpPath ,
341- ] ;
283+ return [ envp , envpPath ] ;
342284 } , meta ) ;
343285 // End connection early to avoid errors on server
344286 await pkClient . stop ( ) ;
0 commit comments