podhmo / apikit

api toolkit (WIP)
MIT License
0 stars 0 forks source link

web, auth with barer token #124

Closed podhmo closed 2 years ago

podhmo commented 2 years ago

meta #19

podhmo commented 2 years ago

action function doesn't know request. but in action, login user is needed, maybe.

then using something like getLoginUser interface (or function) as component?

podhmo commented 2 years ago

if so, get component in provider, handle with request as the argument of provider function?(currently not supported yet, primitive arguments and context.Context only)

podhmo commented 2 years ago

or #26 recursive dependency is needed?

podhmo commented 2 years ago

attaching loginRequired middleware is best. auth is web handler layer

podhmo commented 2 years ago

It is cool and handy that authorization in the middleware phase. But this is not suitable with provider abstraction ( the code that middleware using the provided value by provider )

the code something like followings are not happy.

https://gist.github.com/podhmo/5f8fe705128f4b4e4145e157b62b0cf2#file-main-go-L44-L82

podhmo commented 2 years ago

So, simply, adding option that adding external dependencies, something like below.

r.Get("/hello". Hello, web.WithExternalDependencies(LoginRequired))

then Login required is something like this.

func LoginRequired(db *DB) error {
  ... do something
}

and generated code is here

func Handler(getProvider func(*http.Request) (*http.Request, Provider, error)) func(http.ResponseWriter, *http.Request) {
    return func(w http.ResponseWriter, req *http.Request) {
        req, provider, err := getProvider(req)
        if err != nil {
            runtime.HandleResult(w, req, nil, err); return
        }
        var ctx context.Context = req.Context()
        var db *genchi.DB
        {
            db = provider.DB()
        }
        if err := action.LoginRequired(db); err != nil { // added.
            runtime.HandleResult(w, req, nil, err); return
        }
        result, err := action.Hello()
        runtime.HandleResult(w, req, result, err)
    }
}

postscript

podhmo commented 2 years ago

need also request, right?

action.LoginRequired(db, req)
podhmo commented 2 years ago

Of course, internal action requires loginUser. It is OK.

func Submit(loginUser *LoginUser, db *DB, data Data) (*SubmitOutput, error) {
 ...
} 

type LoginUser User

// r.Post("/submit", Submit)

generated code is something like below.

var db *DB
...

var loginUser *LoginUser
{
   var err error
   loginUser, err = provider.LoginUser(db, req)
   if err != nil {
      ...
    }
}
...
result, err := action.Submit(loginUser, db, data)
runtime.HandleResult(w, req, result, err)

recursive components

And LoginRequired() requires *loginUser.

func LoginRequired(loginUser *LoginUser) error {
  if loginUser == nil {
    return fmt.Errorf("401")
  }
  return nil
}

then generated code is changed.

// r.Get("/hello". Hello, web.WithExternalDependencies(LoginRequired))

func Handler(getProvider func(*http.Request) (*http.Request, Provider, error)) func(http.ResponseWriter, *http.Request) {
    return func(w http.ResponseWriter, req *http.Request) {
        req, provider, err := getProvider(req)
        if err != nil {
            runtime.HandleResult(w, req, nil, err)
            return
        }
        var ctx context.Context = req.Context()
        var db *genchi.DB
        {
            db = provider.DB()
        }
        var loginUser *LoginUser
        {
            var err error
            loginUser, err = provider.LoginUser(db, req)
            if err != nil {
                runtime.HandleResult(w, req, nil, err)
                return
            }
        }
        if err := action.LoginRequired(loginUser); err != nil {  // in this case, this if-statement is almost dead-code.
            runtime.HandleResult(w, req, nil, err)
            return
        }
        result, err := action.Hello()
        runtime.HandleResult(w, req, result, err)
    }
}
podhmo commented 2 years ago

bonus point (?)

In addition, if you can create new individual additional metadata attachment option (e.g. the metadata for openAPI doc)

package yourpackage

var mu sync.Mutex
var metaInfoMap = map[*web.Node]*MetaInfo

func WithMetaInfo(info *MetaInfo) web.RoutingOption {
  return func(node *web.Node) {
     mu.Lock()
     defer mu.Unlock()
     metainfoMap[node] = info
  }
}

And use it

r.Get("/foo/{fooId}", GetFoo, yourpackage.WithMetaInfo(&MetaInfo{Examples: "<example json>"}))

and your own loginRequired option

func WithLoginRequired() web.RoutingOption {
  return func(node *web.Node) {
     web.SetExternalDependencies(node, LoginRequired)
     metaInfoMap[node].LoginRequired = true
  }
}

then

r.Get("/foo/{fooId}", GetFoo, yourpackage.WithLoginRequired())
podhmo commented 2 years ago

more comfortable name handling is #131

podhmo commented 2 years ago

adding example is not using barer token, but feature is fulfilled enough.