♻️ Refactored user HTTP delivery layer
This commit is contained in:
parent
caf25386fd
commit
bf41a38014
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue