# 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"),
}])