2024-08-13 14:03:57 -07:00
|
|
|
package keydbextension
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2024-08-13 14:10:39 -07:00
|
|
|
"strconv"
|
2024-08-13 14:03:57 -07:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/caddyserver/caddy/v2"
|
|
|
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
2024-08-13 14:58:31 -07:00
|
|
|
"github.com/caddyserver/caddy/v2/caddyconfig/httpcaddyfile"
|
2024-08-13 14:03:57 -07:00
|
|
|
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
|
|
|
"github.com/go-redis/redis/v8"
|
|
|
|
)
|
|
|
|
|
2024-08-13 16:15:15 -07:00
|
|
|
const authTokenHeader = "X-GuardianInternal-Token"
|
|
|
|
|
|
|
|
// init registers the KeyDBHandler module with Caddy and registers the "keydb" directive
|
|
|
|
// for use in the Caddyfile configuration.
|
2024-08-13 14:03:57 -07:00
|
|
|
func init() {
|
|
|
|
caddy.RegisterModule(KeyDBHandler{})
|
2024-08-13 15:19:17 -07:00
|
|
|
httpcaddyfile.RegisterHandlerDirective("keydb", parseCaddyfile)
|
2024-08-13 14:03:57 -07:00
|
|
|
}
|
|
|
|
|
2024-08-13 16:15:15 -07:00
|
|
|
// KeyDBHandler is a Caddy module that provides a HTTP handler for interacting with a KeyDB server.
|
|
|
|
// It allows retrieving values from the KeyDB server based on a provided hash parameter.
|
2024-08-13 14:03:57 -07:00
|
|
|
type KeyDBHandler struct {
|
2024-08-13 16:15:15 -07:00
|
|
|
Address string `json:"address"`
|
|
|
|
Password string `json:"password"`
|
|
|
|
DB int `json:"db"`
|
|
|
|
client *redis.Client
|
|
|
|
validTokens map[string]bool
|
2024-08-13 14:03:57 -07:00
|
|
|
}
|
|
|
|
|
2024-08-13 16:15:15 -07:00
|
|
|
// CaddyModule returns module information for use by Caddy.
|
2024-08-13 14:03:57 -07:00
|
|
|
func (KeyDBHandler) CaddyModule() caddy.ModuleInfo {
|
|
|
|
return caddy.ModuleInfo{
|
|
|
|
ID: "http.handlers.keydb",
|
|
|
|
New: func() caddy.Module { return new(KeyDBHandler) },
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-08-13 16:15:15 -07:00
|
|
|
// Provision initializes the KeyDBHandler by creating a new Redis client with the configured address, password, and database index.
|
|
|
|
// The Redis client is the most robust and is backwards compatible with KeyDB.
|
|
|
|
func (handler *KeyDBHandler) Provision(ctx caddy.Context) error {
|
|
|
|
// The database index must be between 0 and 15 (inclusive).
|
|
|
|
if handler.DB < 0 || handler.DB > 15 {
|
|
|
|
return fmt.Errorf("invalid db value: %d", handler.DB)
|
2024-08-13 14:34:12 -07:00
|
|
|
}
|
2024-08-13 16:15:15 -07:00
|
|
|
handler.client = redis.NewClient(&redis.Options{
|
|
|
|
Addr: handler.Address,
|
|
|
|
Password: handler.Password,
|
|
|
|
DB: handler.DB,
|
2024-08-13 14:03:57 -07:00
|
|
|
})
|
2024-08-13 16:15:15 -07:00
|
|
|
handler.validTokens = map[string]bool{
|
|
|
|
"token1": true,
|
|
|
|
"token2": true,
|
|
|
|
"token3": true,
|
|
|
|
}
|
2024-08-13 14:03:57 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-08-13 16:15:15 -07:00
|
|
|
// ServeHTTP is the HTTP handler for the KeyDBHandler module. It retrieves a value from the KeyDB server
|
|
|
|
// 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 KeyDB server, it returns a 404 Not Found error.
|
|
|
|
// If there is an error interacting with the KeyDB server, it returns a 500 Internal Server Error.
|
|
|
|
func (handler KeyDBHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request, next caddyhttp.Handler) error {
|
|
|
|
authToken := request.Header.Get(authTokenHeader)
|
|
|
|
if authToken == "" {
|
2024-08-13 16:28:06 -07:00
|
|
|
sendJSONError(writer, ErrNoAuthToken)
|
2024-08-13 16:15:15 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if !handler.validTokens[authToken] {
|
2024-08-13 16:28:06 -07:00
|
|
|
sendJSONError(writer, ErrUnauthorized)
|
2024-08-13 16:15:15 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
question := request.URL.Query().Get("q")
|
|
|
|
if question == "" {
|
2024-08-13 16:28:06 -07:00
|
|
|
sendJSONError(writer, ErrIncompleteReq)
|
2024-08-13 14:03:57 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
|
2024-08-13 16:15:15 -07:00
|
|
|
val, err := handler.client.Get(ctx, question).Result()
|
2024-08-13 14:03:57 -07:00
|
|
|
if err == redis.Nil {
|
2024-08-13 16:28:06 -07:00
|
|
|
sendJSONError(writer, ErrConnFailed)
|
2024-08-13 14:03:57 -07:00
|
|
|
return nil
|
|
|
|
} else if err != nil {
|
2024-08-13 16:28:06 -07:00
|
|
|
sendJSONError(writer, ErrReqNotProcessed)
|
2024-08-13 14:03:57 -07:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-08-13 16:15:15 -07:00
|
|
|
// Append the auth token to the response content
|
|
|
|
responseContent := fmt.Sprintf("%s\nAuth Token: %s", val, authToken)
|
|
|
|
|
|
|
|
// Write the returned value with the appended auth token to the response writer
|
|
|
|
fmt.Fprintln(writer, responseContent)
|
2024-08-13 14:03:57 -07:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-08-13 16:15:15 -07:00
|
|
|
// UnmarshalCaddyfile parses the Caddyfile configuration for the KeyDBHandler module.
|
|
|
|
// It sets the address, password, and database index for the KeyDB connection.
|
|
|
|
// The database index must be between 0 and 15 (inclusive) and is converted to an integer here for the Redis client.
|
|
|
|
func (handler *KeyDBHandler) UnmarshalCaddyfile(dispenser *caddyfile.Dispenser) error {
|
|
|
|
for dispenser.Next() {
|
|
|
|
for dispenser.NextBlock(0) {
|
|
|
|
switch dispenser.Val() {
|
2024-08-13 14:03:57 -07:00
|
|
|
case "address":
|
2024-08-13 16:15:15 -07:00
|
|
|
if !dispenser.Args(&handler.Address) {
|
|
|
|
return dispenser.ArgErr()
|
2024-08-13 14:03:57 -07:00
|
|
|
}
|
|
|
|
case "password":
|
2024-08-13 16:15:15 -07:00
|
|
|
if !dispenser.Args(&handler.Password) {
|
|
|
|
return dispenser.ArgErr()
|
2024-08-13 14:03:57 -07:00
|
|
|
}
|
|
|
|
case "db":
|
2024-08-13 14:46:21 -07:00
|
|
|
var dbString string
|
2024-08-13 16:15:15 -07:00
|
|
|
if !dispenser.Args(&dbString) {
|
|
|
|
return dispenser.ArgErr()
|
2024-08-13 14:03:57 -07:00
|
|
|
}
|
2024-08-13 14:46:21 -07:00
|
|
|
db, err := strconv.Atoi(dbString)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-08-13 16:15:15 -07:00
|
|
|
handler.DB = db
|
2024-08-13 14:03:57 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-08-13 16:15:15 -07:00
|
|
|
// parseCaddyfile creates a new KeyDBHandler instance and initializes it by parsing the Caddyfile configuration.
|
|
|
|
// This function is used by Caddy to load the KeyDBHandler module from the Caddyfile. It's called once during init.
|
|
|
|
func parseCaddyfile(helper httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error) {
|
|
|
|
var handler KeyDBHandler
|
|
|
|
err := handler.UnmarshalCaddyfile(helper.Dispenser)
|
|
|
|
return handler, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// sendJSONError writes a JSON-formatted error response to the provided http.ResponseWriter.
|
2024-08-13 16:28:06 -07:00
|
|
|
func sendJSONError(w http.ResponseWriter, errorKey string) {
|
|
|
|
errorResponse, exists := errorResponses[errorKey]
|
|
|
|
if !exists {
|
|
|
|
errorResponse = errorResponses["ErrUnknown"]
|
|
|
|
}
|
2024-08-13 16:15:15 -07:00
|
|
|
|
2024-08-13 16:28:06 -07:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
w.WriteHeader(getHTTPStatusCode(errorKey))
|
|
|
|
w.Write(errorResponse)
|
2024-08-13 14:58:31 -07:00
|
|
|
}
|
|
|
|
|
2024-08-13 16:15:15 -07:00
|
|
|
// The KeyDBHandler 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.
|
2024-08-13 14:03:57 -07:00
|
|
|
var (
|
|
|
|
_ caddy.Provisioner = (*KeyDBHandler)(nil)
|
|
|
|
_ caddyhttp.MiddlewareHandler = (*KeyDBHandler)(nil)
|
|
|
|
_ caddyfile.Unmarshaler = (*KeyDBHandler)(nil)
|
|
|
|
)
|