diff --git a/LICENSE b/LICENSE index 91bf91b..54683f3 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2023 Flavio Garcia + Copyright 2024 Flavio Garcia Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 5c041b3..a94840d 100644 --- a/README.md +++ b/README.md @@ -27,4 +27,4 @@ initiatives. Available under the This site and all documentation are licensed under [Creative Commons 3.0](http://creativecommons.org/licenses/by/3.0/). -Copyright © 2023 Flavio Gonçalves Garcia +Copyright © 2024 Flavio Garcia diff --git a/service_test.go b/service_test.go index ccd7dd0..ecc4488 100644 --- a/service_test.go +++ b/service_test.go @@ -1,14 +1,27 @@ package peasant import ( - "log" + "math/rand" "net/http" "testing" + "time" "github.com/candango/gopeasant/testrunner" "github.com/stretchr/testify/assert" ) +func randomString(s int) string { + asciiLower := "abcdefghijklmnopqrstuvwxyz" + asciiUpper := "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + digits := "012345679" + chars := []rune(asciiLower + asciiUpper + digits) + r := make([]rune, s) + for i := range r { + r[i] = chars[rand.Intn(len(chars))] + } + return string(r) +} + type InMemoryNonceService struct { nonceMap map[string]*interface{} } @@ -17,62 +30,103 @@ func (s *InMemoryNonceService) Block(resp http.ResponseWriter, req *http.Request) error { return nil } + func (s *InMemoryNonceService) Clear(nonce string) error { + _, ok := s.nonceMap[nonce] + if !ok { + return nil + } + delete(s.nonceMap, nonce) return nil } + func (s *InMemoryNonceService) Consume(resp http.ResponseWriter, req *http.Request, nonce string) (bool, error) { - return false, nil + _, ok := s.nonceMap[nonce] + if !ok { + return false, nil + } + err := s.Clear(nonce) + if err != nil { + return false, err + } + return true, nil } + func (s *InMemoryNonceService) GetNonce(req *http.Request) (string, error) { - return "", nil + nonce := randomString(32) + s.nonceMap[nonce] = nil + ticker := time.NewTicker(2000 * time.Millisecond) + done := make(chan bool) + + go func(nonce string) { + for { + select { + case <-done: + return + case <-ticker.C: + s.Clear(nonce) + done <- true + } + } + }(nonce) + + return nonce, nil } -func (s *InMemoryNonceService) provided(req http.ResponseWriter, - res *http.Request) (bool, error) { + +func (s *InMemoryNonceService) Provided(_ http.ResponseWriter, + _ *http.Request) (bool, error) { return false, nil } type NoncedHandler struct { http.Handler - log.Logger service NonceService } func NewNoncedHandler() *NoncedHandler { h := http.NewServeMux() - h.Handle("/new-nonce", &NonceHandler{}) + h.Handle("/new-nonce", &NoncedHandler{}) + s := &InMemoryNonceService{ + nonceMap: make(map[string]*interface{}), + } return &NoncedHandler{ Handler: h, + service: s, } } -type NonceHandler struct { - service NonceService -} - -func (h *NonceHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) { +func (h *NoncedHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) { method := req.Method if method != http.MethodHead { res.WriteHeader(http.StatusMethodNotAllowed) return } - res.Header().Add("return", "OK") + nonce, err := h.service.GetNonce(req) + if err != nil { + res.WriteHeader(http.StatusInternalServerError) + return + } + res.Header().Add("nonce", nonce) } func TestServer(t *testing.T) { runner := testrunner.NewHttpTestRunner(t).WithHandler(NewNoncedHandler()) t.Run("Head Request tests", func(t *testing.T) { - t.Run("Request OK", func(t *testing.T) { res, err := runner.WithPath("/new-nonce").Head() if err != nil { t.Error(err) } + assert.Equal(t, "200 OK", res.Status) assert.Equal(t, http.NoBody, res.Body) - assert.Equal(t, "OK", res.Header.Get("return")) + assert.Equal(t, 32, len(res.Header.Get("nonce"))) + res, err = runner.WithPath("/new-nonce").Get() + assert.Equal(t, "405 Method Not Allowed", res.Status) + assert.Equal(t, http.NoBody, res.Body) }) }) }