1
Fork 0
photoview/api/graphql/auth/auth.go

104 lines
2.6 KiB
Go
Raw Normal View History

2020-01-31 23:30:34 +01:00
package auth
import (
"context"
"database/sql"
"errors"
"log"
"net/http"
"regexp"
2020-02-21 17:53:04 +01:00
"github.com/99designs/gqlgen/handler"
2020-01-31 23:30:34 +01:00
"github.com/viktorstrate/photoview/api/graphql/models"
)
var ErrUnauthorized = errors.New("unauthorized")
// A private key for context that only this package can access. This is important
// to prevent collisions between different context uses
var userCtxKey = &contextKey{"user"}
type contextKey struct {
name string
}
// Middleware decodes the share session cookie and packs the session into context
func Middleware(db *sql.DB) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
bearer := r.Header.Get("Authorization")
if bearer == "" {
next.ServeHTTP(w, r)
return
}
2020-02-21 17:53:04 +01:00
token, err := TokenFromBearer(&bearer)
if err != nil {
log.Printf("Invalid bearer format: %s\n", bearer)
2020-01-31 23:30:34 +01:00
http.Error(w, "Invalid authorization header format", http.StatusBadRequest)
return
}
2020-02-21 17:53:04 +01:00
user, err := models.VerifyTokenAndGetUser(db, *token)
2020-01-31 23:30:34 +01:00
if err != nil {
2020-02-01 00:08:23 +01:00
log.Printf("Invalid token: %s\n", err)
2020-01-31 23:30:34 +01:00
http.Error(w, "Invalid authorization token", http.StatusForbidden)
return
}
// put it in context
ctx := context.WithValue(r.Context(), userCtxKey, user)
// and call the next with our new context
r = r.WithContext(ctx)
next.ServeHTTP(w, r)
})
}
}
2020-02-21 17:53:04 +01:00
func TokenFromBearer(bearer *string) (*string, error) {
regex, _ := regexp.Compile("^Bearer ([a-zA-Z0-9]{24})$")
matches := regex.FindStringSubmatch(*bearer)
if len(matches) != 2 {
return nil, errors.New("invalid bearer format")
}
token := matches[1]
return &token, nil
}
2020-01-31 23:30:34 +01:00
// UserFromContext finds the user from the context. REQUIRES Middleware to have run.
func UserFromContext(ctx context.Context) *models.User {
raw, _ := ctx.Value(userCtxKey).(*models.User)
return raw
}
2020-02-21 17:53:04 +01:00
func AuthWebsocketInit(db *sql.DB) func(context.Context, handler.InitPayload) (context.Context, error) {
return func(ctx context.Context, initPayload handler.InitPayload) (context.Context, error) {
bearer, exists := initPayload["Authorization"].(string)
if !exists {
return ctx, nil
}
token, err := TokenFromBearer(&bearer)
if err != nil {
log.Printf("Invalid bearer format (websocket): %s\n", bearer)
return nil, err
}
user, err := models.VerifyTokenAndGetUser(db, *token)
if err != nil {
log.Printf("Invalid token in websocket: %s\n", err)
return nil, errors.New("invalid authorization token")
}
// put it in context
userCtx := context.WithValue(ctx, userCtxKey, user)
// and return it so the resolvers can see it
return userCtx, nil
}
}