# API Gateway
The API Gateway is the public part of our whole system. It handles login session, permission checking, rate Limiting, param validation, param transformation and other middlewares to safeguard the system.
We are using koa as our HTTP Server.
# Versed middleware
A versed middleware is in the form of
type Middleware = (ctx: Context, next: Function) => Promise<any>;
The middleware will be used for any transport (including both http and websocket)
# API List
The api list is under src/services/api/endpoints. The API list define
all the public endpoint and will automatically generate
the API documentation based on our definition.
# Definition Format
An API definition would be:
import Joi from '@hapi/joi';
import { APIGroup } from '../routes/'
import { TwoFA } from '../policies/2fa'
import { RateLimiter } from '../policies/ratelimit'
import { OwnerCheck, LoginCheck, PermissionCheck } from '../policies/checkers'
import { Or, And } from '../policies/util'
export default APIGroup("user", [{
method: "PATCH",
path: "/user/{userId}",
handlerId: "user.update", // required when handler is a middleware
handler: "user.update", // The micro-service action or a middleware: async (ctx: Versed.Context, next) => any
securityPolicy: And(
RateLimiter({ points: 10, duration: 60 }), // can call this endpoint 10 times per 60 seconds per ip
Or(
PermissionCheck('user', 'write'),
And(LoginCheck(), OwnerCheck()),
),
TwoFA({
sms: {
skipIfNotExist: true,
},
email: {
skipIfNotExist: true,
},
code: {
skipIfNotExist: true,
},
})
),
request: {
query: Joi.object({
someParam: Joi.string().required().description("Description of this param")
}),
cookie: Joi.object({
lang: Joi.string().default('en-US').description("The language code of the user, e.g. 'en-US'")
}),
body: Joi.object({
name: Joi.string().description("Full name of the user"),
phone: Joi.string().description("Phone number, format: '+852 5123 4567'"),
}),
},
response: Joi.object({
id: Joi.string().description("User ID"),
name: Joi.string().description("Full name of the user"),
phone: Joi.string().description("Phone number, format: '+852 5123 4567'"),
}).description("an user object"),
}])
To generate the API documentation, run j docs:api
To watch your file change and generate the API documentation
real time, run j docs:api-dev;
# Security policy
A security policy is a versed middleware. When it pass, it should call await next().
If it fail, it should throw an error.
If it want to intercept the response, it should skip calling next(),
and return the response.
# developing transport dependent policy
If you want to develop a policy related to transport data
please write the transport middleware separately and add the data into Context.
This approach ensures the policy is independent to transport.
For example: A policy want to check if the http header contains a token.
We need to write an http middleware to get the value from http header and
set the value to Context meta. Then write a policy to read the value from Context.
# Permission
TODO
# API Error
The returned error is in the following format:
{
"status": 500,
"code": "NotImplementedError",
"message": "This endpoint is not implemented",
"data": {
"endpoint": "user.Update"
}
}
# RateLimiter
For API details, please refer to the comments in the code of policies/ratelimit package
# Prometheus metrics
We will support metrics on the status code of each endpoint TODO: in progress
# Websocket
(Websocket support is not ready yet)
For websocket, we define a similar api definition format:
import Joi from '@hapi/joi';
import { APIGroup } from '../routes/'
import { TwoFA } from '../policies/2fa'
import { InMemoryRateLimiter } from '../policies/ratelimit'
import { SocketPermissionCheck } from '../policies/checkers'
import { Or, And } from '../policies/util'
export default APIGroup("user", [{
method: "SOCKET", // use SOCKET for websocket format
path: "/user/update", // this is the event name
handlerId: "user.update", // required when handler is a function
handler: "user.update", // The micro-service action or a function: async (ctx: Versed.Context, next) => any
// For socket, we support securityPolicy, but we should avoid using it
// Instead, we should handle the security part at the beginning of the connection
securityPolicy: And(
InMemoryRateLimiter({ points: 10, duration: 60 }), // can call this endpoint 10 times per 60 seconds per ip
SocketPermissionCheck('user', 'write'),
),
request: {
query: Joi.object({
someParam: Joi.string().required().description("Description of this param")
}),
cookie: Joi.object({
lang: Joi.string().default('en-US').description("The language code of the user, e.g. 'en-US'")
}),
body: Joi.object({
name: Joi.string().description("Full name of the user"),
phone: Joi.string().description("Phone number, format: '+852 5123 4567'"),
}),
},
response: Joi.object({
id: Joi.string().description("User ID"),
name: Joi.string().description("Full name of the user"),
phone: Joi.string().description("Phone number, format: '+852 5123 4567'"),
}).description("an user object"),
}])