a quest
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

145 lines
3.1 KiB

package lib
import (
"crypto/ecdsa"
"crypto/sha256"
"crypto/subtle"
"encoding/hex"
"encoding/json"
"fmt"
"math/rand"
"net/http"
"regexp"
"strings"
"time"
jwtgo "github.com/dgrijalva/jwt-go"
"github.com/google/uuid"
routing "github.com/jackwhelpton/fasthttp-routing/v2"
"github.com/jackwhelpton/fasthttp-routing/v2/access"
"github.com/jackwhelpton/fasthttp-routing/v2/cors"
"github.com/jackwhelpton/fasthttp-routing/v2/fault"
log "github.com/sirupsen/logrus"
"github.com/valyala/fasthttp"
)
const (
URL = "/check"
URLHealthz = "/healthz"
)
type Api struct {
Base string
Alg string
Pub *ecdsa.PublicKey
Priv *ecdsa.PrivateKey
Answer []byte
}
type Answer struct {
Answer string `json:"answer"`
Name string `json:"name"`
}
func (a *Api) GetHandler() fasthttp.RequestHandler {
crs := cors.Options{
AllowOrigins: "*",
AllowHeaders: "*",
AllowMethods: "*",
AllowCredentials: true,
}
router := routing.New()
router.Use(
access.Logger(log.Debugf),
cors.Handler(crs),
fault.PanicHandler(log.Warnf),
)
base := strings.TrimSuffix(a.Base, "/")
api := router.Group(base)
api.Post(URL, a.checkAnswer)
api.Get(URLHealthz, a.healthCheck)
return router.HandleRequest
}
func (a *Api) healthCheck(ctx *routing.Context) (err error) {
ctx.Response.Header.Set("Content-Type", "application/json")
_, err = ctx.WriteString("{\"health\":\"ok\"}")
if err != nil {
return routing.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return
}
func (a *Api) checkAnswer(ctx *routing.Context) (err error) {
now := time.Now()
id := uuid.New()
answer := &Answer{}
err = json.Unmarshal(ctx.Request.Body(), answer)
if err != nil {
return routing.NewHTTPError(http.StatusBadRequest, err.Error())
}
cleaner := regexp.MustCompile("[[:^alnum:]]")
pure := cleaner.ReplaceAllLiteralString(answer.Answer, "")
pure = strings.ToLower(pure)
hash := sha256.New()
hash.Write([]byte(pure))
hexed := make([]byte, hex.EncodedLen(sha256.Size))
hex.Encode(hexed, hash.Sum(nil))
log.Debugf("req answer, %v", answer)
log.Debugf("req hashed, %s", hexed)
time.Sleep(time.Second + (time.Millisecond * time.Duration(rand.Intn(500))))
valid := subtle.ConstantTimeCompare(a.Answer, hexed)
if valid == 0 {
return routing.NewHTTPError(http.StatusForbidden)
}
claims := &jwtgo.StandardClaims{
Id: id.String(),
Subject: answer.Name,
Issuer: "a quest",
IssuedAt: now.Unix(),
}
method := jwtgo.GetSigningMethod(a.Alg)
token := jwtgo.NewWithClaims(method, claims)
jwt, err := token.SignedString(a.Priv)
if err != nil {
return routing.NewHTTPError(http.StatusInternalServerError, err.Error())
}
ctx.Response.Header.Set("Content-Type", "application/json")
_, err = ctx.WriteString(fmt.Sprintf(`
["to find the next level check the subject alternative name",
"and use there this key to continue",
"%s"]
`, jwt))
if err != nil {
return routing.NewHTTPError(http.StatusInternalServerError, err.Error())
}
return
}
func (a *Api) getPrivKey(t *jwtgo.Token) (interface{}, error) {
return a.Priv, nil
}
func (a *Api) getKey(t *jwtgo.Token) (interface{}, error) {
return a.Pub, nil
}