@mysense/middleware v1.5.0
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 implementsService
:
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 theinitExternalServices
middleware. - the
init
method also calls thevalidate
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)
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago