diff options
| author | mo khan <mo@mokhan.ca> | 2022-05-13 14:45:14 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2022-05-13 14:45:14 -0600 |
| commit | d26df9af95676edbc15518aa179fd5f2f389c2e2 (patch) | |
| tree | 093a856a9b77597f989bcdfec9f0d5ee416c1361 /cmd/api | |
| parent | 006bec0db7292eb75ac33ca6967af715ea635514 (diff) | |
squash code into main
Diffstat (limited to 'cmd/api')
| -rw-r--r-- | cmd/api/main.go | 108 |
1 files changed, 104 insertions, 4 deletions
diff --git a/cmd/api/main.go b/cmd/api/main.go index c74f176..d20731e 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -1,16 +1,116 @@ package main import ( + "context" + "errors" "fmt" "log" "net/http" + "net/url" + "os" + "strings" + "time" jwtmiddleware "github.com/auth0/go-jwt-middleware/v2" + "github.com/auth0/go-jwt-middleware/v2/jwks" "github.com/auth0/go-jwt-middleware/v2/validator" "github.com/joho/godotenv" - "github.com/xlgmokha/api-auth0/pkg/middleware" ) +type CustomClaims struct { + Scope string `json:"scope"` +} + +func (c CustomClaims) Validate(ctx context.Context) error { + return nil +} + +func (c CustomClaims) HasScope(expectedScope string) bool { + result := strings.Split(c.Scope, " ") + for i := range result { + if result[i] == expectedScope { + return true + } + } + return false +} + +// type TokenExtractor func(r *http.Request) (string, error) +func Extractor(r *http.Request) (string, error) { + authHeader := r.Header.Get("Authorization") + fmt.Printf("%v %v\nAuthorization: %v\n", r.Method, r.URL.Path, authHeader) + if authHeader == "" { + return "", nil + } + + authHeaderParts := strings.Fields(authHeader) + if len(authHeaderParts) != 2 || strings.ToLower(authHeaderParts[0]) != "bearer" { + // exchange opaque access token for a JWT access token + return "", errors.New("Authorization header format must be Bearer {token}") + } + + rawToken := authHeaderParts[1] + sections := strings.Split(authHeaderParts[1], ".") + if len(sections) != 3 { + fmt.Printf("sections: %v\n", len(sections)) + return "", errors.New("Token is not a JWT") + } + + return rawToken, nil + return "", nil +} + +func EnsureValidToken() func(next http.Handler) http.Handler { + issuerURL, err := url.Parse("https://" + os.Getenv("AUTH0_DOMAIN") + "/") + if err != nil { + log.Fatal(err) + } + + provider := jwks.NewCachingProvider(issuerURL, 5*time.Minute) + jwtValidator, err := validator.New( + provider.KeyFunc, + validator.RS256, + issuerURL.String(), + []string{os.Getenv("AUTH0_AUDIENCE")}, + validator.WithCustomClaims( + func() validator.CustomClaims { + return &CustomClaims{} + }, + ), + validator.WithAllowedClockSkew(time.Minute), + ) + if err != nil { + log.Fatal(err) + } + + errorHandler := func(w http.ResponseWriter, r *http.Request, err error) { + fmt.Printf("Error: %v\n", err) + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(`{"message":"Failed to validate JWT."}`)) + } + + middleware := jwtmiddleware.New( + jwtValidator.ValidateToken, + jwtmiddleware.WithErrorHandler(errorHandler), + jwtmiddleware.WithTokenExtractor(Extractor), + ) + + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == "OPTIONS" { + w.Header().Set("Access-Control-Allow-Credentials", "true") + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Headers", "Authorization") + w.WriteHeader(http.StatusOK) + } else { + middleware.CheckJWT(next).ServeHTTP(w, r) + } + }) + } +} + func main() { if err := godotenv.Load(); err != nil { log.Fatal(err) @@ -25,7 +125,7 @@ func main() { w.Write([]byte(`{"message":"public"}`)) })) - router.Handle("/api/private", middleware.EnsureValidToken()( + router.Handle("/api/private", EnsureValidToken()( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Printf("in /api/private handler\n") w.Header().Set("Access-Control-Allow-Credentials", "true") @@ -38,7 +138,7 @@ func main() { }), )) - router.Handle("/api/private-scoped", middleware.EnsureValidToken()( + router.Handle("/api/private-scoped", EnsureValidToken()( http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Credentials", "true") w.Header().Set("Access-Control-Allow-Origin", "*") @@ -47,7 +147,7 @@ func main() { w.Header().Set("Content-Type", "application/json") token := r.Context().Value(jwtmiddleware.ContextKey{}).(*validator.ValidatedClaims) - claims := token.CustomClaims.(*middleware.CustomClaims) + claims := token.CustomClaims.(*CustomClaims) if !claims.HasScope("read:messages") { w.WriteHeader(http.StatusForbidden) w.Write([]byte(`{"message":"insufficient scope."}`)) |
