authkit initial
This commit is contained in:
parent
5173b0a43d
commit
134393fbca
43 changed files with 5188 additions and 1 deletions
138
middleware/middleware.go
Normal file
138
middleware/middleware.go
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"git.juancwu.dev/juancwu/authkit"
|
||||
)
|
||||
|
||||
// Options configures auth middleware. Auth is required; the rest fall back
|
||||
// to defaults: BearerExtractor, a JSON 401 on auth failure, and a JSON 403
|
||||
// on authz failure.
|
||||
type Options struct {
|
||||
Auth *authkit.Auth
|
||||
Extractor authkit.Extractor
|
||||
OnUnauth func(w http.ResponseWriter, r *http.Request, err error)
|
||||
OnForbidden func(w http.ResponseWriter, r *http.Request, err error)
|
||||
}
|
||||
|
||||
func (o Options) extractor() authkit.Extractor {
|
||||
if o.Extractor != nil {
|
||||
return o.Extractor
|
||||
}
|
||||
return authkit.BearerExtractor()
|
||||
}
|
||||
|
||||
func (o Options) onUnauth() func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
if o.OnUnauth != nil {
|
||||
return o.OnUnauth
|
||||
}
|
||||
return defaultJSONError(http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
func (o Options) onForbidden() func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
if o.OnForbidden != nil {
|
||||
return o.OnForbidden
|
||||
}
|
||||
return defaultJSONError(http.StatusForbidden)
|
||||
}
|
||||
|
||||
func defaultJSONError(status int) func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
return func(w http.ResponseWriter, _ *http.Request, err error) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
_ = json.NewEncoder(w).Encode(map[string]string{
|
||||
"error": http.StatusText(status),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// RequireSession authenticates the request via an opaque session string. The
|
||||
// extractor is consulted first; if no extractor is set the default Bearer
|
||||
// extractor is used. For cookie-based session lookup, set
|
||||
// Options.Extractor = authkit.CookieExtractor(cfg.SessionCookieName).
|
||||
func RequireSession(opts Options) func(http.Handler) http.Handler {
|
||||
return requireWith(opts, func(r *http.Request, raw string) (*authkit.Principal, error) {
|
||||
return opts.Auth.AuthenticateSession(r.Context(), raw)
|
||||
})
|
||||
}
|
||||
|
||||
// RequireJWT authenticates the request via an HS256 JWT.
|
||||
func RequireJWT(opts Options) func(http.Handler) http.Handler {
|
||||
return requireWith(opts, func(r *http.Request, raw string) (*authkit.Principal, error) {
|
||||
return opts.Auth.AuthenticateJWT(r.Context(), raw)
|
||||
})
|
||||
}
|
||||
|
||||
// RequireAPIKey authenticates the request via an opaque API secret.
|
||||
func RequireAPIKey(opts Options) func(http.Handler) http.Handler {
|
||||
return requireWith(opts, func(r *http.Request, raw string) (*authkit.Principal, error) {
|
||||
return opts.Auth.AuthenticateAPIKey(r.Context(), raw)
|
||||
})
|
||||
}
|
||||
|
||||
// RequireAny tries each method in order until one succeeds. Useful for routes
|
||||
// that accept either a session cookie or an API key.
|
||||
func RequireAny(opts Options, methods ...authkit.AuthMethod) func(http.Handler) http.Handler {
|
||||
if len(methods) == 0 {
|
||||
methods = []authkit.AuthMethod{
|
||||
authkit.AuthMethodSession,
|
||||
authkit.AuthMethodJWT,
|
||||
authkit.AuthMethodAPIKey,
|
||||
}
|
||||
}
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
raw, ok := opts.extractor()(r)
|
||||
if !ok || raw == "" {
|
||||
opts.onUnauth()(w, r, authkit.ErrSessionInvalid)
|
||||
return
|
||||
}
|
||||
var (
|
||||
p *authkit.Principal
|
||||
lastErr error
|
||||
)
|
||||
for _, m := range methods {
|
||||
switch m {
|
||||
case authkit.AuthMethodSession:
|
||||
p, lastErr = opts.Auth.AuthenticateSession(r.Context(), raw)
|
||||
case authkit.AuthMethodJWT:
|
||||
p, lastErr = opts.Auth.AuthenticateJWT(r.Context(), raw)
|
||||
case authkit.AuthMethodAPIKey:
|
||||
p, lastErr = opts.Auth.AuthenticateAPIKey(r.Context(), raw)
|
||||
}
|
||||
if lastErr == nil && p != nil {
|
||||
next.ServeHTTP(w, r.WithContext(withPrincipal(r.Context(), p)))
|
||||
return
|
||||
}
|
||||
}
|
||||
opts.onUnauth()(w, r, lastErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// requireWith is the shared scaffolding for the single-method Require*
|
||||
// middlewares.
|
||||
func requireWith(opts Options, authn func(r *http.Request, raw string) (*authkit.Principal, error)) func(http.Handler) http.Handler {
|
||||
if opts.Auth == nil {
|
||||
panic("authkit/middleware: Options.Auth is required")
|
||||
}
|
||||
extractor := opts.extractor()
|
||||
onUnauth := opts.onUnauth()
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
raw, ok := extractor(r)
|
||||
if !ok || raw == "" {
|
||||
onUnauth(w, r, authkit.ErrSessionInvalid)
|
||||
return
|
||||
}
|
||||
p, err := authn(r, raw)
|
||||
if err != nil {
|
||||
onUnauth(w, r, err)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r.WithContext(withPrincipal(r.Context(), p)))
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue