Documentation ¶
Index ¶
- Constants
- Variables
- type AuthorizationService
- func (authorizationService *AuthorizationService) IsAuthorized(token string, permissionsRequired []string) bool
- func (authorizationService *AuthorizationService) WithClient(client *Client) *AuthorizationService
- func (authorizationService *AuthorizationService) WithClientProvider(provider *ClientProvider) *AuthorizationService
- func (authorizationService *AuthorizationService) WithClients(clients []*Client) *AuthorizationService
- func (authorizationService *AuthorizationService) WithToken(token string) *AuthorizationService
- func (authorizationService *AuthorizationService) WithTokens(tokens []string) *AuthorizationService
- type Client
- type ClientProvider
- type Gate
- func (gate *Gate) ExtractTokenFromRequest(request *http.Request) string
- func (gate *Gate) PermissionMiddleware(permissions ...string) func(http.Handler) http.Handler
- func (gate *Gate) Protect(handler http.Handler) http.Handler
- func (gate *Gate) ProtectFunc(handlerFunc http.HandlerFunc) http.HandlerFunc
- func (gate *Gate) ProtectFuncWithPermission(handlerFunc http.HandlerFunc, permission string) http.HandlerFunc
- func (gate *Gate) ProtectFuncWithPermissions(handlerFunc http.HandlerFunc, permissions []string) http.HandlerFunc
- func (gate *Gate) ProtectWithPermission(handler http.Handler, permission string) http.Handler
- func (gate *Gate) ProtectWithPermissions(handler http.Handler, permissions []string) http.Handler
- func (gate *Gate) WithAuthorizationService(authorizationService *AuthorizationService) *Gate
- func (gate *Gate) WithCustomTokenExtractor(customTokenExtractorFunc func(request *http.Request) string) *Gate
- func (gate *Gate) WithCustomUnauthorizedResponseBody(unauthorizedResponseBody []byte) *Gate
- func (gate *Gate) WithRateLimit(maximumRequestsPerSecond int) *Gate
- type RateLimiter
Constants ¶
const ( // AuthorizationHeader is the header in which g8 looks for the authorization bearer token AuthorizationHeader = "Authorization" DefaultUnauthorizedResponseBody = "token is missing or invalid" // DefaultTooManyRequestsResponseBody is the default response body returned if a request exceeded the allowed rate limit DefaultTooManyRequestsResponseBody = "too many requests" // TokenContextKey is the key used to store the token in the context. TokenContextKey = "g8.token" )
Variables ¶
var ( // ErrNoExpiration is the error returned by ClientProvider.StartCacheJanitor if there was an attempt to start the // janitor despite no expiration being configured. // To clarify, this is because the cache janitor is only useful when an expiration is set. ErrNoExpiration = errors.New("no point starting the janitor if the TTL is set to not expire") // ErrCacheNotInitialized is the error returned by ClientProvider.StartCacheJanitor if there was an attempt to start // the janitor despite the cache not having been initialized using ClientProvider.WithCache ErrCacheNotInitialized = errors.New("cannot start janitor because cache is not configured") )
Functions ¶
This section is empty.
Types ¶
type AuthorizationService ¶
type AuthorizationService struct {
// contains filtered or unexported fields
}
AuthorizationService is the service that manages client/token registry and client fallback as well as the service that determines whether a token meets the specific requirements to be authorized by a Gate or not.
func NewAuthorizationService ¶
func NewAuthorizationService() *AuthorizationService
NewAuthorizationService creates a new AuthorizationService
func (*AuthorizationService) IsAuthorized ¶
func (authorizationService *AuthorizationService) IsAuthorized(token string, permissionsRequired []string) bool
IsAuthorized checks whether a client with a given token exists and has the permissions required.
If permissionsRequired is nil or empty and a client with the given token exists, said client will have access to all handlers that are not protected by a given permission.
func (*AuthorizationService) WithClient ¶
func (authorizationService *AuthorizationService) WithClient(client *Client) *AuthorizationService
WithClient is used to specify a single client for which authorization will be granted
When compared to WithToken, the advantage of using this function is that you may specify the client's permissions and thus, be a lot more granular with what endpoint a token has access to.
In other words, if you were to do the following:
gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithClient(g8.NewClient("12345").WithPermission("mod")))
The following handlers would be accessible with the token 12345:
router.Handle("/1st-handler", gate.ProtectWithPermissions(yourHandler, []string{"mod"})) router.Handle("/2nd-handler", gate.Protect(yourOtherHandler))
But not this one, because the user does not have the permission "admin":
router.Handle("/3rd-handler", gate.ProtectWithPermissions(yetAnotherHandler, []string{"admin"}))
Calling this function multiple times will add multiple clients, though you may want to use WithClients instead if you plan to add multiple clients
func (*AuthorizationService) WithClientProvider ¶
func (authorizationService *AuthorizationService) WithClientProvider(provider *ClientProvider) *AuthorizationService
WithClientProvider allows specifying a custom provider to fetch clients by token.
For example, you can use it to fallback to making a call in your database when a request is made with a token that hasn't been specified via WithToken, WithTokens, WithClient or WithClients.
func (*AuthorizationService) WithClients ¶
func (authorizationService *AuthorizationService) WithClients(clients []*Client) *AuthorizationService
WithClients is used to specify a slice of clients for which authorization will be granted
func (*AuthorizationService) WithToken ¶
func (authorizationService *AuthorizationService) WithToken(token string) *AuthorizationService
WithToken is used to specify a single token for which authorization will be granted
The client that will be created from this token will have access to all handlers that are not protected with a specific permission.
In other words, if you were to do the following:
gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithToken("12345"))
The following handler would be accessible with the token 12345:
router.Handle("/1st-handler", gate.Protect(yourHandler))
But not this one would not be accessible with the token 12345:
router.Handle("/2nd-handler", gate.ProtectWithPermissions(yourOtherHandler, []string{"admin"}))
Calling this function multiple times will add multiple clients, though you may want to use WithTokens instead if you plan to add multiple clients
If you wish to configure advanced permissions, consider using WithClient instead.
func (*AuthorizationService) WithTokens ¶
func (authorizationService *AuthorizationService) WithTokens(tokens []string) *AuthorizationService
WithTokens is used to specify a slice of tokens for which authorization will be granted
type Client ¶
type Client struct { // Token is the value used to authenticate with the API. Token string // Permissions is a slice of extra permissions that may be used for more granular access control. // // If you only wish to use Gate.Protect and Gate.ProtectFunc, you do not have to worry about this, // since they're only used by Gate.ProtectWithPermissions and Gate.ProtectFuncWithPermissions Permissions []string }
Client is a struct containing both a Token and a slice of extra Permissions that said token has.
func NewClientWithPermissions ¶
NewClientWithPermissions creates a Client with a slice of permissions Equivalent to using NewClient and WithPermissions
func (Client) HasPermission ¶
HasPermission checks whether a client has a given permission
func (Client) HasPermissions ¶
HasPermissions checks whether a client has the all permissions passed
func (*Client) WithPermission ¶
WithPermission adds a permission to a client
func (*Client) WithPermissions ¶
WithPermissions adds a slice of permissions to a client
type ClientProvider ¶
type ClientProvider struct {
// contains filtered or unexported fields
}
ClientProvider has the task of retrieving a Client from an external source (e.g. a database) when provided with a token. It should be used when you have a lot of tokens, and it wouldn't make sense to register all of them using AuthorizationService's WithToken, WithTokens, WithClient or WithClients.
Note that the provider is used as a fallback source. As such, if a token is explicitly registered using one of the 4 aforementioned functions, the client provider will not be used by the AuthorizationService when a request is made with said token. It will, however, be called upon if a token that is not explicitly registered in AuthorizationService is sent alongside a request going through the Gate.
clientProvider := g8.NewClientProvider(func(token string) *g8.Client { // We'll assume that the following function calls your database and returns a struct "User" that // has the user's token as well as the permissions granted to said user user := database.GetUserByToken(token) if user != nil { return g8.NewClient(user.Token).WithPermissions(user.Permissions) } return nil }) gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithClientProvider(clientProvider))
func NewClientProvider ¶
func NewClientProvider(getClientByTokenFunc func(token string) *Client) *ClientProvider
NewClientProvider creates a ClientProvider The parameter that must be passed is a function that the provider will use to retrieve a client by a given token
Example:
clientProvider := g8.NewClientProvider(func(token string) *g8.Client { // We'll assume that the following function calls your database and returns a struct "User" that // has the user's token as well as the permissions granted to said user user := database.GetUserByToken(token) if user == nil { return nil } return g8.NewClient(user.Token).WithPermissions(user.Permissions) }) gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithClientProvider(clientProvider))
func (*ClientProvider) GetClientByToken ¶
func (provider *ClientProvider) GetClientByToken(token string) *Client
GetClientByToken retrieves a client by its token through the provided getClientByTokenFunc.
func (*ClientProvider) StartCacheJanitor ¶
func (provider *ClientProvider) StartCacheJanitor() error
StartCacheJanitor starts the cache janitor, which passively deletes expired cache entries in the background.
Not really necessary unless you have a lot of clients (100000+).
Even without the janitor, active eviction will still happen (i.e. when GetClientByToken is called, but the cache entry for the given token has expired, the cache entry will be automatically deleted and re-fetched from the user-defined getClientByTokenFunc)
func (*ClientProvider) StopCacheJanitor ¶
func (provider *ClientProvider) StopCacheJanitor()
StopCacheJanitor stops the cache janitor
Not required unless your application initializes multiple providers over the course of its lifecycle. In English, that means if you initialize a ClientProvider only once on application start and it stays up until your application shuts down, you don't need to call this function.
func (*ClientProvider) WithCache ¶
func (provider *ClientProvider) WithCache(ttl time.Duration, maxSize int) *ClientProvider
WithCache adds cache options to the ClientProvider.
ttl is the time until the cache entry will expire. A TTL of gocache.NoExpiration (-1) means no expiration maxSize is the maximum amount of entries that can be in the cache at any given time. If a value of gocache.NoMaxSize (0) or less is provided for maxSize, there will be no maximum size.
Example:
clientProvider := g8.NewClientProvider(func(token string) *g8.Client { // We'll assume that the following function calls your database and returns a struct "User" that // has the user's token as well as the permissions granted to said user user := database.GetUserByToken(token) if user != nil { return g8.NewClient(user.Token).WithPermissions(user.Permissions) } return nil }) gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithClientProvider(clientProvider.WithCache(time.Hour, 70000)))
type Gate ¶
type Gate struct {
// contains filtered or unexported fields
}
Gate is lock to the front door of your API, letting only those you allow through.
func NewGate
deprecated
func NewGate(authorizationService *AuthorizationService) *Gate
Deprecated: use New instead.
func (*Gate) ExtractTokenFromRequest ¶ added in v1.3.0
ExtractTokenFromRequest extracts a token from a request.
By default, it extracts the bearer token from the AuthorizationHeader, but if a customTokenExtractorFunc is defined, it will use that instead.
Note that this method is internally used by Protect, ProtectWithPermission, ProtectFunc and ProtectFuncWithPermissions, but it is exposed in case you need to use it directly.
func (*Gate) PermissionMiddleware ¶ added in v1.4.0
PermissionMiddleware is a middleware that behaves like ProtectWithPermission, but it is meant to be used as a middleware for libraries that support such a feature.
For instance, if you are using github.com/gorilla/mux, you can use PermissionMiddleware like so:
router := mux.NewRouter() router.Use(gate.PermissionMiddleware("admin")) router.Handle("/admin/handle", adminHandler)
If you do not want to protect a router with a specific permission, you can use Gate.Protect instead.
func (*Gate) Protect ¶
Protect secures a handler, requiring requests going through to have a valid Authorization Bearer token. Unlike ProtectWithPermissions, Protect will allow access to any registered tokens, regardless of their permissions or lack thereof.
Example:
gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithToken("token")) router := http.NewServeMux() // Without protection router.Handle("/handle", yourHandler) // With protection router.Handle("/handle", gate.Protect(yourHandler))
The token extracted from the request is passed to the handlerFunc request context under the key TokenContextKey
func (*Gate) ProtectFunc ¶
func (gate *Gate) ProtectFunc(handlerFunc http.HandlerFunc) http.HandlerFunc
ProtectFunc secures a handlerFunc, requiring requests going through to have a valid Authorization Bearer token. Unlike ProtectFuncWithPermissions, ProtectFunc will allow access to any registered tokens, regardless of their permissions or lack thereof.
Example:
gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithToken("token")) router := http.NewServeMux() // Without protection router.HandleFunc("/handle", yourHandlerFunc) // With protection router.HandleFunc("/handle", gate.ProtectFunc(yourHandlerFunc))
The token extracted from the request is passed to the handlerFunc request context under the key TokenContextKey
func (*Gate) ProtectFuncWithPermission ¶
func (gate *Gate) ProtectFuncWithPermission(handlerFunc http.HandlerFunc, permission string) http.HandlerFunc
ProtectFuncWithPermission does the same thing as ProtectFuncWithPermissions, but for a single permission instead of a slice of permissions
See ProtectFuncWithPermissions for further documentation
func (*Gate) ProtectFuncWithPermissions ¶
func (gate *Gate) ProtectFuncWithPermissions(handlerFunc http.HandlerFunc, permissions []string) http.HandlerFunc
ProtectFuncWithPermissions secures a handler, requiring requests going through to have a valid Authorization Bearer token as well as a slice of permissions that must be met.
Example:
gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithClient(g8.NewClient("token").WithPermission("admin"))) router := http.NewServeMux() // Without protection router.HandleFunc("/handle", yourHandlerFunc) // With protection router.HandleFunc("/handle", gate.ProtectFuncWithPermissions(yourHandlerFunc, []string{"admin"}))
The token extracted from the request is passed to the handlerFunc request context under the key TokenContextKey
func (*Gate) ProtectWithPermission ¶
ProtectWithPermission does the same thing as ProtectWithPermissions, but for a single permission instead of a slice of permissions
See ProtectWithPermissions for further documentation
func (*Gate) ProtectWithPermissions ¶
ProtectWithPermissions secures a handler, requiring requests going through to have a valid Authorization Bearer token as well as a slice of permissions that must be met.
Example:
gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithClient(g8.NewClient("token").WithPermission("ADMIN"))) router := http.NewServeMux() // Without protection router.Handle("/handle", yourHandler) // With protection router.Handle("/handle", gate.ProtectWithPermissions(yourHandler, []string{"admin"}))
The token extracted from the request is passed to the handlerFunc request context under the key TokenContextKey
func (*Gate) WithAuthorizationService ¶ added in v1.2.0
func (gate *Gate) WithAuthorizationService(authorizationService *AuthorizationService) *Gate
WithAuthorizationService sets the authorization service to use.
If there is no authorization service, Gate will not enforce authorization.
func (*Gate) WithCustomTokenExtractor ¶ added in v1.2.0
func (gate *Gate) WithCustomTokenExtractor(customTokenExtractorFunc func(request *http.Request) string) *Gate
WithCustomTokenExtractor allows the specification of a custom function to extract a token from a request. If a custom token extractor is not specified, the token will be extracted from the Authorization header.
For instance, if you're using a session cookie, you can extract the token from the cookie like so:
authorizationService := g8.NewAuthorizationService() customTokenExtractorFunc := func(request *http.Request) string { sessionCookie, err := request.Cookie("session") if err != nil { return "" } return sessionCookie.Value } gate := g8.New().WithAuthorizationService(authorizationService).WithCustomTokenExtractor(customTokenExtractorFunc)
You would normally use this with a client provider that matches whatever need you have. For example, if you're using a session cookie, your client provider would retrieve the user from the session ID extracted by this custom token extractor.
Note that for the sake of convenience, the token extracted from the request is passed the protected handlers request context under the key TokenContextKey. This is especially useful if the token is in fact a session ID.
func (*Gate) WithCustomUnauthorizedResponseBody ¶
WithCustomUnauthorizedResponseBody sets a custom response body when Gate determines that a request must be blocked
func (*Gate) WithRateLimit ¶
WithRateLimit adds rate limiting to the Gate
If you just want to use a gate for rate limiting purposes:
gate := g8.New().WithRateLimit(50)
type RateLimiter ¶
type RateLimiter struct {
// contains filtered or unexported fields
}
RateLimiter is a fixed rate limiter
func NewRateLimiter ¶
func NewRateLimiter(maximumExecutionsPerSecond int) *RateLimiter
NewRateLimiter creates a RateLimiter
func (*RateLimiter) Try ¶
func (r *RateLimiter) Try() bool
Try updates the number of executions if the rate limit quota hasn't been reached and returns whether the attempt was successful or not.
Returns false if the execution was not successful (rate limit quota has been reached) Returns true if the execution was successful (rate limit quota has not been reached)