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.
|
// properties that may be filled for history and notes.
|
||||||
type JournalRequest struct {
|
type JournalRequest struct {
|
||||||
RequestID string `json:"requestId"`
|
RequestID string `json:"requestId"`
|
||||||
UserID string `json:"userId"`
|
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
AsOf int64 `json:"asOf"`
|
AsOf int64 `json:"asOf"`
|
||||||
LastStatus string `json:"lastStatus"`
|
LastStatus string `json:"lastStatus"`
|
||||||
History []History `json:"history"`
|
History []History `json:"history,omitempty"`
|
||||||
Notes []Note `json:"notes"`
|
Notes []Note `json:"notes,omitempty"`
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/danieljsummers/myPrayerJournal/src/api/data"
|
"github.com/danieljsummers/myPrayerJournal/src/api/data"
|
||||||
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
/* Support */
|
/* 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{}) {
|
func sendJSON(w http.ResponseWriter, r *http.Request, result interface{}) {
|
||||||
w.Header().Set("Content-Type", "application/json; encoding=UTF-8")
|
w.Header().Set("Content-Type", "application/json; encoding=UTF-8")
|
||||||
w.WriteHeader(http.StatusOK)
|
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)
|
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 */
|
/* Handlers */
|
||||||
|
|
||||||
|
// GET: /api/journal
|
||||||
func journal(w http.ResponseWriter, r *http.Request) {
|
func journal(w http.ResponseWriter, r *http.Request) {
|
||||||
user := r.Context().Value(ContextUserKey)
|
reqs := data.Journal(userID(r))
|
||||||
reqs := data.Journal(user.(string))
|
|
||||||
if reqs == nil {
|
if reqs == nil {
|
||||||
reqs = []data.JournalRequest{}
|
reqs = []data.JournalRequest{}
|
||||||
}
|
}
|
||||||
sendJSON(w, r, reqs)
|
sendJSON(w, r, reqs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GET: /*
|
||||||
func staticFiles(w http.ResponseWriter, r *http.Request) {
|
func staticFiles(w http.ResponseWriter, r *http.Request) {
|
||||||
// serve index for known routes handled client-side by the app
|
// serve index for known routes handled client-side by the app
|
||||||
for _, prefix := range ClientPrefixes {
|
for _, prefix := range ClientPrefixes {
|
||||||
log.Print("Checking " + r.URL.Path)
|
|
||||||
if strings.HasPrefix(r.URL.Path, prefix) {
|
if strings.HasPrefix(r.URL.Path, prefix) {
|
||||||
w.Header().Add("Content-Type", "text/html")
|
w.Header().Add("Content-Type", "text/html")
|
||||||
http.ServeFile(w, r, "./public/index.html")
|
http.ServeFile(w, r, "./public/index.html")
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/auth0-community/go-auth0"
|
"github.com/auth0/go-jwt-middleware"
|
||||||
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
"github.com/husobee/vestigo"
|
"github.com/husobee/vestigo"
|
||||||
"gopkg.in/square/go-jose.v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AuthConfig contains the Auth0 configuration passed from the "auth" JSON object.
|
// AuthConfig contains the Auth0 configuration passed from the "auth" JSON object.
|
||||||
@ -17,49 +19,102 @@ type AuthConfig struct {
|
|||||||
ClientSecret string `json:"secret"`
|
ClientSecret string `json:"secret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContextKey is the type of key used in our contexts.
|
// JWKS is a structure into which the JSON Web Key Set is unmarshaled.
|
||||||
type ContextKey string
|
type JWKS struct {
|
||||||
|
Keys []JWK `json:"keys"`
|
||||||
|
}
|
||||||
|
|
||||||
// ContextUserKey is the key for the current user in the context.
|
// JWK is a structure into which a single JSON Web Key is unmarshaled.
|
||||||
const ContextUserKey ContextKey = "user"
|
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 {
|
// authCfg is the Auth0 configuration provided at application startup.
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
var authCfg *AuthConfig
|
||||||
secret := []byte(cfg.ClientSecret)
|
|
||||||
secretProvider := auth0.NewKeyProvider(secret)
|
|
||||||
audience := []string{fmt.Sprintf("https://%s/userinfo", cfg.Domain)}
|
|
||||||
|
|
||||||
configuration := auth0.NewConfiguration(secretProvider, audience, fmt.Sprintf("https://%s/", cfg.Domain), jose.HS256)
|
// jwksBytes is a cache of the JSON Web Key Set for this domain.
|
||||||
validator := auth0.NewValidator(configuration, nil)
|
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 {
|
if err != nil {
|
||||||
fmt.Println(err)
|
return cert, err
|
||||||
fmt.Println("Token is not valid:", token)
|
}
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
defer resp.Body.Close()
|
||||||
w.Write([]byte("Unauthorized"))
|
|
||||||
} else {
|
if jwksBytes, err = ioutil.ReadAll(resp.Body); err != nil {
|
||||||
values := make(map[string]interface{})
|
return cert, err
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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.
|
// NewRouter returns a configured router to handle all incoming requests.
|
||||||
func NewRouter(cfg *AuthConfig) *vestigo.Router {
|
func NewRouter(cfg *AuthConfig) *vestigo.Router {
|
||||||
|
authCfg = cfg
|
||||||
router := vestigo.NewRouter()
|
router := vestigo.NewRouter()
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
if route.IsPublic {
|
if route.IsPublic {
|
||||||
router.Add(route.Method, route.Pattern, route.Func)
|
router.Add(route.Method, route.Pattern, route.Func)
|
||||||
} else {
|
} else {
|
||||||
router.Add(route.Method, route.Pattern, withAuth(route.Func, cfg))
|
router.Add(route.Method, route.Pattern, route.Func, authMiddleware)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return router
|
return router
|
||||||
|
Loading…
Reference in New Issue
Block a user