g8

package module
v1.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 14, 2022 License: MIT Imports: 7 Imported by: 2

README

g8

test Go Report Card codecov Go version Go Reference Follow TwiN

g8, pronounced gate, is a simple Go library for protecting HTTP handlers.

Tired of constantly re-implementing a security layer for each application? Me too, that's why I made g8.

Installation

go get -u github.com/TwiN/g8

Usage

Because the entire purpose of g8 is to NOT waste time configuring the layer of security, the primary emphasis is to keep it as simple as possible.

Simple

Just want a simple layer of security without the need for advanced permissions? This configuration is what you're looking for.

authorizationService := g8.NewAuthorizationService().WithToken("mytoken")
gate := g8.New().WithAuthorizationService(authorizationService)

router := http.NewServeMux()
router.Handle("/unprotected", yourHandler)
router.Handle("/protected", gate.Protect(yourHandler))

http.ListenAndServe(":8080", router)

The endpoint /protected is now only accessible if you pass the header Authorization: Bearer mytoken.

If you use http.HandleFunc instead of http.Handle, you may use gate.ProtectFunc(yourHandler) instead.

If you're not using the Authorization header, you can specify a custom token extractor. This enables use cases like Protecting a handler using session cookie

Advanced permissions

If you have tokens with more permissions than others, g8's permission system will make managing authorization a breeze.

Rather than registering tokens, think of it as registering clients, the only difference being that clients may be configured with permissions while tokens cannot.

authorizationService := g8.NewAuthorizationService().WithClient(g8.NewClient("mytoken").WithPermission("admin"))
gate := g8.New().WithAuthorizationService(authorizationService)

router := http.NewServeMux()
router.Handle("/unprotected", yourHandler)
router.Handle("/protected-with-admin", gate.ProtectWithPermissions(yourHandler, []string{"admin"}))

http.ListenAndServe(":8080", router)

The endpoint /protected-with-admin is now only accessible if you pass the header Authorization: Bearer mytoken, because the client with the token mytoken has the permission admin. Note that the following handler would also be accessible with that token:

router.Handle("/protected", gate.Protect(yourHandler))

To clarify, both clients and tokens have access to handlers that aren't protected with extra permissions, and essentially, tokens are registered as clients with no extra permissions in the background.

Creating a token like so:

authorizationService := g8.NewAuthorizationService().WithToken("mytoken")

is the equivalent of creating the following client:

authorizationService := g8.NewAuthorizationService().WithClient(g8.NewClient("mytoken"))
With client provider

A client provider's task is to retrieve a Client from an external source (e.g. a database) when provided with a token. You should use a client provider when you have a lot of tokens and it wouldn't make sense to register all of them using AuthorizationService's WithToken/WithTokens/WithClient/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.

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
})
authorizationService := g8.NewAuthorizationService().WithClientProvider(clientProvider)
gate := g8.New().WithAuthorizationService(authorizationService)

You can also configure the client provider to cache the output of the function you provide to retrieve clients by token:

clientProvider := g8.NewClientProvider(...).WithCache(ttl, maxSize)

Since g8 leverages TwiN/gocache, you can also use gocache's constants for configuring the TTL and the maximum size:

  • Setting the TTL to gocache.NoExpiration (-1) will disable the TTL.
  • Setting the maximum size to gocache.NoMaxSize (0) will disable the maximum cache size

If you're using a TTL and have a lot of tokens (100k+), you may want to use clientProvider.StartJanitor() to allow the cache to passively delete expired entries. If you have to re-initialize the client provider after the janitor has been started, make sure to stop the janitor first (clientProvider.StopJanitor()). This is because the janitor runs on a separate goroutine, thus, if you were to re-create a client provider and re-assign it, the old client provider would still exist in memory with the old cache. I'm only specifying this for completeness, because for the overwhelming majority of people, the gate will be created on application start and never modified again until the application shuts down, in which case, you don't even need to worry about stopping the janitor.

To avoid any misunderstandings, using a client provider is not mandatory. If you only have a few tokens and you can load them on application start, you can just leverage AuthorizationService's WithToken/WithTokens/WithClient/WithClients.

AuthorizationService

As the previous examples may have hinted, there are several ways to create clients. The one thing they have in common is that they all go through AuthorizationService, which is in charge of both managing clients and determining whether a request should be blocked or allowed through.

Function Description
WithToken Creates a single static client with no extra permissions
WithTokens Creates a slice of static clients with no extra permissions
WithClient Creates a single static client
WithClients Creates a slice of static clients
WithClientProvider Creates a client provider which will allow a fallback to a dynamic source (e.g. to a database) when a static client is not found

Except for WithClientProvider, every functions listed above can be called more than once. As a result, you may safely perform actions like this:

authorizationService := g8.NewAuthorizationService().
    WithToken("123").
    WithToken("456").
    WithClient(g8.NewClient("789").WithPermission("admin"))
gate := g8.New().WithAuthorizationService(authorizationService)

Be aware that g8.Client supports a list of permissions as well. You may call WithPermission several times, or call WithPermissions with a slice of permissions instead.

Permissions

Unlike client permissions, handler permissions are requirements.

A client may have as many permissions as you want, but for said client to have access to a handler protected by permissions, the client must have all permissions defined by said handler in order to have access to it.

In other words, a client with the permissions create, read, update and delete would have access to all of these handlers:

gate := g8.New().WithAuthorizationService(g8.NewAuthorizationService().WithClient(g8.NewClient("mytoken").WithPermissions([]string{"create", "read", "update", "delete"})))
router := http.NewServeMux()
router.Handle("/", gate.Protect(homeHandler)) // equivalent of gate.ProtectWithPermissions(homeHandler, []string{})
router.Handle("/create", gate.ProtectWithPermissions(createHandler, []string{"create"}))
router.Handle("/read", gate.ProtectWithPermissions(readHandler, []string{"read"}))
router.Handle("/update", gate.ProtectWithPermissions(updateHandler, []string{"update"}))
router.Handle("/delete", gate.ProtectWithPermissions(deleteHandler, []string{"delete"}))
router.Handle("/crud", gate.ProtectWithPermissions(crudHandler, []string{"create", "read", "update", "delete"}))

But it would not have access to the following handler, because while mytoken has the read permission, it does not have the backup permission:

router.Handle("/backup", gate.ProtectWithPermissions(&testHandler{}, []string{"read", "backup"}))

If you're using an HTTP library that supports middlewares like mux, you can protect an entire group of handlers instead using gate.Protect or gate.PermissionMiddleware():

router := mux.NewRouter()

userRouter := router.PathPrefix("/").Subrouter()
userRouter.Use(gate.Protect)
userRouter.HandleFunc("/api/v1/users/me", getUserProfile).Methods("GET")
userRouter.HandleFunc("/api/v1/users/me/friends", getUserFriends).Methods("GET")
userRouter.HandleFunc("/api/v1/users/me/email", updateUserEmail).Methods("PATCH")

adminRouter := router.PathPrefix("/").Subrouter()
adminRouter.Use(gate.PermissionMiddleware("admin"))
adminRouter.HandleFunc("/api/v1/users/{id}/ban", banUserByID).Methods("POST")
adminRouter.HandleFunc("/api/v1/users/{id}/delete", deleteUserByID).Methods("DELETE")

Rate limiting

To add a rate limit of 100 requests per second:

gate := g8.New().WithRateLimit(100)

Accessing the token from the protected handlers

If you need to access the token from the handlers you are protecting with g8, you can retrieve it from the request context by using the key g8.TokenContextKey:

http.Handle("/handle", gate.ProtectFunc(func(w http.ResponseWriter, r *http.Request) {
    token, _ := r.Context().Value(g8.TokenContextKey).(string)
    // ...
}))

Examples

If you want to only allow authenticated users to access a handler, you can use a custom token extractor function combined with a client provider.

First, we'll create a function to extract the session ID from the session cookie. While a session ID does not theoretically refer to a token, g8 uses the term token as a blanket term to refer to any string that can be used to identify a client.

customTokenExtractorFunc := func(request *http.Request) string {
    sessionCookie, err := request.Cookie("session")
    if err != nil {
        return ""
    }
    return sessionCookie.Value
}

Next, we need to create a client provider that will validate our token, which refers to the session ID in this case.

clientProvider := g8.NewClientProvider(func(token string) *g8.Client {
    // We'll assume that the following function calls your database and validates whether the session is valid.
    isSessionValid := database.CheckIfSessionIsValid(token)
    if !isSessionValid {
        return nil // Returning nil will cause the gate to return a 401 Unauthorized.
    }
    // You could also retrieve the user and their permissions if you wanted instead, but for this example,
    // all we care about is confirming whether the session is valid or not.
    return g8.NewClient(token)
})

Keep in mind that you can get really creative with the client provider above. For instance, you could refresh the session's expiration time, which will allow the user to stay logged in for as long as they're active.

You're also not limited to using something stateful like the example above. You could use a JWT and have your client provider validate said JWT.

Finally, we can create the authorization service and the gate:

authorizationService := g8.NewAuthorizationService().WithClientProvider(clientProvider)
gate := g8.New().WithAuthorizationService(authorizationService).WithCustomTokenExtractor(customTokenExtractorFunc)

If you need to access the token (session ID in this case) from the protected handlers, you can retrieve it from the request context by using the key g8.TokenContextKey:

http.Handle("/handle", gate.ProtectFunc(func(w http.ResponseWriter, r *http.Request) {
    sessionID, _ := r.Context().Value(g8.TokenContextKey).(string)
    // ...
}))
Using a custom header

The logic is the same as the example above:

customTokenExtractorFunc := func(request *http.Request) string {
    return request.Header.Get("X-API-Token")
}

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
})
authorizationService := g8.NewAuthorizationService().WithClientProvider(clientProvider)
gate := g8.New().WithAuthorizationService(authorizationService).WithCustomTokenExtractor(customTokenExtractorFunc)

Documentation

Index

Constants

View Source
const (
	// AuthorizationHeader is the header in which g8 looks for the authorization bearer token
	AuthorizationHeader = "Authorization"

	// DefaultUnauthorizedResponseBody is the default response body returned if a request was sent with a missing or invalid token
	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

View Source
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 NewClient

func NewClient(token string) *Client

NewClient creates a Client with a given token

func NewClientWithPermissions

func NewClientWithPermissions(token string, permissions []string) *Client

NewClientWithPermissions creates a Client with a slice of permissions Equivalent to using NewClient and WithPermissions

func (Client) HasPermission

func (client Client) HasPermission(permissionRequired string) bool

HasPermission checks whether a client has a given permission

func (Client) HasPermissions

func (client Client) HasPermissions(permissionsRequired []string) bool

HasPermissions checks whether a client has the all permissions passed

func (*Client) WithPermission

func (client *Client) WithPermission(permission string) *Client

WithPermission adds a permission to a client

func (*Client) WithPermissions

func (client *Client) WithPermissions(permissions []string) *Client

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 New added in v1.2.0

func New() *Gate

New creates a new Gate.

func NewGate deprecated

func NewGate(authorizationService *AuthorizationService) *Gate

Deprecated: use New instead.

func (*Gate) ExtractTokenFromRequest added in v1.3.0

func (gate *Gate) ExtractTokenFromRequest(request *http.Request) string

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

func (gate *Gate) PermissionMiddleware(permissions ...string) func(http.Handler) http.Handler

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

func (gate *Gate) Protect(handler http.Handler) http.Handler

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

func (gate *Gate) ProtectWithPermission(handler http.Handler, permission string) http.Handler

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

func (gate *Gate) ProtectWithPermissions(handler http.Handler, permissions []string) http.Handler

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

func (gate *Gate) WithCustomUnauthorizedResponseBody(unauthorizedResponseBody []byte) *Gate

WithCustomUnauthorizedResponseBody sets a custom response body when Gate determines that a request must be blocked

func (*Gate) WithRateLimit

func (gate *Gate) WithRateLimit(maximumRequestsPerSecond int) *Gate

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)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL