Basic Auth Context

This commit is contained in:
2021-02-02 22:55:35 -05:00
parent 7e6454c593
commit c39fe6ec24
13 changed files with 828 additions and 202 deletions

View File

@@ -4,14 +4,16 @@ import (
"fmt"
"time"
"strings"
"context"
"net/http"
"encoding/json"
"github.com/google/uuid"
log "github.com/sirupsen/logrus"
"github.com/lestrrat-go/jwx/jwt"
"github.com/99designs/gqlgen/graphql"
"reichard.io/imagini/internal/models"
graphql "reichard.io/imagini/graph/model"
"reichard.io/imagini/graph/model"
)
func (api *API) loginHandler(w http.ResponseWriter, r *http.Request) {
@@ -22,7 +24,7 @@ func (api *API) loginHandler(w http.ResponseWriter, r *http.Request) {
}
// Decode into Struct
var creds models.APICredentials
var creds APICredentials
err := json.NewDecoder(r.Body).Decode(&creds)
if err != nil {
errorJSON(w, "Invalid parameters.", http.StatusBadRequest)
@@ -36,7 +38,7 @@ func (api *API) loginHandler(w http.ResponseWriter, r *http.Request) {
}
// Do login
resp, user := api.Auth.AuthenticateUser(creds)
resp, user := api.Auth.AuthenticateUser(creds.User, creds.Password)
if !resp {
errorJSON(w, "Invalid credentials.", http.StatusUnauthorized)
return
@@ -82,7 +84,7 @@ func (api *API) logoutHandler(w http.ResponseWriter, r *http.Request) {
/**
* This will find or create the requested device based on ID and User.
**/
func (api *API) upsertRequestedDevice(user graphql.User, r *http.Request) (graphql.Device, error) {
func (api *API) upsertRequestedDevice(user model.User, r *http.Request) (model.Device, error) {
requestedDevice := deriveRequestedDevice(r)
requestedDevice.Type = deriveDeviceType(r)
requestedDevice.User.ID = user.ID
@@ -93,7 +95,7 @@ func (api *API) upsertRequestedDevice(user graphql.User, r *http.Request) (graph
return createdDevice, err
}
foundDevice, err := api.DB.Device(&graphql.Device{
foundDevice, err := api.DB.Device(&model.Device{
ID: requestedDevice.ID,
User: &user,
})
@@ -101,28 +103,28 @@ func (api *API) upsertRequestedDevice(user graphql.User, r *http.Request) (graph
return foundDevice, err
}
func deriveDeviceType(r *http.Request) graphql.DeviceType {
func deriveDeviceType(r *http.Request) model.DeviceType {
userAgent := strings.ToLower(r.Header.Get("User-Agent"))
if strings.HasPrefix(userAgent, "ios-imagini"){
return graphql.DeviceTypeIOs
return model.DeviceTypeIOs
} else if strings.HasPrefix(userAgent, "android-imagini"){
return graphql.DeviceTypeAndroid
return model.DeviceTypeAndroid
} else if strings.HasPrefix(userAgent, "chrome"){
return graphql.DeviceTypeChrome
return model.DeviceTypeChrome
} else if strings.HasPrefix(userAgent, "firefox"){
return graphql.DeviceTypeFirefox
return model.DeviceTypeFirefox
} else if strings.HasPrefix(userAgent, "msie"){
return graphql.DeviceTypeInternetExplorer
return model.DeviceTypeInternetExplorer
} else if strings.HasPrefix(userAgent, "edge"){
return graphql.DeviceTypeEdge
return model.DeviceTypeEdge
} else if strings.HasPrefix(userAgent, "safari"){
return graphql.DeviceTypeSafari
return model.DeviceTypeSafari
}
return graphql.DeviceTypeUnknown
return model.DeviceTypeUnknown
}
func deriveRequestedDevice(r *http.Request) graphql.Device {
deviceSkeleton := graphql.Device{}
func deriveRequestedDevice(r *http.Request) model.Device {
deviceSkeleton := model.Device{}
authHeader := r.Header.Get("X-Imagini-Authorization")
splitAuthInfo := strings.Split(authHeader, ",")
@@ -202,8 +204,8 @@ func (api *API) refreshAccessToken(w http.ResponseWriter, r *http.Request) (jwt.
stringDeviceUUID := deviceUUID.String()
// Device & User Skeleton
user := graphql.User{ID: &stringUserUUID}
device := graphql.Device{ID: &stringDeviceUUID}
user := model.User{ID: &stringUserUUID}
device := model.Device{ID: &stringDeviceUUID}
// Update token
accessTokenString, err := api.Auth.CreateJWTAccessToken(user, device)
@@ -230,3 +232,17 @@ func trimQuotes(s string) string {
}
return s
}
func hasMinRoleDirective(ctx context.Context, obj interface{}, next graphql.Resolver, role model.Role) (res interface{}, err error) {
// if !getCurrentUser(ctx).HasRole(role) {
// // block calling the next resolver
// return nil, fmt.Errorf("Access denied")
// }
// or let it pass through
return next(ctx)
}
func metaDirective(ctx context.Context, obj interface{}, next graphql.Resolver, gorm *string) (res interface{}, err error){
return next(ctx)
}

View File

@@ -1,10 +1,12 @@
package api
import (
"os"
"context"
"net/http"
log "github.com/sirupsen/logrus"
"net/http"
"context"
"os"
"reichard.io/imagini/graph/model"
)
type Middleware func(http.Handler) http.HandlerFunc
@@ -20,6 +22,35 @@ func multipleMiddleware(h http.HandlerFunc, m ...Middleware) http.HandlerFunc {
return wrapped
}
func (api *API) injectContextMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Info("[middleware] Entering testMiddleware...")
authContext := &model.AuthContext{
AuthResponse: &w,
AuthRequest: r,
}
accessCookie, err := r.Cookie("AccessToken")
if err != nil {
log.Warn("[middleware] AccessToken not found")
} else {
authContext.AccessToken = accessCookie.Value
}
refreshCookie, err := r.Cookie("RefreshToken")
if err != nil {
log.Warn("[middleware] RefreshToken not found")
} else {
authContext.RefreshToken = refreshCookie.Value
}
// Add context
ctx := context.WithValue(r.Context(), "auth", authContext)
r = r.WithContext(ctx)
log.Info("[middleware] Exiting testMiddleware...")
next.ServeHTTP(w, r)
})
}
func (api *API) authMiddleware(next http.Handler) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

24
internal/api/models.go Normal file
View File

@@ -0,0 +1,24 @@
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,33 +1,38 @@
package api
import (
"encoding/json"
"net/http"
"encoding/json"
"reichard.io/imagini/internal/models"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
"reichard.io/imagini/graph"
"reichard.io/imagini/graph/generated"
)
func (api *API) registerRoutes() {
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))
// TODO: Provide Authentication for srv
// Set up Directives
c := generated.Config{ Resolvers: &graph.Resolver{ DB: api.DB } }
c.Directives.HasMinRole = hasMinRoleDirective
c.Directives.Meta = metaDirective
srv := handler.NewDefaultServer(generated.NewExecutableSchema(c))
// Handle GraphQL
api.Router.Handle("/playground", playground.Handler("GraphQL playground", "/query"))
api.Router.Handle("/query", srv)
api.Router.Handle("/query", api.injectContextMiddleware(srv))
// Handle Resource Route
api.Router.HandleFunc("/media/", multipleMiddleware(
api.mediaHandler,
api.authMiddleware,
))
api.Router.HandleFunc("/logout", api.logoutHandler)
api.Router.HandleFunc("/login", api.loginHandler)
}
func errorJSON(w http.ResponseWriter, err string, code int) {
errStruct := &models.APIResponse{Error: &models.APIError{Message: err, Code: int64(code)}}
errStruct := &APIResponse{Error: &APIError{Message: err, Code: int64(code)}}
responseJSON(w, errStruct, code)
}