1.5.0 • Published 4 years ago

@mysense/middleware v1.5.0

Weekly downloads
-
License
MIT
Repository
github
Last release
4 years ago

Middy middleware

Handy Middy middleware, use with lambdas

Function Shield

Secure lambda handlers

import { functionShield, allowOutboundRequest } from '@mysense/middleware/lib/functionShield';
import got from 'got';
import middy from 'middy';
import { ssm } from 'middy/middlewares';

const { STAGE = 'dev' } = process.env;

// Business logic
const handler = async (/*event, context*/) =>
    allowOutboundRequest(got('www.google.com'))
        .then((res) => console.log(res.body))
        .catch((err) => console.log(err.response.body));

// Add middleware
export const securedHandler = middy(handler)
    // Load config from AWS SSM
    .use(ssm({
        cache: true,
        cacheExpiryInMillis: 5 * 60 * 1000, // tslint:disable-line:no-magic-numbers (re-fetch every 5 minutes)
        setToContext: true,
        names: {
            FUNCTION_SHIELD_TOKEN: `/global/${STAGE}/puresec/function-shield-token`,
        },
    }))
    // Secure handler
    .use(functionShield({
        policy: {
            outbound_connectivity: 'block',
            read_write_tmp: 'block',
            create_child_process: 'block',
            read_handler: 'block',
        },
        disable_analytics: false,
    }));

Assert Environment Variables

Ensure required environment variables are present

import { assertEnvVars } from '@mysense/middleware/lib/assertEnvVars';
import middy from 'middy';

// Business logic
const handler = async (/*event, context*/) => {
    console.log(`Your var is: ${ process.env.MY_VAR_1 }`)
};

// Add middleware
export const validatedHandler = middy(handler)
    // Ensure required env vars are present
    .use(assertEnvVars([
        'MY_VAR_1',
        'MY_VAR_2',
    ]));

Validate SNS Events

Ensure received event's schema is compliant with inputSchema

import { log } from '@mysense/logger';
import { validateSnsEvent } from '@mysense/middleware/lib/validateSnsEvent';
import middy from 'middy';

// Business logic
export const handler: SNSHandler = async (event/*, context*/) => {
    const parsedSnsEvents = event.Records
        // tslint:disable-next-line
        .map((r) => ({ ...JSON.parse(r.Sns.Message).event }))
        .map((snsEventInfo) => {
            if (isEmpty(snsEventInfo))
                throw new Error(`Invalid event: <exampleService|exampleEventName>: ${snsEventInfo}`);
            return snsEventInfo;
        });
    log.debug(`consumerMoveEvents :${JSON.stringify(parsedSnsEvents)}`);

};

const inputSchema = {
    type: 'object',
    properties: {
        event: {
            type: 'object',
            properties: {
                MyKey1: { type: 'string', minLength: 36, maxLength: 36 },
                MyKey2: { type: 'string' },
                MyKey3: { type: 'string' },
            },
            additionalProperties: false,
            required: ['MyKey1', 'MyKey2'],
        },
    },
    required: ['event'],
};

// Add middleware
export const validatedHandler = middy(handler)
    .use(validateSnsEvent({ inputSchema }))

Inline middleware

Log AWS Event

Inline middleware that prints the incoming event

Enrich HTTP Response

Inline middleware that enriches the HTTP response with requestId & logStream

Error Handler

Inline middleware that handles errors consistently across lambdas

import { logAWSEvent } from '@mysense/middleware/lib/inline/logAWSEvent';
import { enrichHTTPResponse } from '@mysense/middleware/lib/inline/enrichHTTPResponse';
import { errorHandler } from '@mysense/middleware/lib/inline/errorHandler';
import middy from 'middy';

// Business logic
const handler = async (/*event, context*/) => {
    console.log('Hello World')
};

// Add middleware
export const wrappedHandler = middy(handler);

// Inline middleware
wrappedHandler.before(logAWSEvent);         // log AWS event which triggered the lambda
wrappedHandler.after(enrichHTTPResponse);   // enrich HTTP response with `requestId` & `logStream`
wrappedHandler.onError(errorHandler);       // same as enrichResponse + log troubleshoot info, mask error in prod, set statusCode

External Services

Initialize 3rd party services

For now, the services only encapsulate the configuration & make it easy to load it from SSM. This gives us the benefit that consumers don't need to know or care about the required SSM params for a service (for the most part, they still need to grant lambdas permission to ssm:GetParameters* on arn:aws:ssm:${REGION}:${ACCOUNT}:parameter/global/${STAGE}/${EXTERNAL-SERVICE-NAME}/*). We can modify a 3rd party service's config structure (add new config, change current SSM param structure) and all services that upgrade to the new version will just work, without required code changes.

Future versions will also encapsulate, initialize & make available the service sdk (eg. Twilio SDK).

How it works:

  • every external service extends BaseService and implements Service:
import { Context } from 'aws-lambda';

export declare class BaseService {
    protected _initialized: boolean;
}

export declare abstract class Service {
    public readonly serviceName: string;

    public static getSSMParamNames(stage: string): object;
    public abstract init(context: Context): Promise<string | void>;
    public abstract validate(): Promise<string | void>;
}
  • every external service provides a list of SSM params to fetch via the Service.getSSMParamNames() static method
  • the actual param fetching is done by the ssm middleware
public static getSSMParamNames(stage: string): TwilioConfig {
    return {
        TWILIO_AUTH_TOKEN: `/global/${stage}/twilio/auth`,
        TWILIO_NOTIFY_SMS_SID: `/global/${stage}/twilio/notify-sms-sid`,
        TWILIO_NOTIFY_URL: `/global/${stage}/twilio/notify-url`,
        TWILIO_SID: `/global/${stage}/twilio/sid`,
        TWILIO_URL: `/global/${stage}/twilio/url`,
    };
}
  • every external service implements the init method which is invoked by the initExternalServices middleware.
  • the init method also calls the validate member function which makes sure that the service config was loaded OK
  • every external service stores config data in private member variables that are accessible via getter functions; this is so the config is only initialized once via the init method
import { initExternalServices } from '@mysense/middleware/lib/initExternalServices';
import { Twilio } from '@mysense/middleware/lib/external/twilio';
import middy from 'middy';

// This will be the initialized service
const twilio = new Twilio();

// Business logic
const handler = async (/*event, context*/) => {
    console.log(`Twilio URL is: ${ twilio.url }`)
};

// Add middleware
export const wrappedHandler = middy(handler)
    // Get config from AWS SSM.
    .use(ssm({
        cache: true,
        cacheExpiryInMillis: 5 * 60 * 1000, // tslint:disable-line:no-magic-numbers (re-fetch every 5 minutes)
        setToContext: true,
        names: {
            ...Twilio.getSSMParamNames(STAGE),
        },
    }))
    // Bootstrap 3rd party services based on retrieved config (also validates the config for the services).
    .use(initExternalServices([
        twilio,
    ]))

Available 3rd Party Services

  • Twilio
  • Puresec (only loads, verifies & provides the FunctionShield token)
1.5.0

4 years ago

1.4.0

4 years ago

1.3.18

4 years ago

1.3.19

4 years ago

1.3.20

4 years ago

1.3.17

4 years ago

1.3.16

4 years ago

1.3.15

4 years ago

1.3.13

4 years ago

1.3.14

4 years ago

1.3.12

4 years ago

1.3.11

4 years ago

1.3.10

4 years ago

1.3.9

4 years ago

1.3.8

4 years ago

1.3.7

5 years ago

1.3.6

5 years ago

1.3.5

5 years ago

1.3.4

5 years ago

1.3.3

5 years ago

1.3.2

5 years ago

1.3.1

5 years ago

1.3.0

5 years ago

1.2.6

5 years ago

1.2.5

5 years ago

1.2.4

5 years ago

1.2.3

5 years ago

1.2.2

5 years ago

1.2.1

5 years ago

1.2.0

5 years ago

1.1.1

5 years ago

1.1.0

5 years ago

1.0.1

5 years ago