This commit is contained in:
2021-02-04 15:31:07 -05:00
parent 082f923482
commit a5692babb8
20 changed files with 319 additions and 348 deletions

View File

@@ -1,12 +1,10 @@
package api
import (
"context"
"errors"
"fmt"
"net/http"
"github.com/99designs/gqlgen/graphql"
"github.com/google/uuid"
"github.com/lestrrat-go/jwx/jwt"
@@ -94,31 +92,3 @@ func (api *API) validateTokens(w *http.ResponseWriter, r *http.Request) (jwt.Tok
return jwt.ParseBytes([]byte(newAccessCookie))
}
func (api *API) hasMinRoleDirective(ctx context.Context, obj interface{}, next graphql.Resolver, role model.Role) (res interface{}, err error) {
authContext := ctx.Value("auth").(*model.AuthContext)
accessToken, err := api.validateTokens(authContext.AuthResponse, authContext.AuthRequest)
if err != nil {
return nil, errors.New("Access Denied")
}
authContext.AccessToken = &accessToken
userRole, ok := accessToken.Get("role")
if !ok {
return nil, errors.New("Access Denied")
}
if userRole == model.RoleAdmin.String() {
return next(ctx)
}
if userRole == role.String() {
return next(ctx)
}
return nil, errors.New("Role Not Authenticated")
}
func (api *API) metaDirective(ctx context.Context, obj interface{}, next graphql.Resolver, gorm *string) (res interface{}, err error) {
return next(ctx)
}

View File

@@ -0,0 +1,50 @@
package api
import (
"context"
"errors"
"github.com/99designs/gqlgen/graphql"
"reichard.io/imagini/graph/model"
)
/**
* This is used to validate whether the users role is adequate for the requested resource.
**/
func (api *API) hasMinRoleDirective(ctx context.Context, obj interface{}, next graphql.Resolver, role model.Role) (res interface{}, err error) {
authContext := ctx.Value("auth").(*model.AuthContext)
accessToken, err := api.validateTokens(authContext.AuthResponse, authContext.AuthRequest)
if err != nil {
return nil, errors.New("Access Denied")
}
authContext.AccessToken = &accessToken
userRole, ok := accessToken.Get("role")
if !ok {
return nil, errors.New("Access Denied")
}
if userRole == model.RoleAdmin.String() {
return next(ctx)
}
if userRole == role.String() {
return next(ctx)
}
return nil, errors.New("Role Not Authenticated")
}
/**
* This is needed but not used. Meta is used for Gorm.
**/
func (api *API) metaDirective(ctx context.Context, obj interface{}, next graphql.Resolver, gorm *string) (res interface{}, err error) {
return next(ctx)
}
/**
* This overrides the response so fields with an @isPrivate directive are always nil.
**/
func (api *API) isPrivateDirective(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) {
return nil, errors.New("Private Field")
}

View File

@@ -8,7 +8,9 @@ import (
"reichard.io/imagini/graph/model"
)
// Responsible for serving up static images / videos
/**
* Responsible for serving up static images / videos
**/
func (api *API) mediaHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.WriteHeader(http.StatusMethodNotAllowed)

View File

@@ -3,9 +3,6 @@ package api
import (
"context"
"net/http"
"os"
log "github.com/sirupsen/logrus"
"reichard.io/imagini/graph/model"
)
@@ -27,9 +24,8 @@ func multipleMiddleware(h http.HandlerFunc, m ...Middleware) http.HandlerFunc {
* This is used for the graphQL endpoints that may require access to the
* Request and ResponseWriter variables. These are used to get / set cookies.
**/
func (api *API) injectContextMiddleware(next http.Handler) http.Handler {
func (api *API) contextMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authContext := &model.AuthContext{
AuthResponse: &w,
AuthRequest: r,
@@ -40,7 +36,6 @@ func (api *API) injectContextMiddleware(next http.Handler) http.Handler {
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
@@ -49,10 +44,10 @@ func (api *API) injectContextMiddleware(next http.Handler) http.Handler {
**/
func (api *API) authMiddleware(next http.Handler) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Validate Tokens
accessToken, err := api.validateTokens(&w, r)
if err != nil {
errorJSON(w, "Invalid token.", http.StatusUnauthorized)
w.WriteHeader(http.StatusUnauthorized)
return
}
@@ -64,16 +59,5 @@ func (api *API) authMiddleware(next http.Handler) http.HandlerFunc {
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
func (api *API) logMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.SetOutput(os.Stdout)
log.Println(r.Method, r.URL)
h.ServeHTTP(w, r)
})
}

View File

@@ -1,24 +0,0 @@
package api
type APICredentials struct {
User string `json:"user"`
Password string `json:"password"`
}
type APIData interface{}
type APIMeta struct {
Count int64 `json:"count"`
Page int64 `json:"page"`
}
type APIError struct {
Message string `json:"message"`
Code int64 `json:"code"`
}
type APIResponse struct {
Data APIData `json:"data,omitempty"`
Meta *APIMeta `json:"meta,omitempty"`
Error *APIError `json:"error,omitempty"`
}

View File

@@ -1,9 +1,6 @@
package api
import (
"encoding/json"
"net/http"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
@@ -12,7 +9,6 @@ import (
)
func (api *API) registerRoutes() {
// Set up Directives
graphConfig := generated.Config{
Resolvers: &graph.Resolver{
@@ -21,6 +17,7 @@ func (api *API) registerRoutes() {
},
Directives: generated.DirectiveRoot{
Meta: api.metaDirective,
IsPrivate: api.isPrivateDirective,
HasMinRole: api.hasMinRoleDirective,
},
}
@@ -28,31 +25,11 @@ func (api *API) registerRoutes() {
// Handle GraphQL
api.Router.Handle("/playground", playground.Handler("GraphQL playground", "/query"))
api.Router.Handle("/query", api.injectContextMiddleware(srv))
api.Router.Handle("/query", api.contextMiddleware(srv))
// Handle Resource Route
api.Router.HandleFunc("/media/", multipleMiddleware(
api.mediaHandler,
api.authMiddleware,
))
}
func errorJSON(w http.ResponseWriter, err string, code int) {
errStruct := &APIResponse{Error: &APIError{Message: err, Code: int64(code)}}
responseJSON(w, errStruct, code)
}
func responseJSON(w http.ResponseWriter, msg interface{}, code int) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(code)
json.NewEncoder(w).Encode(msg)
}
func successJSON(w http.ResponseWriter, msg string, code int) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(code)
json.NewEncoder(w).Encode(map[string]interface{}{"success": msg})
}