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)
}