Authorization works (yay)
The journal page once again loads as it should; now to migrate the remaining routes
This commit is contained in:
parent
9637b38a3f
commit
419c181eff
|
@ -26,10 +26,9 @@ type Request struct {
|
|||
// properties that may be filled for history and notes.
|
||||
type JournalRequest struct {
|
||||
RequestID string `json:"requestId"`
|
||||
UserID string `json:"userId"`
|
||||
Text string `json:"text"`
|
||||
AsOf int64 `json:"asOf"`
|
||||
LastStatus string `json:"lastStatus"`
|
||||
History []History `json:"history"`
|
||||
Notes []Note `json:"notes"`
|
||||
History []History `json:"history,omitempty"`
|
||||
Notes []Note `json:"notes,omitempty"`
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/danieljsummers/myPrayerJournal/src/api/data"
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
/* Support */
|
||||
|
@ -24,26 +25,33 @@ func sendError(w http.ResponseWriter, r *http.Request, err error) {
|
|||
func sendJSON(w http.ResponseWriter, r *http.Request, result interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json; encoding=UTF-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(map[string]interface{}{"data": result}); err != nil {
|
||||
if err := json.NewEncoder(w).Encode(result); err != nil {
|
||||
sendError(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
// userID is a convenience function to extract the subscriber ID from the user's JWT.
|
||||
// NOTE: Do not call this from public routes; there are a lot of type assertions that won't be true if the request
|
||||
// hasn't gone through the authorization process.
|
||||
func userID(r *http.Request) string {
|
||||
return r.Context().Value("user").(*jwt.Token).Claims.(jwt.MapClaims)["sub"].(string)
|
||||
}
|
||||
|
||||
/* Handlers */
|
||||
|
||||
// GET: /api/journal
|
||||
func journal(w http.ResponseWriter, r *http.Request) {
|
||||
user := r.Context().Value(ContextUserKey)
|
||||
reqs := data.Journal(user.(string))
|
||||
reqs := data.Journal(userID(r))
|
||||
if reqs == nil {
|
||||
reqs = []data.JournalRequest{}
|
||||
}
|
||||
sendJSON(w, r, reqs)
|
||||
}
|
||||
|
||||
// GET: /*
|
||||
func staticFiles(w http.ResponseWriter, r *http.Request) {
|
||||
// serve index for known routes handled client-side by the app
|
||||
for _, prefix := range ClientPrefixes {
|
||||
log.Print("Checking " + r.URL.Path)
|
||||
if strings.HasPrefix(r.URL.Path, prefix) {
|
||||
w.Header().Add("Content-Type", "text/html")
|
||||
http.ServeFile(w, r, "./public/index.html")
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
package routes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/auth0-community/go-auth0"
|
||||
"github.com/auth0/go-jwt-middleware"
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/husobee/vestigo"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
// AuthConfig contains the Auth0 configuration passed from the "auth" JSON object.
|
||||
|
@ -17,49 +19,102 @@ type AuthConfig struct {
|
|||
ClientSecret string `json:"secret"`
|
||||
}
|
||||
|
||||
// ContextKey is the type of key used in our contexts.
|
||||
type ContextKey string
|
||||
// JWKS is a structure into which the JSON Web Key Set is unmarshaled.
|
||||
type JWKS struct {
|
||||
Keys []JWK `json:"keys"`
|
||||
}
|
||||
|
||||
// ContextUserKey is the key for the current user in the context.
|
||||
const ContextUserKey ContextKey = "user"
|
||||
// JWK is a structure into which a single JSON Web Key is unmarshaled.
|
||||
type JWK struct {
|
||||
Kty string `json:"kty"`
|
||||
Kid string `json:"kid"`
|
||||
Use string `json:"use"`
|
||||
N string `json:"n"`
|
||||
E string `json:"e"`
|
||||
X5c []string `json:"x5c"`
|
||||
}
|
||||
|
||||
func withAuth(next http.HandlerFunc, cfg *AuthConfig) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
secret := []byte(cfg.ClientSecret)
|
||||
secretProvider := auth0.NewKeyProvider(secret)
|
||||
audience := []string{fmt.Sprintf("https://%s/userinfo", cfg.Domain)}
|
||||
// authCfg is the Auth0 configuration provided at application startup.
|
||||
var authCfg *AuthConfig
|
||||
|
||||
configuration := auth0.NewConfiguration(secretProvider, audience, fmt.Sprintf("https://%s/", cfg.Domain), jose.HS256)
|
||||
validator := auth0.NewValidator(configuration, nil)
|
||||
// jwksBytes is a cache of the JSON Web Key Set for this domain.
|
||||
var jwksBytes = make([]byte, 0)
|
||||
|
||||
token, err := validator.ValidateRequest(r)
|
||||
// getPEMCert is a function to get the applicable certificate for a JSON Web Token.
|
||||
func getPEMCert(token *jwt.Token) (string, error) {
|
||||
cert := ""
|
||||
|
||||
if len(jwksBytes) == 0 {
|
||||
resp, err := http.Get(fmt.Sprintf("https://%s/.well-known/jwks.json", authCfg.Domain))
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Token is not valid:", token)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte("Unauthorized"))
|
||||
} else {
|
||||
values := make(map[string]interface{})
|
||||
if err := token.Claims(secret, &values); err != nil {
|
||||
sendError(w, r, err)
|
||||
}
|
||||
r = r.WithContext(context.WithValue(r.Context(), ContextUserKey, values["sub"]))
|
||||
// TODO pass the user ID (sub) along; this -> doesn't work | r.Header.Add("user-id", token.Claims("sub"))
|
||||
next(w, r)
|
||||
return cert, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if jwksBytes, err = ioutil.ReadAll(resp.Body); err != nil {
|
||||
return cert, err
|
||||
}
|
||||
}
|
||||
|
||||
jwks := JWKS{}
|
||||
if err := json.Unmarshal(jwksBytes, &jwks); err != nil {
|
||||
return cert, err
|
||||
}
|
||||
for k, v := range jwks.Keys[0].X5c {
|
||||
if token.Header["kid"] == jwks.Keys[k].Kid {
|
||||
cert = fmt.Sprintf("-----BEGIN CERTIFICATE-----\n%s\n-----END CERTIFICATE-----", v)
|
||||
}
|
||||
}
|
||||
if cert == "" {
|
||||
err := errors.New("unable to find appropriate key")
|
||||
return cert, err
|
||||
}
|
||||
|
||||
return cert, nil
|
||||
}
|
||||
|
||||
// authZero is an instance of Auth0's JWT middlware. Since it doesn't support the http.HandlerFunc sig, it is wrapped
|
||||
// below; it's defined outside that function, though, so it does not get recreated every time.
|
||||
var authZero = jwtmiddleware.New(jwtmiddleware.Options{
|
||||
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
|
||||
if checkAud := token.Claims.(jwt.MapClaims).VerifyAudience(authCfg.ClientID, false); !checkAud {
|
||||
return token, errors.New("invalid audience")
|
||||
}
|
||||
iss := fmt.Sprintf("https://%s/", authCfg.Domain)
|
||||
if checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false); !checkIss {
|
||||
return token, errors.New("invalid issuer")
|
||||
}
|
||||
|
||||
cert, err := getPEMCert(token)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert))
|
||||
return result, nil
|
||||
},
|
||||
SigningMethod: jwt.SigningMethodRS256,
|
||||
})
|
||||
|
||||
// authMiddleware is a wrapper for the Auth0 middleware above with a signature Vestigo recognizes.
|
||||
func authMiddleware(f http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
err := authZero.CheckJWT(w, r)
|
||||
if err == nil {
|
||||
f(w, r)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewRouter returns a configured router to handle all incoming requests.
|
||||
func NewRouter(cfg *AuthConfig) *vestigo.Router {
|
||||
authCfg = cfg
|
||||
router := vestigo.NewRouter()
|
||||
for _, route := range routes {
|
||||
if route.IsPublic {
|
||||
router.Add(route.Method, route.Pattern, route.Func)
|
||||
} else {
|
||||
router.Add(route.Method, route.Pattern, withAuth(route.Func, cfg))
|
||||
router.Add(route.Method, route.Pattern, route.Func, authMiddleware)
|
||||
}
|
||||
}
|
||||
return router
|
||||
|
|
Loading…
Reference in New Issue
Block a user