148 lines
4.8 KiB
Go
148 lines
4.8 KiB
Go
|
package guardianextension
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"html"
|
||
|
"net/http"
|
||
|
"sync"
|
||
|
|
||
|
"github.com/caddyserver/caddy/v2"
|
||
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||
|
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
||
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||
|
)
|
||
|
|
||
|
const authTokenHeader = "X-GuardianInternal-Token"
|
||
|
|
||
|
// init registers the GuardianCacheHandler module with Caddy and registers the "guardiancache" directive
|
||
|
// for use in the Caddyfile configuration.
|
||
|
func init() {
|
||
|
caddy.RegisterModule((*GuardianCacheHandler)(nil))
|
||
|
httpcaddyfile.RegisterHandlerDirective("guardiancache", parseCaddyfile)
|
||
|
}
|
||
|
|
||
|
// GuardianCacheHandler is a Caddy module that provides a cache handler for the Guardian application.
|
||
|
// It manages a cache of data stored in a JSON file, and validates access tokens.
|
||
|
// The mutex is necessary because maps are not thread-safe for concurrent read (and/or write) operations in Go.
|
||
|
type GuardianCacheHandler struct {
|
||
|
DataFile string `json:"data_file"`
|
||
|
validTokens map[string]bool
|
||
|
data map[string]string
|
||
|
mutex sync.RWMutex
|
||
|
}
|
||
|
|
||
|
// CaddyModule returns module information for use by Caddy.
|
||
|
func (handler *GuardianCacheHandler) CaddyModule() caddy.ModuleInfo {
|
||
|
return caddy.ModuleInfo{
|
||
|
ID: "http.handlers.guardiancache",
|
||
|
New: func() caddy.Module { return new(GuardianCacheHandler) },
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Provision initializes the GuardianCacheHandler by loading data from a JSON file and setting up valid tokens.
|
||
|
func (handler *GuardianCacheHandler) Provision(ctx caddy.Context) error {
|
||
|
handler.validTokens = map[string]bool{
|
||
|
"token1": true,
|
||
|
"token2": true,
|
||
|
"token3": true,
|
||
|
}
|
||
|
|
||
|
errorResponsesOnce.Do(initErrorResponses)
|
||
|
|
||
|
// Load data from file
|
||
|
if err := handler.LoadData(); err != nil {
|
||
|
return fmt.Errorf("failed to load data: %v", err)
|
||
|
}
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// validateAuthToken checks if the auth token is valid. If the token is valid, it returns the escaped token.
|
||
|
// If the token is invalid, it sends an error response and returns an empty string which returns nil in the calling ServeHTTP method.
|
||
|
func (handler *GuardianCacheHandler) validateAuthToken(authToken string) (string, error) {
|
||
|
if authToken == "" {
|
||
|
return "", ErrNoAuthToken
|
||
|
}
|
||
|
|
||
|
if len(authToken) > 256 {
|
||
|
return "", ErrUnauthorized
|
||
|
}
|
||
|
|
||
|
escapedAuthToken := html.EscapeString(authToken)
|
||
|
|
||
|
// TODO: Temporary - this isn't how validity will be checked
|
||
|
if !handler.validTokens[escapedAuthToken] {
|
||
|
return "", ErrUnauthorized
|
||
|
}
|
||
|
|
||
|
return escapedAuthToken, nil
|
||
|
}
|
||
|
|
||
|
// ServeHTTP is the HTTP handler for the GuardianCacheHandler module. It retrieves a value from the loaded JSON data
|
||
|
// based on the "q" query parameter provided in the request. If the q parameter is missing, it returns
|
||
|
// a 400 Bad Request error. If the value is not found in the loaded JSON data, it returns a 404 Not Found error.
|
||
|
// If there is an error interacting with the loaded JSON data, it returns a 500 Internal Server Error.
|
||
|
func (handler *GuardianCacheHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request, next caddyhttp.Handler) error {
|
||
|
// Step 1: Get authentication token from the request header and validate it
|
||
|
_, err := handler.validateAuthToken(request.Header.Get(authTokenHeader))
|
||
|
|
||
|
if err != nil {
|
||
|
sendJSONError(writer, err)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
question := request.URL.Query().Get("q")
|
||
|
if question == "" {
|
||
|
sendJSONError(writer, ErrIncompleteReq)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if len(question) > 256 {
|
||
|
sendJSONError(writer, ErrConnFailed)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
hashedQuestion := questionHash(question)
|
||
|
|
||
|
handler.mutex.RLock()
|
||
|
val, exists := handler.data[hashedQuestion]
|
||
|
handler.mutex.RUnlock()
|
||
|
|
||
|
if !exists {
|
||
|
sendJSONError(writer, ErrConnFailed)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
fmt.Fprintln(writer, val)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// UnmarshalCaddyfile parses the Caddyfile configuration for the GuardianCacheHandler module.
|
||
|
// It sets the path to the JSON data file for the GuardianCacheHandler.
|
||
|
// The parameter "data_file" specifies the location of the JSON file to be loaded.
|
||
|
func (handler *GuardianCacheHandler) UnmarshalCaddyfile(dispenser *caddyfile.Dispenser) error {
|
||
|
for dispenser.Next() {
|
||
|
for dispenser.NextBlock(0) {
|
||
|
switch dispenser.Val() {
|
||
|
case "filepath":
|
||
|
if !dispenser.Args(&handler.DataFile) {
|
||
|
return dispenser.ArgErr()
|
||
|
}
|
||
|
default:
|
||
|
return dispenser.Errf("unrecognized parameter: %s", dispenser.Val())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// The GuardianCacheHandler type implements several interfaces that allow it to be used as a Caddy module:
|
||
|
// - caddy.Provisioner: Allows the module to be provisioned and configured.
|
||
|
// - caddyhttp.MiddlewareHandler: Allows the module to be used as HTTP middleware.
|
||
|
// - caddyfile.Unmarshaler: Allows the module to be configured from a Caddyfile.
|
||
|
var (
|
||
|
_ caddy.Provisioner = (*GuardianCacheHandler)(nil)
|
||
|
_ caddyhttp.MiddlewareHandler = (*GuardianCacheHandler)(nil)
|
||
|
_ caddyfile.Unmarshaler = (*GuardianCacheHandler)(nil)
|
||
|
)
|