Skip to content

Commit

Permalink
refactor!: move api authorization middleware in api package, also add…
Browse files Browse the repository at this point in the history
… huma echo adapter, remove RBAC authorization and remove session middleware
  • Loading branch information
iagapie committed Nov 19, 2024
1 parent 8732287 commit 92f3fa3
Show file tree
Hide file tree
Showing 17 changed files with 271 additions and 1,097 deletions.
15 changes: 15 additions & 0 deletions api/api.go → api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,23 @@ import (

"github.com/danielgtaylor/huma/v2"
"github.com/labstack/echo/v4"
"go.uber.org/fx"
)

type Handler interface {
Area() string
Version() string
Register(*echo.Echo, huma.API)
}

func AsHandler(f any) any {
return fx.Annotate(
f,
fx.As(new(Handler)),
fx.ResultTags(`group:"api-handler"`),
)
}

type ErrorTransformerFunc func(context.Context, error) error

type CRUDInfo struct {
Expand Down
175 changes: 175 additions & 0 deletions api/huma_adapter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package api

import (
"context"
"crypto/tls"
"io"
"mime/multipart"
"net/http"
"net/url"
"strings"
"sync"
"time"

"github.com/danielgtaylor/huma/v2"
"github.com/labstack/echo/v4"
)

// MultipartMaxMemory is the maximum memory to use when parsing multipart
// form data.
var MultipartMaxMemory int64 = 8 * 1024

type echoCtx struct {
op *huma.Operation
orig echo.Context
status int
}

// check that echoCtx implements huma.Context
var _ huma.Context = &echoCtx{}

func (c *echoCtx) EchoContext() echo.Context {
return c.orig
}

func (c *echoCtx) Request() *http.Request {
return c.orig.Request()
}

func (c *echoCtx) Operation() *huma.Operation {
return c.op
}

func (c *echoCtx) Context() context.Context {
return c.orig.Request().Context()
}

func (c *echoCtx) Method() string {
return c.orig.Request().Method
}

func (c *echoCtx) Host() string {
return c.orig.Request().Host
}

func (c *echoCtx) RemoteAddr() string {
return c.orig.Request().RemoteAddr
}

func (c *echoCtx) URL() url.URL {
return *c.orig.Request().URL
}

func (c *echoCtx) Param(name string) string {
return c.orig.Param(name)
}

func (c *echoCtx) Query(name string) string {
return c.orig.QueryParam(name)
}

func (c *echoCtx) Header(name string) string {
return c.orig.Request().Header.Get(name)
}

func (c *echoCtx) EachHeader(cb func(name, value string)) {
for name, values := range c.orig.Request().Header {
for _, value := range values {
cb(name, value)
}
}
}

func (c *echoCtx) BodyReader() io.Reader {
return c.orig.Request().Body
}

func (c *echoCtx) GetMultipartForm() (*multipart.Form, error) {
err := c.orig.Request().ParseMultipartForm(MultipartMaxMemory)
return c.orig.Request().MultipartForm, err
}

func (c *echoCtx) SetReadDeadline(deadline time.Time) error {
return huma.SetReadDeadline(c.orig.Response(), deadline)
}

func (c *echoCtx) SetStatus(code int) {
c.status = code
c.orig.Response().WriteHeader(code)
}

func (c *echoCtx) Status() int {
return c.status
}

func (c *echoCtx) AppendHeader(name, value string) {
c.orig.Response().Header().Add(name, value)
}

func (c *echoCtx) SetHeader(name, value string) {
c.orig.Response().Header().Set(name, value)
}

func (c *echoCtx) BodyWriter() io.Writer {
return c.orig.Response()
}

func (c *echoCtx) TLS() *tls.ConnectionState {
return c.orig.Request().TLS
}

func (c *echoCtx) Version() huma.ProtoVersion {
r := c.orig.Request()
return huma.ProtoVersion{
Proto: r.Proto,
ProtoMajor: r.ProtoMajor,
ProtoMinor: r.ProtoMinor,
}
}

func (c *echoCtx) reset(op *huma.Operation, orig echo.Context) {
c.op = op
c.orig = orig
c.status = 0
}

type router interface {
Add(method, path string, handler echo.HandlerFunc, middlewares ...echo.MiddlewareFunc) *echo.Route
}

type echoAdapter struct {
http.Handler
router router
pool *sync.Pool
}

func (a *echoAdapter) Handle(op *huma.Operation, handler func(huma.Context)) {
// Convert {param} to :param
path := op.Path
path = strings.ReplaceAll(path, "{", ":")
path = strings.ReplaceAll(path, "}", "")
a.router.Add(op.Method, path, func(c echo.Context) error {
ctx := a.pool.Get().(*echoCtx)
ctx.reset(op, c)

defer func() {
ctx.reset(nil, nil)
a.pool.Put(ctx)
}()

handler(ctx)
return nil
})
}

func NewAdapter(r *echo.Echo, g *echo.Group) huma.Adapter {
return &echoAdapter{
Handler: r,
router: g,
pool: &sync.Pool{
New: func() any {
return new(echoCtx)
},
},
}
}
62 changes: 62 additions & 0 deletions api/middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package api

import (
"net/http"
"strings"

"github.com/danielgtaylor/huma/v2"
"github.com/labstack/echo/v4"
"go.uber.org/fx"
"go.uber.org/zap"
)

type Middleware struct {
Name string
Middleware func(huma.API) func(huma.Context, func(huma.Context))
}

func NewMiddleware(name string, middleware func(huma.API) func(huma.Context, func(huma.Context))) Middleware {
return Middleware{
Name: name,
Middleware: middleware,
}
}

func AsMiddleware(middleware any) any {
return fx.Annotate(
middleware,
fx.ResultTags(`group:"api-middleware"`),
)
}

func AsMiddlewareFunc(name string, middleware func(huma.API) func(ctx huma.Context, next func(huma.Context))) any {
return AsMiddleware(NewMiddleware(name, middleware))
}

type Authorizer func(huma.Context) error

func AuthorizationMiddleware(authorizer Authorizer, logger *zap.Logger) Middleware {
return NewMiddleware("authorization", func(api huma.API) func(huma.Context, func(huma.Context)) {
unauthorized := func(ctx huma.Context, errs ...error) {
status := http.StatusUnauthorized
message := http.StatusText(status)

if strings.HasPrefix(strings.ToLower(ctx.Header(echo.HeaderAuthorization)), "basic ") {
ctx.SetHeader(echo.HeaderWWWAuthenticate, "basic realm=Restricted")
}

if err := huma.WriteErr(api, ctx, status, message, errs...); err != nil {
logger.Error("huma api: failed to write error", zap.Error(err))
}
}

return func(ctx huma.Context, next func(huma.Context)) {
if err := authorizer(ctx); err != nil {
unauthorized(ctx)
logger.Error("huma api: failed to authorize", zap.Error(err))
return
}
next(ctx)
}
})
}
105 changes: 0 additions & 105 deletions authorization.go

This file was deleted.

Loading

0 comments on commit 92f3fa3

Please sign in to comment.