Skip to content

Commit

Permalink
Validate signed api name (#157)
Browse files Browse the repository at this point in the history
* Re-order exports

* Validate signed API name triggers
  • Loading branch information
Siegrift authored Dec 6, 2023
1 parent c4fb655 commit 911238b
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 17 deletions.
18 changes: 18 additions & 0 deletions packages/airnode-feed/src/validation/schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,21 @@ test('validates trigger references', async () => {
])
);
});

test('trigger must point to a valid Signed API definition', async () => {
// As a note, having unused Signed API definitions is not an error.
const invalidConfig: Config = {
...config,
signedApis: [{ ...config.signedApis[0]!, name: 'different-name' }],
};

await expect(async () => configSchema.parseAsync(invalidConfig)).rejects.toStrictEqual(
new ZodError([
{
code: 'custom',
message: 'Unable to find signed API with name: localhost',
path: ['triggers', 'signedApiUpdates', 0, 'signedApiName'],
},
])
);
});
67 changes: 50 additions & 17 deletions packages/airnode-feed/src/validation/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,27 @@ import { z, type SuperRefinement } from 'zod';

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

export type Config = z.infer<typeof configSchema>;
export type Address = z.infer<typeof config.evmAddressSchema>;
export type BeaconId = z.infer<typeof config.evmIdSchema>;
export type TemplateId = z.infer<typeof config.evmIdSchema>;
export type EndpointId = z.infer<typeof config.evmIdSchema>;

export const parameterSchema = z.strictObject({
name: z.string(),
type: z.string(),
value: z.string(),
});

export type Parameter = z.infer<typeof parameterSchema>;

export const templateSchema = z.strictObject({
endpointId: config.evmIdSchema,
parameters: z.array(parameterSchema),
});

export type Template = z.infer<typeof templateSchema>;

export const templatesSchema = z.record(config.evmIdSchema, templateSchema).superRefine((templates, ctx) => {
for (const [templateId, template] of Object.entries(templates)) {
// Verify that config.templates.<templateId> is valid by deriving the hash of the endpointId and parameters
Expand Down Expand Up @@ -53,11 +63,15 @@ export const templatesSchema = z.record(config.evmIdSchema, templateSchema).supe
}
});

export type Templates = z.infer<typeof templatesSchema>;

export const endpointSchema = z.strictObject({
oisTitle: z.string(),
endpointName: z.string(),
});

export type Endpoint = z.infer<typeof endpointSchema>;

export const endpointsSchema = z.record(endpointSchema).superRefine((endpoints, ctx) => {
for (const [endpointId, endpoint] of Object.entries(endpoints)) {
// Verify that config.endpoints.<endpointId> is valid
Expand All @@ -77,6 +91,8 @@ export const endpointsSchema = z.record(endpointSchema).superRefine((endpoints,
}
});

export type Endpoints = z.infer<typeof endpointsSchema>;

export const baseBeaconUpdateSchema = z.strictObject({
deviationThreshold: z.number(),
heartbeatInterval: z.number().int(),
Expand All @@ -88,17 +104,23 @@ export const beaconUpdateSchema = z
})
.merge(baseBeaconUpdateSchema);

export type BeaconUpdate = z.infer<typeof beaconUpdateSchema>;

export const signedApiUpdateSchema = z.strictObject({
signedApiName: z.string(),
templateIds: z.array(config.evmIdSchema),
fetchInterval: z.number(),
updateDelay: z.number(),
});

export type SignedApiUpdate = z.infer<typeof signedApiUpdateSchema>;

export const triggersSchema = z.strictObject({
signedApiUpdates: z.array(signedApiUpdateSchema).nonempty(),
});

export type Triggers = z.infer<typeof triggersSchema>;

const validateTemplatesReferences: SuperRefinement<{ templates: Templates; endpoints: Endpoints }> = (config, ctx) => {
for (const [templateId, template] of Object.entries(config.templates)) {
const endpoint = config.endpoints[template.endpointId];
Expand Down Expand Up @@ -203,6 +225,8 @@ export const signedApiSchema = z.strictObject({
url: z.string().url(),
});

export type SignedApi = z.infer<typeof signedApiSchema>;

export const signedApisSchema = z
.array(signedApiSchema)
.nonempty()
Expand All @@ -219,10 +243,28 @@ export const signedApisSchema = z
}
});

const validateSignedApiReferences: SuperRefinement<{
triggers: Triggers;
signedApis: SignedApi[];
}> = (config, ctx) => {
for (const [index, trigger] of config.triggers.signedApiUpdates.entries()) {
const api = config.signedApis.find((api) => api.name === trigger.signedApiName);
if (!api) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Unable to find signed API with name: ${trigger.signedApiName}`,
path: ['triggers', 'signedApiUpdates', index, 'signedApiName'],
});
}
}
};

export const oisesSchema = z.array(oisSchema);

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

export type ApisCredentials = z.infer<typeof apisCredentialsSchema>;

export const nodeSettingsSchema = z.strictObject({
nodeVersion: z.string().refine((version) => version === packageJson.version, 'Invalid node version'),
airnodeWalletMnemonic: z.string().refine((mnemonic) => ethers.utils.isValidMnemonic(mnemonic), 'Invalid mnemonic'),
Expand All @@ -248,41 +290,32 @@ export const configSchema = z
})
.superRefine(validateTemplatesReferences)
.superRefine(validateOisReferences)
.superRefine(validateTriggerReferences);
.superRefine(validateTriggerReferences)
.superRefine(validateSignedApiReferences);

export const encodedValueSchema = z.string().regex(/^0x[\dA-Fa-f]{64}$/);

export const signatureSchema = z.string().regex(/^0x[\dA-Fa-f]{130}$/);

export const signedDataSchema = z.strictObject({
timestamp: z.string(),
encodedValue: encodedValueSchema,
signature: signatureSchema,
});

export type SignedData = z.infer<typeof signedDataSchema>;

export const signedApiPayloadSchema = signedDataSchema.extend({
beaconId: config.evmIdSchema,
airnode: config.evmAddressSchema,
templateId: config.evmIdSchema,
});

export type SignedApiPayload = z.infer<typeof signedApiPayloadSchema>;

export const signedApiBatchPayloadSchema = z.array(signedApiPayloadSchema);

export type SignedApiPayload = z.infer<typeof signedApiPayloadSchema>;
export type SignedApiBatchPayload = z.infer<typeof signedApiBatchPayloadSchema>;
export type Config = z.infer<typeof configSchema>;
export type Template = z.infer<typeof templateSchema>;
export type Templates = z.infer<typeof templatesSchema>;
export type BeaconUpdate = z.infer<typeof beaconUpdateSchema>;
export type SignedApiUpdate = z.infer<typeof signedApiUpdateSchema>;
export type Triggers = z.infer<typeof triggersSchema>;
export type Address = z.infer<typeof config.evmAddressSchema>;
export type BeaconId = z.infer<typeof config.evmIdSchema>;
export type TemplateId = z.infer<typeof config.evmIdSchema>;
export type EndpointId = z.infer<typeof config.evmIdSchema>;
export type SignedData = z.infer<typeof signedDataSchema>;
export type Endpoint = z.infer<typeof endpointSchema>;
export type Endpoints = z.infer<typeof endpointsSchema>;
export type ApisCredentials = z.infer<typeof apisCredentialsSchema>;
export type Parameter = z.infer<typeof parameterSchema>;

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

Expand Down

0 comments on commit 911238b

Please sign in to comment.