1 | # Epsilon
|
2 |
|
3 | A tiny library to simplify serving consistent apis from Lambda with OpenAPI
|
4 |
|
5 | # TODO:
|
6 |
|
7 | - Discuss pros/cons of single lambda for http/batch in this document
|
8 | - path/query var checking against open api doc
|
9 | - check compression handling
|
10 |
|
11 | ## How better than just using straight Node?
|
12 |
|
13 | - Uses typescript instead of the Godforsaken straight javascript
|
14 | - Handles route mapping (multiple ends, single lambda)
|
15 | - Uses Promises and has a top level .catch to convert to 500
|
16 | - Adds compression
|
17 | - Adds CORS
|
18 | - Adds JWT handling
|
19 | - Consistent error handling
|
20 | - Can serve static content as well
|
21 | - Kinda-persistent objects allow for optimistic caching
|
22 | - Built in support for Non-HTTP (Batch) processing via SaltMint, SNS, SQS, Email, etc
|
23 |
|
24 | # How better than using Express?
|
25 |
|
26 | - Doesn't have req/res architecture to fake so much easier to test
|
27 | - Much lighter
|
28 |
|
29 | # Other service
|
30 |
|
31 | - Environmental service
|
32 | - Simple redirects
|
33 |
|
34 | # Version 0.4.0 Release Notes
|
35 |
|
36 | - Switched logging for GraphQL introspection calls on local-server down to silly level
|
37 | - Updated to new version of libraries
|
38 | - Switched to js-yaml instead of node-yaml
|
39 | - Moved api-gateway package to http package to reflect that this also handles ALB endpoints
|
40 |
|
41 | # Version 0.3.0 Release Notes
|
42 |
|
43 | - Remapped CRON handler to be able to filter on more than just the incoming Event name. Given the new mapping,
|
44 | I'd recommend just setting up an "every minute" Cloudwatch event and using filters. Filters now allow
|
45 | running multiple Batch processors, eg Dev/QA/Prod
|
46 | - Adding logging of request ID to output errors
|
47 | - Added default error (to allow masking of 500 errors in prod and prevent information leakage)
|
48 | - Allow optional access to the request context for handlers (esp for the request id, remaining time)
|
49 |
|
50 | # GraphQL Support (v0.1.x and above)
|
51 |
|
52 | If you are just doing straight GraphQL then you don't really need to use Epsilon at all (I'd recommend just
|
53 | going with straight https://www.npmjs.com/package/apollo-server-lambda). However, if you want to start messing
|
54 | with GraphQL while maintaining your existing OpenAPI 3.0 endpoints, Epsilon allows you to designate a regular
|
55 | expression for which all matching requests are delegated to a supplied ApolloServer, bypassing Epsilon.
|
56 |
|
57 | To do this, you must include the following libraries (They aren't marked as dependencies of Epsilon since they
|
58 | aren't required if you don't support GraphQL)
|
59 |
|
60 | ```
|
61 | "apollo-server-lambda": "2.8.1",
|
62 | "graphql": "14.4.2",
|
63 | ```
|
64 |
|
65 | Then, in your router-config, you must set an ApolloServer and an Apollo Regex:
|
66 |
|
67 | ```typescript
|
68 | const typeDefs = gql`
|
69 | type Query {
|
70 | hello: String
|
71 | }
|
72 | `;
|
73 |
|
74 | // Provide resolver functions for your schema fields
|
75 | const resolvers = {
|
76 | Query: {
|
77 | hello: () => 'Hello world!'
|
78 | }
|
79 | };
|
80 |
|
81 | const server: ApolloServer = new ApolloServer({ typeDefs, resolvers });
|
82 |
|
83 | // ...
|
84 |
|
85 | const cfg: RouterConfig = RouterUtil.openApiYamlToRouterConfig(yamlString, handlers, authorizers, options);
|
86 |
|
87 | // ...
|
88 | cfg.apolloServer = server;
|
89 | cfg.apolloCreateHandlerOptions = {
|
90 | origin: '*',
|
91 | credentials: true
|
92 | } as CreateHandlerOptions;
|
93 | cfg.apolloRegex = new RegExp('.*graphql.*');
|
94 | ```
|
95 |
|
96 | # Usage
|
97 |
|
98 | ## Using WebHandler to simplify the Lambda
|
99 |
|
100 | You will configure a RouterConfig, and then create a WebHandler from that. Your lambda
|
101 | function should look like:
|
102 |
|
103 | ```
|
104 | const handler: Handler = (event: APIGatewayEvent, context: Context, callback: Callback) => {
|
105 | const routerConfig: RouterConfig = getMyRouterConfig(); // Implement this function
|
106 | const commonHandler: WebHandler = new WebHandler(routerConfig);
|
107 | commonHandler.lambdaHandler(event, context, callback);
|
108 | };
|
109 |
|
110 | export {handler};
|
111 |
|
112 | ```
|
113 |
|
114 | ## Using auth/AuthHandler to simplify a JWT token based auth
|
115 |
|
116 | Your auth lambda should look like this (I here assume you are storing your encryption key in AWS
|
117 | System Manager so you can keep it encrypted at rest, which you definitely should be doing):
|
118 |
|
119 | ```
|
120 |
|
121 | import {AuthHandler} from '@bitblit/epsilon/dist/auth/auth-handler';
|
122 | import {Callback, Context, CustomAuthorizerEvent, Handler} from 'aws-lambda';
|
123 | import {EnvironmentService} from '@bitblit/ratchet/dist/aws/environment-service';
|
124 | import 'reflect-metadata';
|
125 |
|
126 | const handler: Handler = (event: CustomAuthorizerEvent, context: Context, callback: Callback) => {
|
127 |
|
128 | EnvironmentService.getConfig('MyConfigurationName').then(cfg => {
|
129 | const commonAuth: AuthHandler = new AuthHandler('api.mycompany.com', cfg['encryptionKey']);
|
130 | commonAuth.lambdaHandler(event, context, callback);
|
131 | });
|
132 | };
|
133 |
|
134 | export {handler};
|
135 |
|
136 | ```
|
137 |
|
138 | This will pass through anyone with a valid JWT token. Note that Epsilon doesn't yet support role based
|
139 | filtering in this version.
|
140 |
|
141 | To create valid JWT tokens, your authentication endpoint can use the **auth/WebTokenManipulator** class like so
|
142 | (after you have verified the users principal/credentials pair) :
|
143 |
|
144 | ```
|
145 | // Other authentication stuff happens up here.
|
146 | const email: string = 'user-email@test.com';
|
147 | const roles: string[] = ['USER','NOT-AN-ADMIN'];
|
148 | const userData: any = {'other': 'stuff'};
|
149 | const myConfig: any = await EnvironmentService.getConfig('MyConfigurationName'); // same as above
|
150 | const encryptionKey: string = cfg['encryptionKey'];
|
151 | const adminUser: any = null; // Set this if the user is an admin doing run-as (this is the admin user)
|
152 | const expSec: number = 3600; // How long until this token expires in seconds
|
153 |
|
154 | const tokenHandler: WebTokenManipulator = new WebTokenManipulator(encryptionKey, 'api.mycompany.com');
|
155 | const token: string = tokenHandler.createJWTString(email, userData, roles, expSec, admin);
|
156 |
|
157 | ```
|
158 |
|
159 | # Notes on adding a new gateway/stage
|
160 |
|
161 | You'll need to auth the gateway to hit the lambda (yes, as of 2018-10-13 this is still ugly) :
|
162 |
|
163 | ```
|
164 | aws lambda add-permission --function-name "arn:aws:lambda:us-east-1:{accountId}:function:{lambda-function-name}"
|
165 | --source-arn "arn:aws:execute-api:us-east-1:{account number}:{api id}/*/*/*"
|
166 | --principal apigateway.amazonaws.com
|
167 | --statement-id b57d8a0f-08e5-407c-9093-47d7e8e840bc
|
168 | --action lambda:InvokeFunction
|
169 |
|
170 | ```
|
171 |
|
172 | And you'll need to remember to go to IAM / Keys and authorize the new stack user to use your KMS key (if you are
|
173 | using KMS to encrypt your config via SystemManager, which you should be doing)
|