HTTP Handler
// - http-handler/handler_test.go -
package httphandler
import (
"httptesting"
"testing"
"github.com/stretchr/testify/suite"
)
func TestHttpHandler(t *testing.T) {
suite.Run(t, &httptesting.HandlerTestSuite{
Handler: Handler{
Users: users,
},
})
}
// - http-handler/handler.go -
package httphandler
import (
"fmt"
"net/http"
"time"
"github.com/golang-jwt/jwt/v4"
"github.com/golang-jwt/jwt/v4/request"
)
type Handler struct {
Users map[string]string
}
func (h Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
mux := http.NewServeMux()
mux.HandleFunc("/public", h.Public)
mux.HandleFunc("/authorize", h.Authorize)
mux.Handle("/restricted", h.Restrict(func(tc *TokenClaim) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "Welcome,", tc.Name)
})
}))
mux.ServeHTTP(rw, req)
}
func (h Handler) Public(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("this is public endpoint"))
}
func (h Handler) Authorize(w http.ResponseWriter, req *http.Request) {
// restrictions...
if req.Method != "POST" {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusMethodNotAllowed)
fmt.Fprintln(w, `{"message":"Method Not Allowed"}`)
return
}
userIncoming := req.FormValue("user")
passIncoming := req.FormValue("pass")
// check values
for user, pass := range h.Users {
if user == userIncoming && pass == passIncoming {
tokenString, err := createToken(user)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintln(w, "Sorry, error while Signing Token!")
// log.Printf("Token Signing error: %v\n", err)
return
}
// w.Header().Set("Content-Type", "application/jwt")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, `{ "token": "%s" }`, tokenString)
return
}
}
// giving up
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprintln(w, "i don't know you")
return
}
func (h Handler) Restrict(tch func(tc *TokenClaim) http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.Header.Get("Authorization") == "" {
rw.WriteHeader(http.StatusBadRequest)
return
}
token, err := request.ParseFromRequestWithClaims(req, request.OAuth2Extractor, &TokenClaim{}, verifyTokenWithKey)
if err != nil {
rw.WriteHeader(http.StatusUnauthorized)
fmt.Fprintln(rw, "Invalid toke1n:", err)
return
}
tch(token.Claims.(*TokenClaim)).ServeHTTP(rw, req)
})
}
func verifyTokenWithKey(token *jwt.Token) (interface{}, error) {
return verifyKey, nil
}
func createToken(user string) (string, error) {
token := jwt.New(jwt.GetSigningMethod("RS256"))
// set our claims
token.Claims = &TokenClaim{
&jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Minute * 1).Unix(),
},
"root",
user,
}
return token.SignedString(signKey)
}
// - http-handler/init.go -
package httphandler
import (
"crypto/rsa"
"io/ioutil"
"log"
"github.com/golang-jwt/jwt/v4"
)
const (
privKeyPath = "keys/app.rsa"
pubKeyPath = "keys/app.rsa.pub"
)
var (
verifyKey *rsa.PublicKey
signKey *rsa.PrivateKey
serverPort int
// storing sample username/password pairs
// don't do this on a real server
users = map[string]string{
"user": "pass",
"ford": "betelgeuse7",
}
)
type TokenClaim struct {
*jwt.StandardClaims
TokenType string
Name string
}
func fatal(err error) {
if err != nil {
log.Fatal(err)
}
}
// read the key files before starting http handlers
func init() {
signBytes, err := ioutil.ReadFile(privKeyPath)
fatal(err)
signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes)
fatal(err)
verifyBytes, err := ioutil.ReadFile(pubKeyPath)
fatal(err)
verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
fatal(err)
}