Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Add External Source (and External Event) Attribute Validation #116

Open
wants to merge 39 commits into
base: develop
Choose a base branch
from

Conversation

pranav-super
Copy link

@pranav-super pranav-super commented Nov 18, 2024

This pull request adds external source/event attribute validation to Aerie.

This means that when an external source is uploaded to AERIE, via endpoints like the CLI or the UI, it is forwarded here, has its formatting checked against a schema, and additionally has the attributes of the source and each of its events checked for validity, before submitting it to the database.

Submission of event/source type schemas for the purpose of later validation is also handled here - the gateway simply checks that they are valid JSON Schema.

A more thorough explanation of the feature can be found in the AERIE PR, here.

} catch (error) {
logger.error((error as Error).message);
res.status(500);
res.send((error as Error).message);
Copy link
Contributor

@Mythicaeda Mythicaeda Nov 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not Aerie Gateway standard. Do not just send a text message back as an error message, you need to send a JSON. See L43 of middleware.ts for an example of the correct way to return an error status. This applies to all places where you're returning error statuses.

async function uploadExternalEventType(req: Request, res: Response) {
logger.info(`POST /uploadExternalEventType: Entering function...`);
const authorizationHeader = req.get('authorization');
logger.info(`POST /uploadExternalEventType: ${authorizationHeader}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this, you should never be logging user credentials like this.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry - missed this from cleaning up some testing debugs, removed now!

const ajv = new Ajv();

async function uploadExternalEventType(req: Request, res: Response) {
logger.info(`POST /uploadExternalEventType: Entering function...`);
Copy link
Contributor

@Mythicaeda Mythicaeda Nov 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This log is unnecessary, please remove it.

Comment on lines 37 to 43
if (!schemaIsValid) {
throw new Error("Schema was not a valid JSON Schema.");
}
} catch (error) {
logger.error((error as Error).message);
res.status(500);
res.send((error as Error).message);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can ajv.validateSchema throw an Error? If not, why is this a try-catch? If it can, then this comment can be resolved.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch - no it looks like all errors are just directly accessible through ajv.errors. I updated the logger and res.send statements to use ajv.errors instead of relying on a try / catch.

Comment on lines 45 to 56
try {
if (attribute_schema['title'] === undefined || attribute_schema.title !== external_event_type_name) {
throw new Error('Schema title does not match provided external event type name.');
}
} catch (error) {
logger.error(
`POST /uploadExternalEventType: Error occurred during External Event Type ${external_event_type_name} upload`,
);
logger.error((error as Error).message);
res.status(500).send({ message: (error as Error).message });
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this a try-catch instead of an if-return?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned up, no longer using try/catch

return;
}

logger.info(`POST /uploadExternalEventType: Attribute schema was VALID`);
Copy link
Contributor

@Mythicaeda Mythicaeda Nov 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use present tense in logs, unless the log is explicitly referring to a "prior state":

Suggested change
logger.info(`POST /uploadExternalEventType: Attribute schema was VALID`);
logger.info(`POST /uploadExternalEventType: Attribute schema is VALID`);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All logger and response messages now use present tense!

method: 'POST',
});

type CreateExternalEventTypeResponse = {
Copy link
Contributor

@Mythicaeda Mythicaeda Nov 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Types should be declared in a file in the types directory, not inline. Additionally, this type should not start at the data wrapper object but just be the middle { attribute_schema: object; name: string }

Please use types/plans.ts for a reference of what I mean.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved all type definitions out to types/external-source.ts

@dandelany dandelany added the publish Tells GH to publish docker images for this PR label Nov 26, 2024
Copy link
Contributor

@duranb duranb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we keep a consistent camel cased variable names where applicable?

@@ -0,0 +1,406 @@
import Ajv from 'ajv';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this file be renamed to be kebab cased, unless it needs to be snake?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! Renamed

Comment on lines 130 to 135
const {
headers: { 'x-hasura-role': roleHeader, 'x-hasura-user-id': userHeader },
} = req;

const { body } = req;
const { event_types, source_types } = body;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be done at once:

Suggested change
const {
headers: { 'x-hasura-role': roleHeader, 'x-hasura-user-id': userHeader },
} = req;
const { body } = req;
const { event_types, source_types } = body;
const {
headers: { 'x-hasura-role': roleHeader, 'x-hasura-user-id': userHeader },
body: {event_types, source_types}
} = req;

This comment applies to multiple functions in this file

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented!

Comment on lines 203 to 207
const {
headers: { 'x-hasura-role': roleHeader, 'x-hasura-user-id': userHeader },
} = req;

const { body } = req;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified:

Suggested change
const {
headers: { 'x-hasura-role': roleHeader, 'x-hasura-user-id': userHeader },
} = req;
const { body } = req;
const {
headers: { 'x-hasura-role': roleHeader, 'x-hasura-user-id': userHeader },
body: { source, events }
} = req;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented!

Comment on lines 209 to 217
if (typeof body !== 'object') {
logger.error(
`POST /uploadExternalSourceEventTypes: Body of request must be a JSON, with two stringified properties: "source" and "events".`,
);
res
.status(500)
.send({ message: `Body of request must be a JSON, with two stringified properties: "source" and "events".` });
return;
}
Copy link
Contributor

@Mythicaeda Mythicaeda Dec 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if statement is unnecessary. Additionally, this error message describes a different body than the swagger docs of the endpoint. Please align the two. (Additionally please move the error string into a const if you're going to keep repeating it)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the if and fixed the swagger docs for both end-points. Also went through and all logger + res.status(500).send({...}) combos are now using a consistent errorMsg for both!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
publish Tells GH to publish docker images for this PR
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants