Skip to content

Commit 8a7721e

Browse files
committed
use the agreed approach with schema hash
1 parent 452cf6f commit 8a7721e

File tree

8 files changed

+396
-87
lines changed

8 files changed

+396
-87
lines changed

packages/input_schema/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
},
5050
"dependencies": {
5151
"@apify/consts": "^2.41.0",
52+
"@apify/input_secrets": "file:../input_secrets",
5253
"acorn-loose": "^8.4.0",
5354
"countries-list": "^3.0.0"
5455
},

packages/input_schema/src/intl.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const intlStrings = {
3333
'Field schema.properties.{fieldKey} does not exist, but it is specified in schema.required. Either define the field or remove it from schema.required.',
3434
'inputSchema.validation.proxyGroupMustBeArrayOfStrings':
3535
'Field {rootName}.{fieldKey}.apifyProxyGroups must be an array of strings.',
36+
'inputSchema.validation.secretFieldSchemaChanged':
37+
'The field schema.properties.{fieldKey} is a secret field, but its schema has changed. Please update the value in the input editor.',
3638
};
3739

3840
/**

packages/input_schema/src/schema.json

Lines changed: 77 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,10 @@
153153
"title": { "type": "string" },
154154
"description": { "type": "string" },
155155
"example": { "type": "string" },
156+
"pattern": { "type": "string" },
156157
"nullable": { "type": "boolean" },
158+
"minLength": { "type": "integer" },
159+
"maxLength": { "type": "integer" },
157160
"editor": { "enum": ["textfield", "textarea", "hidden"] },
158161
"isSecret": { "enum": [true] },
159162
"sectionCaption": { "type": "string" },
@@ -166,71 +169,102 @@
166169
"type": "object",
167170
"properties": {
168171
"type": { "enum": ["array"] },
169-
"editor": { "enum": ["json", "requestListSources", "pseudoUrls", "globs", "keyValue", "stringList", "select", "hidden"] }
172+
"editor": { "enum": ["json", "requestListSources", "pseudoUrls", "globs", "keyValue", "stringList", "select", "hidden"] },
173+
"isSecret": { "type": "boolean" }
170174
},
171175
"additionalProperties": true,
172176
"required": ["type", "title", "description", "editor"],
173177
"if": {
174178
"properties": {
175-
"editor": { "const": "select" }
179+
"isSecret": {
180+
"not": {
181+
"const": true
182+
}
183+
}
176184
}
177185
},
178186
"then": {
179-
"additionalProperties": false,
180-
"required": ["items"],
181-
"properties": {
182-
"type": { "enum": ["array"] },
183-
"editor": { "enum": ["select"] },
184-
"title": { "type": "string" },
185-
"description": { "type": "string" },
186-
"default": { "type": "array" },
187-
"prefill": { "type": "array" },
188-
"example": { "type": "array" },
189-
"nullable": { "type": "boolean" },
190-
"minItems": { "type": "integer" },
191-
"maxItems": { "type": "integer" },
192-
"uniqueItems": { "type": "boolean" },
193-
"sectionCaption": { "type": "string" },
194-
"sectionDescription": { "type": "string" },
195-
"items": {
196-
"type": "object",
197-
"additionalProperties": false,
198-
"properties": {
199-
"type": { "enum": ["string"] },
200-
"enum": {
201-
"type": "array",
202-
"items": { "type": "string" },
203-
"uniqueItems": true
187+
"if": {
188+
"properties": {
189+
"editor": { "const": "select" }
190+
}
191+
},
192+
"then": {
193+
"additionalProperties": false,
194+
"required": ["items"],
195+
"properties": {
196+
"type": { "enum": ["array"] },
197+
"editor": { "enum": ["select"] },
198+
"title": { "type": "string" },
199+
"description": { "type": "string" },
200+
"default": { "type": "array" },
201+
"prefill": { "type": "array" },
202+
"example": { "type": "array" },
203+
"nullable": { "type": "boolean" },
204+
"minItems": { "type": "integer" },
205+
"maxItems": { "type": "integer" },
206+
"uniqueItems": { "type": "boolean" },
207+
"sectionCaption": { "type": "string" },
208+
"sectionDescription": { "type": "string" },
209+
"items": {
210+
"type": "object",
211+
"additionalProperties": false,
212+
"properties": {
213+
"type": { "enum": ["string"] },
214+
"enum": {
215+
"type": "array",
216+
"items": { "type": "string" },
217+
"uniqueItems": true
218+
},
219+
"enumTitles": {
220+
"type": "array",
221+
"items": { "type": "string" }
222+
}
204223
},
205-
"enumTitles": {
206-
"type": "array",
207-
"items": { "type": "string" }
208-
}
224+
"required": ["type", "enum"]
209225
},
210-
"required": ["type", "enum"]
226+
"isSecret": { "enum": [false] }
227+
}
228+
},
229+
"else": {
230+
"additionalProperties": false,
231+
"properties": {
232+
"type": { "enum": ["array"] },
233+
"editor": { "enum": ["json", "requestListSources", "pseudoUrls", "globs", "keyValue", "stringList", "hidden"] },
234+
"title": { "type": "string" },
235+
"description": { "type": "string" },
236+
"default": { "type": "array" },
237+
"prefill": { "type": "array" },
238+
"example": { "type": "array" },
239+
"nullable": { "type": "boolean" },
240+
"minItems": { "type": "integer" },
241+
"maxItems": { "type": "integer" },
242+
"uniqueItems": { "type": "boolean" },
243+
"sectionCaption": { "type": "string" },
244+
"sectionDescription": { "type": "string" },
245+
"placeholderKey": { "type": "string" },
246+
"placeholderValue": { "type": "string" },
247+
"patternKey": { "type": "string" },
248+
"patternValue": { "type": "string" },
249+
"isSecret": { "enum": [false] }
211250
}
212251
}
213252
},
214253
"else": {
215254
"additionalProperties": false,
216255
"properties": {
217256
"type": { "enum": ["array"] },
218-
"editor": { "enum": ["json", "requestListSources", "pseudoUrls", "globs", "keyValue", "stringList", "hidden"] },
257+
"editor": { "enum": ["json", "hidden"] },
219258
"title": { "type": "string" },
220259
"description": { "type": "string" },
221-
"default": { "type": "array" },
222-
"prefill": { "type": "array" },
223260
"example": { "type": "array" },
224261
"nullable": { "type": "boolean" },
225262
"minItems": { "type": "integer" },
226263
"maxItems": { "type": "integer" },
227264
"uniqueItems": { "type": "boolean" },
228265
"sectionCaption": { "type": "string" },
229266
"sectionDescription": { "type": "string" },
230-
"placeholderKey": { "type": "string" },
231-
"placeholderValue": { "type": "string" },
232-
"patternKey": { "type": "string" },
233-
"patternValue": { "type": "string" }
267+
"isSecret": { "enum": [true] }
234268
}
235269
}
236270
},
@@ -282,7 +316,11 @@
282316
"title": { "type": "string" },
283317
"description": { "type": "string" },
284318
"example": { "type": "object" },
319+
"patternKey": { "type": "string" },
320+
"patternValue": { "type": "string" },
285321
"nullable": { "type": "boolean" },
322+
"minProperties": { "type": "integer" },
323+
"maxProperties": { "type": "integer" },
286324
"editor": { "enum": ["json", "hidden"] },
287325
"sectionCaption": { "type": "string" },
288326
"sectionDescription": { "type": "string" },

packages/input_schema/src/utilities.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { ValidateFunction } from 'ajv';
33
import { countries } from 'countries-list';
44

55
import { PROXY_URL_REGEX, URL_REGEX } from '@apify/consts';
6+
import { isEncryptedValueForFieldSchema, isEncryptedValueForFieldType } from '@apify/input_secrets';
67

78
import { parseAjvError } from './input_schema';
89
import { m } from './intl';
@@ -133,13 +134,34 @@ export function validateInputUsingValidator(
133134
// Process AJV validation errors
134135
if (!isValid) {
135136
errors = validator.errors!
137+
.filter((error) => {
138+
// Remove type errors on encrypted object/array secrets
139+
// We are storing encrypted objects/arrays as strings, so AJV will throw type the error here.
140+
if (error.keyword === 'type' && error.instancePath) {
141+
const path = error.instancePath.replace(/^\//, '').split('/')[0];
142+
const propSchema = inputSchema.properties?.[path];
143+
const value = input[path];
144+
145+
// Check if the property is a secret and if the value is an encrypted object/array.
146+
// We do additional validation of the field schema in the later part of this function
147+
if (
148+
propSchema?.isSecret
149+
&& typeof value === 'string'
150+
&& (propSchema.type === 'object' || propSchema.type === 'array')
151+
&& isEncryptedValueForFieldType(value, propSchema.type)
152+
) {
153+
return false;
154+
}
155+
}
156+
return true;
157+
})
136158
.map((error) => parseAjvError(error, 'input', properties, input))
137159
.filter((error) => !!error) as any[];
138160
}
139161

140162
Object.keys(properties).forEach((property) => {
141163
const value = input[property];
142-
const { type, editor, patternKey, patternValue } = properties[property];
164+
const { type, editor, patternKey, patternValue, isSecret } = properties[property];
143165
const fieldErrors = [];
144166
// Check that proxy is required, if yes, valides that it's correctly setup
145167
if (type === 'object' && editor === 'proxy') {
@@ -215,7 +237,7 @@ export function validateInputUsingValidator(
215237
}
216238
}
217239
// Check that object items fit patternKey and patternValue
218-
if (type === 'object' && value) {
240+
if (type === 'object' && value && typeof value === 'object') {
219241
if (patternKey) {
220242
const check = new RegExp(patternKey);
221243
const invalidKeys: any[] = [];
@@ -249,6 +271,15 @@ export function validateInputUsingValidator(
249271
}
250272
}
251273

274+
// Additional validation for secret fields
275+
if (isSecret && value && typeof value === 'string') {
276+
// If the value is a valid encrypted string for the field type,
277+
// we check if the field schema is likely to be still valid (is unchanged from the time of encryption).
278+
if (isEncryptedValueForFieldType(value, type) && !isEncryptedValueForFieldSchema(value, properties[property])) {
279+
fieldErrors.push(m('inputSchema.validation.secretFieldSchemaChanged', { rootName: 'input', fieldKey: property }));
280+
}
281+
}
282+
252283
if (fieldErrors.length > 0) {
253284
const message = fieldErrors.join(', ');
254285
errors.push({ fieldKey: property, message });

0 commit comments

Comments
 (0)