Skip to content

Commit 911238b

Browse files
authored
Validate signed api name (#157)
* Re-order exports * Validate signed API name triggers
1 parent c4fb655 commit 911238b

File tree

2 files changed

+68
-17
lines changed

2 files changed

+68
-17
lines changed

packages/airnode-feed/src/validation/schema.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,21 @@ test('validates trigger references', async () => {
9393
])
9494
);
9595
});
96+
97+
test('trigger must point to a valid Signed API definition', async () => {
98+
// As a note, having unused Signed API definitions is not an error.
99+
const invalidConfig: Config = {
100+
...config,
101+
signedApis: [{ ...config.signedApis[0]!, name: 'different-name' }],
102+
};
103+
104+
await expect(async () => configSchema.parseAsync(invalidConfig)).rejects.toStrictEqual(
105+
new ZodError([
106+
{
107+
code: 'custom',
108+
message: 'Unable to find signed API with name: localhost',
109+
path: ['triggers', 'signedApiUpdates', 0, 'signedApiName'],
110+
},
111+
])
112+
);
113+
});

packages/airnode-feed/src/validation/schema.ts

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,27 @@ import { z, type SuperRefinement } from 'zod';
1515

1616
import packageJson from '../../package.json';
1717

18+
export type Config = z.infer<typeof configSchema>;
19+
export type Address = z.infer<typeof config.evmAddressSchema>;
20+
export type BeaconId = z.infer<typeof config.evmIdSchema>;
21+
export type TemplateId = z.infer<typeof config.evmIdSchema>;
22+
export type EndpointId = z.infer<typeof config.evmIdSchema>;
23+
1824
export const parameterSchema = z.strictObject({
1925
name: z.string(),
2026
type: z.string(),
2127
value: z.string(),
2228
});
2329

30+
export type Parameter = z.infer<typeof parameterSchema>;
31+
2432
export const templateSchema = z.strictObject({
2533
endpointId: config.evmIdSchema,
2634
parameters: z.array(parameterSchema),
2735
});
2836

37+
export type Template = z.infer<typeof templateSchema>;
38+
2939
export const templatesSchema = z.record(config.evmIdSchema, templateSchema).superRefine((templates, ctx) => {
3040
for (const [templateId, template] of Object.entries(templates)) {
3141
// Verify that config.templates.<templateId> is valid by deriving the hash of the endpointId and parameters
@@ -53,11 +63,15 @@ export const templatesSchema = z.record(config.evmIdSchema, templateSchema).supe
5363
}
5464
});
5565

66+
export type Templates = z.infer<typeof templatesSchema>;
67+
5668
export const endpointSchema = z.strictObject({
5769
oisTitle: z.string(),
5870
endpointName: z.string(),
5971
});
6072

73+
export type Endpoint = z.infer<typeof endpointSchema>;
74+
6175
export const endpointsSchema = z.record(endpointSchema).superRefine((endpoints, ctx) => {
6276
for (const [endpointId, endpoint] of Object.entries(endpoints)) {
6377
// Verify that config.endpoints.<endpointId> is valid
@@ -77,6 +91,8 @@ export const endpointsSchema = z.record(endpointSchema).superRefine((endpoints,
7791
}
7892
});
7993

94+
export type Endpoints = z.infer<typeof endpointsSchema>;
95+
8096
export const baseBeaconUpdateSchema = z.strictObject({
8197
deviationThreshold: z.number(),
8298
heartbeatInterval: z.number().int(),
@@ -88,17 +104,23 @@ export const beaconUpdateSchema = z
88104
})
89105
.merge(baseBeaconUpdateSchema);
90106

107+
export type BeaconUpdate = z.infer<typeof beaconUpdateSchema>;
108+
91109
export const signedApiUpdateSchema = z.strictObject({
92110
signedApiName: z.string(),
93111
templateIds: z.array(config.evmIdSchema),
94112
fetchInterval: z.number(),
95113
updateDelay: z.number(),
96114
});
97115

116+
export type SignedApiUpdate = z.infer<typeof signedApiUpdateSchema>;
117+
98118
export const triggersSchema = z.strictObject({
99119
signedApiUpdates: z.array(signedApiUpdateSchema).nonempty(),
100120
});
101121

122+
export type Triggers = z.infer<typeof triggersSchema>;
123+
102124
const validateTemplatesReferences: SuperRefinement<{ templates: Templates; endpoints: Endpoints }> = (config, ctx) => {
103125
for (const [templateId, template] of Object.entries(config.templates)) {
104126
const endpoint = config.endpoints[template.endpointId];
@@ -203,6 +225,8 @@ export const signedApiSchema = z.strictObject({
203225
url: z.string().url(),
204226
});
205227

228+
export type SignedApi = z.infer<typeof signedApiSchema>;
229+
206230
export const signedApisSchema = z
207231
.array(signedApiSchema)
208232
.nonempty()
@@ -219,10 +243,28 @@ export const signedApisSchema = z
219243
}
220244
});
221245

246+
const validateSignedApiReferences: SuperRefinement<{
247+
triggers: Triggers;
248+
signedApis: SignedApi[];
249+
}> = (config, ctx) => {
250+
for (const [index, trigger] of config.triggers.signedApiUpdates.entries()) {
251+
const api = config.signedApis.find((api) => api.name === trigger.signedApiName);
252+
if (!api) {
253+
ctx.addIssue({
254+
code: z.ZodIssueCode.custom,
255+
message: `Unable to find signed API with name: ${trigger.signedApiName}`,
256+
path: ['triggers', 'signedApiUpdates', index, 'signedApiName'],
257+
});
258+
}
259+
}
260+
};
261+
222262
export const oisesSchema = z.array(oisSchema);
223263

224264
export const apisCredentialsSchema = z.array(config.apiCredentialsSchema);
225265

266+
export type ApisCredentials = z.infer<typeof apisCredentialsSchema>;
267+
226268
export const nodeSettingsSchema = z.strictObject({
227269
nodeVersion: z.string().refine((version) => version === packageJson.version, 'Invalid node version'),
228270
airnodeWalletMnemonic: z.string().refine((mnemonic) => ethers.utils.isValidMnemonic(mnemonic), 'Invalid mnemonic'),
@@ -248,41 +290,32 @@ export const configSchema = z
248290
})
249291
.superRefine(validateTemplatesReferences)
250292
.superRefine(validateOisReferences)
251-
.superRefine(validateTriggerReferences);
293+
.superRefine(validateTriggerReferences)
294+
.superRefine(validateSignedApiReferences);
252295

253296
export const encodedValueSchema = z.string().regex(/^0x[\dA-Fa-f]{64}$/);
297+
254298
export const signatureSchema = z.string().regex(/^0x[\dA-Fa-f]{130}$/);
299+
255300
export const signedDataSchema = z.strictObject({
256301
timestamp: z.string(),
257302
encodedValue: encodedValueSchema,
258303
signature: signatureSchema,
259304
});
260305

306+
export type SignedData = z.infer<typeof signedDataSchema>;
307+
261308
export const signedApiPayloadSchema = signedDataSchema.extend({
262309
beaconId: config.evmIdSchema,
263310
airnode: config.evmAddressSchema,
264311
templateId: config.evmIdSchema,
265312
});
266313

314+
export type SignedApiPayload = z.infer<typeof signedApiPayloadSchema>;
315+
267316
export const signedApiBatchPayloadSchema = z.array(signedApiPayloadSchema);
268317

269-
export type SignedApiPayload = z.infer<typeof signedApiPayloadSchema>;
270318
export type SignedApiBatchPayload = z.infer<typeof signedApiBatchPayloadSchema>;
271-
export type Config = z.infer<typeof configSchema>;
272-
export type Template = z.infer<typeof templateSchema>;
273-
export type Templates = z.infer<typeof templatesSchema>;
274-
export type BeaconUpdate = z.infer<typeof beaconUpdateSchema>;
275-
export type SignedApiUpdate = z.infer<typeof signedApiUpdateSchema>;
276-
export type Triggers = z.infer<typeof triggersSchema>;
277-
export type Address = z.infer<typeof config.evmAddressSchema>;
278-
export type BeaconId = z.infer<typeof config.evmIdSchema>;
279-
export type TemplateId = z.infer<typeof config.evmIdSchema>;
280-
export type EndpointId = z.infer<typeof config.evmIdSchema>;
281-
export type SignedData = z.infer<typeof signedDataSchema>;
282-
export type Endpoint = z.infer<typeof endpointSchema>;
283-
export type Endpoints = z.infer<typeof endpointsSchema>;
284-
export type ApisCredentials = z.infer<typeof apisCredentialsSchema>;
285-
export type Parameter = z.infer<typeof parameterSchema>;
286319

287320
export const secretsSchema = z.record(z.string());
288321

0 commit comments

Comments
 (0)