blob: c55beb73a854e2d5369dfe46999352afd9f5c9aa [file] [log] [blame]
package goscaffold
import (
"context"
"encoding/json"
"github.com/SermoDigital/jose/crypto"
"github.com/SermoDigital/jose/jws"
"github.com/SermoDigital/jose/jwt"
"github.com/julienschmidt/httprouter"
"github.com/justinas/alice"
"net/http"
)
const params = "params"
/* Errors to return */
type Errors []string
/*
The SSO key parameters
*/
type ssoKey struct {
Alg string `json:"alg"`
Value string `json:"value"`
Kty string `json:"kty"`
Use string `json:"use"`
N string `json:"n"`
E string `json:"e"`
}
/*
OAuth structure that provides http connection to the URL that has the public
key for verifying the JWT token
*/
type OAuth struct {
keyURL string
client *http.Client
}
/*
The interface functions offered to clients that act on OAuth param,
used to verify JWT tokens for the Http handler functions client
wishes to validate against (via SSOHandler).
*/
type OAuthService interface {
SSOHandler(p string, h func(http.ResponseWriter, *http.Request)) (string, httprouter.Handle)
}
/*
SetParamsInRequest Sets the params and its values in the request
*/
func SetParamsInRequest(r *http.Request, ps httprouter.Params) *http.Request {
newContext := context.WithValue(r.Context(), params, ps)
return r.WithContext(newContext)
}
/*
FetchParams fetches the param values, given the params in the request
*/
func FetchParams(r *http.Request) httprouter.Params {
ctx := r.Context()
return ctx.Value(params).(httprouter.Params)
}
/*
SSOHandler offers the users the flexibility of choosing which http handlers
need JWT validation.
*/
func (a *OAuth) SSOHandler(p string, h func(http.ResponseWriter, *http.Request)) (string, httprouter.Handle) {
return p, a.VerifyOAuth(alice.New().ThenFunc(h))
}
/*
VerifyOAuth verifies the JWT token in the request using the public key configured
via CreateOAuth constructor.
*/
func (a *OAuth) VerifyOAuth(next http.Handler) httprouter.Handle {
return func(rw http.ResponseWriter, r *http.Request, ps httprouter.Params) {
jwt, err := jws.ParseJWTFromRequest(r)
if err != nil {
WriteErrorResponse(http.StatusBadRequest, err.Error(), rw)
return
}
/* Validate the JWT */
err = a.Validate(jwt)
if err != nil {
WriteErrorResponse(http.StatusBadRequest, err.Error(), rw)
return
}
/* Set the params in the request */
r = SetParamsInRequest(r, ps)
next.ServeHTTP(rw, r)
}
}
/*
ValidateKey validate the jwt and return an error if it fails
*/
func (a *OAuth) Validate(jwt jwt.JWT) error {
r, err := a.client.Get(a.keyURL)
if err != nil {
return err
}
defer r.Body.Close()
ssoKey := &ssoKey{}
err = json.NewDecoder(r.Body).Decode(ssoKey)
if err != nil {
return err
}
/* Retrieve the Public Key */
publieKey, err := crypto.ParseRSAPublicKeyFromPEM([]byte(ssoKey.Value))
if err != nil {
return err
}
/* Return the status of validation */
return jwt.Validate(publieKey, crypto.SigningMethodRS256)
}
/*
WriteErrorResponse write a non 200 error response
*/
func WriteErrorResponse(statusCode int, message string, w http.ResponseWriter) {
errors := Errors{message}
WriteErrorResponses(statusCode, errors, w)
}
/*
WriteErrorResponses write our error responses
*/
func WriteErrorResponses(statusCode int, errors Errors, w http.ResponseWriter) {
w.WriteHeader(statusCode)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(errors)
}