Documentation ¶
Overview ¶
Package core is the essential package required by all other packages. While all packages are, as much as possible, independent from each other, all of them depend on the core package.
Loggers are not in the core package only for "Separation of concerns" + "Code like a library". Exposing the logging library should not bring all the API related code with it.
Index ¶
- Constants
- Variables
- func AddCorsHeaders(next http.Handler, corsConfig CorsConfig) http.Handler
- func AddJSONHeaders(next http.Handler) http.Handler
- func BuildJWT(claims JwtClaims) (string, error)
- func DecodeJWT(r *http.Request) (*JwtClaims, *ServiceMessage)
- func DoIfAccess(canAccess AccessChecker, authenticatedHandler AuthenticatedHandler) http.Handler
- func GetVar(r *http.Request, varName string) string
- func HandleServerError(w http.ResponseWriter, r *http.Request, err error)
- func LoggerInOutRequest(next http.Handler) http.Handler
- func MongoConnectFromEnvVar(envVarName string, logger logger.Logger) (*mongo.Client, *mongo.Database, error)
- func MongoConnectToDb(mongoDbURI string, logger logger.Logger) (*mongo.Client, *mongo.Database, error)
- func MongoParseDbURI(mongoDbURI string) (string, string)
- func SetupRouter(isMonolithic bool, apis ...*API) *mux.Router
- type API
- func (api *API) AddMiddleware(mw EndpointAdapter)
- func (api *API) AddProtectedEndpoint(url string, httpMethod string, version string, accessChecker AccessChecker, ...)
- func (api *API) AddPublicEndpoint(url string, httpMethod string, version string, publicHandler http.HandlerFunc)
- func (api *API) LoadInRouter(router *mux.Router)
- type APIEndpoint
- type AccessChecker
- type AuthenticatedHandler
- type CorsConfig
- type EndpointAdapter
- type JwtClaims
- type ServiceMessage
- type TrackedEntity
- type URLBuilder
Constants ¶
const ( // APIv1 is the standardisation for first version of an API endpoint APIv1 string = "v1" // APIv2 is the standardisation for second version of an API endpoint APIv2 string = "v2" // APIMonolithic to enable monolithic mode APIMonolithic = true // APIMicroservice to enable microservice mode APIMicroservice = false )
const ( // TrackedCreatedBy is the createdBy key. TrackedEntity is assumed to in "bson:,inline" TrackedCreatedBy = "createdBy" // TrackedCreatedAt is the createdAt key. TrackedEntity is assumed to in "bson:,inline" TrackedCreatedAt = "createdAt" // TrackedUpdatedBy is the updatedBy key. TrackedEntity is assumed to in "bson:,inline" TrackedUpdatedBy = "updatedBy" // TrackedUpdatedAt is the updatedAt key. TrackedEntity is assumed to in "bson:,inline" TrackedUpdatedAt = "updatedAt" )
Variables ¶
var ( // ClientDomain refers to the expected domain of the client application ClientDomain string )
Functions ¶
func AddCorsHeaders ¶
func AddCorsHeaders(next http.Handler, corsConfig CorsConfig) http.Handler
AddCorsHeaders set the usual CORS headers.
It is a special middleware requiring parameters so it cannot use the standard adapter pattern
func AddJSONHeaders ¶
AddJSONHeaders add the required header for accepting and providing JSON
func BuildJWT ¶
BuildJWT generate a JWT from a specific list of claims. List of claims is based on https://tools.ietf.org/html/rfc7519 found through https://auth0.com/docs/tokens/jwt-claims.
HMAC is chosen over RSA to protect against manipulation: https://security.stackexchange.com/a/220190
Generate Token : https://godoc.org/github.com/dgrijalva/jwt-go#example-New--Hmac Custom claims : https://godoc.org/github.com/dgrijalva/jwt-go#NewWithClaims
func DecodeJWT ¶
func DecodeJWT(r *http.Request) (*JwtClaims, *ServiceMessage)
DecodeJWT extracts the claims from a JWT if it is valid. Parse token : https://godoc.org/github.com/dgrijalva/jwt-go#example-Parse--Hmac Custom claims : https://godoc.org/github.com/dgrijalva/jwt-go#ParseWithClaims
11-Apr-2020: Make authentication check 100% stateless for a microservice-ready architecture by removing the check in the database of the JWT status: a pure JWT is 100% stateless.
Returns: - JwtClaims : if token is present and valid - int : an `authStatus` code if some check already fails
func DoIfAccess ¶
func DoIfAccess(canAccess AccessChecker, authenticatedHandler AuthenticatedHandler) http.Handler
DoIfAccess ensures that the provided accessChecker passes before proceeding to the authenticatedHandler.
Otherwise the request is rejected with the appropriate error code with error message
func GetVar ¶
GetVar fetch the variable defined in the route.
Such method can be framework-dependent.
func HandleServerError ¶
func HandleServerError(w http.ResponseWriter, r *http.Request, err error)
HandleServerError is the generic way to handle server error: just send a 500 and the message with it
func LoggerInOutRequest ¶
LoggerInOutRequest displays information for inbound request and outbound result
func MongoConnectFromEnvVar ¶
func MongoConnectFromEnvVar(envVarName string, logger logger.Logger) (*mongo.Client, *mongo.Database, error)
MongoConnectFromEnvVar connects to a Mongo database with the provided environment variable
This helper does not guarantee that dotenv files are properly loaded
func MongoConnectToDb ¶
func MongoConnectToDb(mongoDbURI string, logger logger.Logger) (*mongo.Client, *mongo.Database, error)
MongoConnectToDb creates a Mongo client instance from an URI as well as the Mongo database instance depending on the database name in the URI
func MongoParseDbURI ¶
MongoParseDbURI parse the DB URL. It is assumed that argument has the proper format such as mongodb://{user}:{passowrd}@{host}:{port}/{database}
return (connectionString, databaseName)
Types ¶
type API ¶
type API struct {
// contains filtered or unexported fields
}
API exposes the list of handler with a specific URL root
Each APIHandler will be linked to the route URL. URL is defined by an urlBuilder
func NewAPI ¶
NewAPI is the API constructor
- Allowed CORS headers and hosts are, for the moment, "*" - By default, the JSON middleware is always added
func (*API) AddMiddleware ¶
func (api *API) AddMiddleware(mw EndpointAdapter)
AddMiddleware appends a middleware to a specific API.
There is no check about duplicates
func (*API) AddProtectedEndpoint ¶
func (api *API) AddProtectedEndpoint(url string, httpMethod string, version string, accessChecker AccessChecker, handler AuthenticatedHandler)
AddProtectedEndpoint appends a handler which requires an AccessControl
AddHandler also checks if the provided httpMethod is valid.
func (*API) AddPublicEndpoint ¶
func (api *API) AddPublicEndpoint(url string, httpMethod string, version string, publicHandler http.HandlerFunc)
AddPublicEndpoint adds a traditional HTTP handler without access check
func (*API) LoadInRouter ¶
LoadInRouter load all API handlers into the provided routing system.
This method aims at making the whole project framework-agnostic: if the routing framework change, only this method should change
The Middleware could have been added AFTER the different endpoints definition. Consequently, it is better to merge the middleware at the last minute, when loading into the router
This method also list all endpoints and the associated HTTP methods for CORS handling. Doing this way allows a single endpoint to support multiple HTTP method. However, each endpoint, given a method, still need CORS headers
type APIEndpoint ¶
type APIEndpoint struct {
// contains filtered or unexported fields
}
APIEndpoint maps a handler (authenticated or standard) with an URL pattern and a HTTP method
If both "publicHandler" and "protectedHandler" are defined, the public version takes precedence
type AccessChecker ¶
AccessChecker ensures that the provided request is allowed to proceed or not.
Most of the checks are based on the token header. An AccessChecker always assumes that the JWT is properly formed, hence the jwtClaims argument. An AccessChecker's check success often leads to some AuthenticatedHandler to proceed.
var CheckIfAdmin AccessChecker = func(r *http.Request, claims JwtClaims) bool {
return claims.IsAdmin
}
CheckIfAdmin simply checks if the JWT has admin privilege
var CheckIfLogged AccessChecker = func(r *http.Request, claims JwtClaims) bool {
return claims.UserID != ""
}
CheckIfLogged is the AuthChecher ensuring the request has a properly logged-in user
type AuthenticatedHandler ¶
type AuthenticatedHandler func(w http.ResponseWriter, r *http.Request, claims JwtClaims)
AuthenticatedHandler is meant to be the core logic of the handler with the user informations already extracted from the request.
claims are assumed to be always non-nil and always validated beforehand
type CorsConfig ¶
CorsConfig allows a flexible way to handle CORS stuff
type EndpointAdapter ¶
EndpointAdapter (or Decorator design pattern) wrapper consecutive middlewares.
EndpointAdapter must use the standard http.Handler method and, when authentication is required, another type of Adapter might be required
type JwtClaims ¶
type JwtClaims struct { IsAdmin bool `json:"isAdmin"` UserID string `json:"userId"` jwt.StandardClaims }
JwtClaims extends standard claims for our User model.
By including the IsAdmin and UserID fields, authorization check can be based on those values
type ServiceMessage ¶
type ServiceMessage struct { Code int `json:"code"` // Internal code: 0 is fine, any code different from 0 is an error Message string `json:"message"` // Explicit description HTTPStatus int `json:"-"` // HTTP Status code, skipped during serialisation Error error `json:"error,omitempty"` // Error if any }
ServiceMessage is a token to forward the status of an action to the next function / whatever handler processing it.
While it is meant to standardize errors handling, it can also help to identify internal success status thanks to its code
func NewServiceErrorMessage ¶
func NewServiceErrorMessage(err error) *ServiceMessage
NewServiceErrorMessage generate a ServiceMessage from an error. By default, status error is 500
func (*ServiceMessage) Write ¶
func (msg *ServiceMessage) Write(rw http.ResponseWriter, req *http.Request)
Write writes the ServiceMessage into a Http.ReponseWriter and uses the incoming request for logging purpose only
type TrackedEntity ¶
type TrackedEntity struct { CreatedBy primitive.ObjectID `json:"createdBy,omitempty" bson:"createdBy,omitempty"` CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt,omitempty"` UpdatedBy primitive.ObjectID `json:"updatedBy,omitempty" bson:"updatedBy,omitempty"` UpdatedAt time.Time `json:"updatedAt,omitempty" bson:"updatedAt,omitempty"` }
TrackedEntity is the basic structure for all entities which require tracking: user tracking and time tracking
User reference are `primitive.ObjectID` to match "primary keys" of the users collection
func (*TrackedEntity) Equals ¶
func (t *TrackedEntity) Equals(t2 TrackedEntity) bool
Equals check the equality of each field and time fields are compared with a precision of one second
func (*TrackedEntity) PrepareForCreate ¶
func (t *TrackedEntity) PrepareForCreate(claims JwtClaims)
PrepareForCreate set creation related fields
func (*TrackedEntity) PrepareForUpdate ¶
func (t *TrackedEntity) PrepareForUpdate(claims JwtClaims)
PrepareForUpdate updates modification related field before any update based on the UserID provided by the claims
type URLBuilder ¶
URLBuilder defines how an API generates a final endpoint URL given an (optional) root url, a version and the endpoint url
Root URL is optional for microservices-ready structure