diff --git a/pkg/handler/auth/middleware.go b/pkg/handler/auth/middleware.go index 99a61c3..b892911 100644 --- a/pkg/handler/auth/middleware.go +++ b/pkg/handler/auth/middleware.go @@ -50,8 +50,10 @@ func (middleware *auth) Wrap(ctx context.Context, next http.HandlerFunc) http.Ha authorized := true if token.IsZero() { authorized = false + httpresponse.Error(w, http.StatusUnauthorized, "invalid or missing token") } else if !token.IsValid() { authorized = false + httpresponse.Error(w, http.StatusUnauthorized, "invalid or missing token") } else if token.IsScope(ScopeRoot) { // Allow - token is a super-user token } else if allowedScopes := router.Scope(r.Context()); len(allowedScopes) == 0 { @@ -59,11 +61,11 @@ func (middleware *auth) Wrap(ctx context.Context, next http.HandlerFunc) http.Ha } else if !token.IsScope(allowedScopes...) { // Deny - token does not have the required scopes authorized = false + httpresponse.Error(w, http.StatusUnauthorized, "required scope: ", strings.Join(allowedScopes, ",")) } // Return unauthorized if token is not found or not valid if !authorized { - httpresponse.Error(w, http.StatusUnauthorized) return } diff --git a/pkg/handler/router/endpoints.go b/pkg/handler/router/endpoints.go new file mode 100644 index 0000000..e07324c --- /dev/null +++ b/pkg/handler/router/endpoints.go @@ -0,0 +1,43 @@ +package router + +import ( + "context" + "net/http" + "regexp" + + // Packages + server "github.com/mutablelogic/go-server" + httpresponse "github.com/mutablelogic/go-server/pkg/httpresponse" +) + +/////////////////////////////////////////////////////////////////////////////// +// GLOBALS + +const ( + jsonIndent = 2 +) + +var ( + reRoot = regexp.MustCompile(`^/?$`) +) + +/////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS - ENDPOINTS + +// Add endpoints to the router +func (service *router) AddEndpoints(ctx context.Context, r server.Router) { + // Path: / + // Methods: GET + // Scopes: read + // Description: Get router services + r.AddHandlerFuncRe(ctx, reRoot, service.GetScopes, http.MethodGet).(Route). + SetScope(service.ScopeRead()...) +} + +/////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS + +// Get registered scopes +func (service *router) GetScopes(w http.ResponseWriter, r *http.Request) { + httpresponse.JSON(w, service.Scopes(), http.StatusOK, jsonIndent) +} diff --git a/pkg/handler/router/interface.go b/pkg/handler/router/interface.go index c7dbbd7..c31033f 100644 --- a/pkg/handler/router/interface.go +++ b/pkg/handler/router/interface.go @@ -15,6 +15,9 @@ type Router interface { // http status code, which will be 200 on success, 404 or 405 and // path parameters extracted from the path. Match(host, method, path string) (*matchedRoute, int) + + // Return all known scopes + Scopes() []string } type Route interface { diff --git a/pkg/handler/router/router.go b/pkg/handler/router/router.go index 34761a8..591a1d6 100644 --- a/pkg/handler/router/router.go +++ b/pkg/handler/router/router.go @@ -184,6 +184,31 @@ func (router *router) Match(host, method, path string) (*matchedRoute, int) { return nil, http.StatusMethodNotAllowed } +func (router *router) Scopes() []string { + scopes := make(map[string]bool) + for _, r := range router.host { + for _, h := range r.prefix { + for _, r := range h.handlers { + for _, s := range r.scopes { + scopes[s] = true + } + } + } + } + + // Gather all scopes + result := make([]string, 0, len(scopes)) + for scope := range scopes { + result = append(result, scope) + } + + // Sort alphabetically + sort.Strings(result) + + // Return the result + return result +} + /////////////////////////////////////////////////////////////////////////////// // PRIVATE METHODS diff --git a/pkg/handler/router/scope.go b/pkg/handler/router/scope.go new file mode 100644 index 0000000..160deb2 --- /dev/null +++ b/pkg/handler/router/scope.go @@ -0,0 +1,33 @@ +package router + +import ( + // Packages + "github.com/mutablelogic/go-server/pkg/version" +) + +//////////////////////////////////////////////////////////////////////////////// +// GLOBALS + +var ( + // Prefix + scopePrefix = version.GitSource + "/scope/" +) + +//////////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS + +func (router *router) ScopeRead() []string { + // Return read (list, get) scopes + return []string{ + scopePrefix + router.Label() + "/read", + scopePrefix + defaultName + "/read", + } +} + +func (router *router) ScopeWrite() []string { + // Return write (create, delete, update) scopes + return []string{ + scopePrefix + router.Label() + "/write", + scopePrefix + defaultName + "/write", + } +}