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