♻️ Refactored user HTTP delivery layer

This commit is contained in:
Maxim Lebedev 2023-01-02 08:31:31 +06:00
parent caf25386fd
commit bf41a38014
Signed by: toby3d
GPG Key ID: 1F14E25B7C119FC5
2 changed files with 26 additions and 32 deletions

View File

@ -2,16 +2,15 @@ package http
import ( import (
"encoding/json" "encoding/json"
"net/http"
"strings" "strings"
"github.com/fasthttp/router"
"github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwa"
http "github.com/valyala/fasthttp"
"source.toby3d.me/toby3d/auth/internal/common" "source.toby3d.me/toby3d/auth/internal/common"
"source.toby3d.me/toby3d/auth/internal/domain" "source.toby3d.me/toby3d/auth/internal/domain"
"source.toby3d.me/toby3d/auth/internal/middleware"
"source.toby3d.me/toby3d/auth/internal/token" "source.toby3d.me/toby3d/auth/internal/token"
"source.toby3d.me/toby3d/middleware"
) )
type ( type (
@ -22,20 +21,20 @@ type (
Email string `json:"email,omitempty"` Email string `json:"email,omitempty"`
} }
RequestHandler struct { Handler struct {
config *domain.Config config *domain.Config
tokens token.UseCase tokens token.UseCase
} }
) )
func NewRequestHandler(tokens token.UseCase, config *domain.Config) *RequestHandler { func NewHandler(tokens token.UseCase, config *domain.Config) *Handler {
return &RequestHandler{ return &Handler{
tokens: tokens, tokens: tokens,
config: config, config: config,
} }
} }
func (h *RequestHandler) Register(r *router.Router) { func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
chain := middleware.Chain{ chain := middleware.Chain{
//nolint:exhaustivestruct //nolint:exhaustivestruct
middleware.JWTWithConfig(middleware.JWTConfig{ middleware.JWTWithConfig(middleware.JWTConfig{
@ -44,39 +43,38 @@ func (h *RequestHandler) Register(r *router.Router) {
SigningKey: []byte(h.config.JWT.Secret), SigningKey: []byte(h.config.JWT.Secret),
SigningMethod: jwa.SignatureAlgorithm(h.config.JWT.Algorithm), SigningMethod: jwa.SignatureAlgorithm(h.config.JWT.Algorithm),
Skipper: middleware.DefaultSkipper, Skipper: middleware.DefaultSkipper,
TokenLookup: "header:" + http.HeaderAuthorization + ":Bearer ", TokenLookup: "header:" + common.HeaderAuthorization + ":Bearer ",
}), }),
middleware.LogFmt(), middleware.LogFmt(),
} }
r.GET("/userinfo", chain.RequestHandler(h.handleUserInformation)) chain.Handler(h.handleFunc).ServeHTTP(w, r)
} }
func (h *RequestHandler) handleUserInformation(ctx *http.RequestCtx) { func (h *Handler) handleFunc(w http.ResponseWriter, r *http.Request) {
ctx.SetContentType(common.MIMEApplicationJSONCharsetUTF8) w.Header().Set(common.HeaderContentType, common.MIMEApplicationJSONCharsetUTF8)
ctx.SetStatusCode(http.StatusOK)
encoder := json.NewEncoder(ctx) encoder := json.NewEncoder(w)
tkn, userInfo, err := h.tokens.Verify(ctx, strings.TrimPrefix(string(ctx.Request.Header.Peek( tkn, userInfo, err := h.tokens.Verify(r.Context(),
http.HeaderAuthorization)), "Bearer ")) strings.TrimPrefix(r.Header.Get(common.HeaderAuthorization), "Bearer "))
if err != nil || tkn == nil { if err != nil || tkn == nil {
// WARN(toby3d): If the token is not valid, the endpoint still // WARN(toby3d): If the token is not valid, the endpoint still
// MUST return a 200 Response. // MUST return a 200 Response.
_ = encoder.Encode(err) //nolint:errchkjson _ = encoder.Encode(err) //nolint:errchkjson
w.WriteHeader(http.StatusOK)
return return
} }
if !tkn.Scope.Has(domain.ScopeProfile) { if !tkn.Scope.Has(domain.ScopeProfile) {
ctx.SetStatusCode(http.StatusForbidden)
//nolint:errchkjson //nolint:errchkjson
_ = encoder.Encode(domain.NewError( _ = encoder.Encode(domain.NewError(
domain.ErrorCodeInsufficientScope, domain.ErrorCodeInsufficientScope,
"token with 'profile' scope is required to view profile data", "token with 'profile' scope is required to view profile data",
"https://indieauth.net/source/#user-information", "https://indieauth.net/source/#user-information",
)) ))
w.WriteHeader(http.StatusForbidden)
return return
} }
@ -105,4 +103,5 @@ func (h *RequestHandler) handleUserInformation(ctx *http.RequestCtx) {
} }
_ = encoder.Encode(resp) //nolint:errchkjson _ = encoder.Encode(resp) //nolint:errchkjson
w.WriteHeader(http.StatusOK)
} }

View File

@ -1,20 +1,20 @@
package http_test package http_test
import ( import (
"net/http/httptest"
"path" "path"
"sync" "sync"
"testing" "testing"
"github.com/fasthttp/router"
"github.com/goccy/go-json" "github.com/goccy/go-json"
http "github.com/valyala/fasthttp" http "github.com/valyala/fasthttp"
"source.toby3d.me/toby3d/auth/internal/common"
"source.toby3d.me/toby3d/auth/internal/domain" "source.toby3d.me/toby3d/auth/internal/domain"
"source.toby3d.me/toby3d/auth/internal/profile" "source.toby3d.me/toby3d/auth/internal/profile"
profilerepo "source.toby3d.me/toby3d/auth/internal/profile/repository/memory" profilerepo "source.toby3d.me/toby3d/auth/internal/profile/repository/memory"
"source.toby3d.me/toby3d/auth/internal/session" "source.toby3d.me/toby3d/auth/internal/session"
sessionrepo "source.toby3d.me/toby3d/auth/internal/session/repository/memory" sessionrepo "source.toby3d.me/toby3d/auth/internal/session/repository/memory"
"source.toby3d.me/toby3d/auth/internal/testing/httptest"
"source.toby3d.me/toby3d/auth/internal/token" "source.toby3d.me/toby3d/auth/internal/token"
tokenrepo "source.toby3d.me/toby3d/auth/internal/token/repository/memory" tokenrepo "source.toby3d.me/toby3d/auth/internal/token/repository/memory"
tokenucase "source.toby3d.me/toby3d/auth/internal/token/usecase" tokenucase "source.toby3d.me/toby3d/auth/internal/token/usecase"
@ -38,25 +38,20 @@ func TestUserInfo(t *testing.T) {
deps := NewDependencies(t) deps := NewDependencies(t)
deps.store.Store(path.Join(profilerepo.DefaultPathPrefix, deps.token.Me.String()), deps.profile) deps.store.Store(path.Join(profilerepo.DefaultPathPrefix, deps.token.Me.String()), deps.profile)
r := router.New()
delivery.NewRequestHandler(deps.tokenService, deps.config).Register(r)
client, _, cleanup := httptest.New(t, r.Handler)
t.Cleanup(cleanup)
req := httptest.NewRequest(http.MethodGet, "https://example.com/userinfo", nil) req := httptest.NewRequest(http.MethodGet, "https://example.com/userinfo", nil)
defer http.ReleaseRequest(req) req.Header.Set(common.HeaderAuthorization, "Bearer "+deps.token.AccessToken)
deps.token.SetAuthHeader(req)
resp := http.AcquireResponse() w := httptest.NewRecorder()
defer http.ReleaseResponse(resp) delivery.NewHandler(deps.tokenService, deps.config).ServeHTTP(w, req)
if err := client.Do(req, resp); err != nil { resp := w.Result()
t.Fatal(err)
if exp := http.StatusOK; resp.StatusCode != exp {
t.Errorf("%s %s = %d, want %d", req.Method, req.RequestURI, resp.StatusCode, exp)
} }
result := new(delivery.UserInformationResponse) result := new(delivery.UserInformationResponse)
if err := json.Unmarshal(resp.Body(), result); err != nil { if err := json.NewDecoder(resp.Body).Decode(result); err != nil {
t.Fatal(err) t.Fatal(err)
} }