Update godeps

This commit is contained in:
Matthew Dillon 2015-05-26 13:26:36 -08:00
parent 1298cffca2
commit 06938a9f2b
7 changed files with 238 additions and 103 deletions

4
Godeps/Godeps.json generated
View file

@ -17,7 +17,7 @@
}, },
{ {
"ImportPath": "github.com/gorilla/mux", "ImportPath": "github.com/gorilla/mux",
"Rev": "8a875a034c69b940914d83ea03d3f1299b4d094b" "Rev": "94903de8c98a68d8b4483c529b26a5d146e386a2"
}, },
{ {
"ImportPath": "github.com/gorilla/schema", "ImportPath": "github.com/gorilla/schema",
@ -39,7 +39,7 @@
}, },
{ {
"ImportPath": "github.com/thermokarst/jwt", "ImportPath": "github.com/thermokarst/jwt",
"Rev": "774185ba9ebde2a02689173b736b760a13a6b777" "Rev": "66ca404d841ed908aa6cc9361107b0c363c94cd8"
}, },
{ {
"ImportPath": "golang.org/x/crypto/bcrypt", "ImportPath": "golang.org/x/crypto/bcrypt",

View file

@ -135,6 +135,42 @@ func TestHost(t *testing.T) {
path: "", path: "",
shouldMatch: false, shouldMatch: false,
}, },
{
title: "Path route with single pattern with pipe, match",
route: new(Route).Path("/{category:a|b/c}"),
request: newRequest("GET", "http://localhost/a"),
vars: map[string]string{"category": "a"},
host: "",
path: "/a",
shouldMatch: true,
},
{
title: "Path route with single pattern with pipe, match",
route: new(Route).Path("/{category:a|b/c}"),
request: newRequest("GET", "http://localhost/b/c"),
vars: map[string]string{"category": "b/c"},
host: "",
path: "/b/c",
shouldMatch: true,
},
{
title: "Path route with multiple patterns with pipe, match",
route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
request: newRequest("GET", "http://localhost/a/product_name/1"),
vars: map[string]string{"category": "a", "product": "product_name", "id": "1"},
host: "",
path: "/a/product_name/1",
shouldMatch: true,
},
{
title: "Path route with multiple patterns with pipe, match",
route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
request: newRequest("GET", "http://localhost/b/c/product_name/1"),
vars: map[string]string{"category": "b/c", "product": "product_name", "id": "1"},
host: "",
path: "/b/c/product_name/1",
shouldMatch: true,
},
} }
for _, test := range tests { for _, test := range tests {
testRoute(t, test) testRoute(t, test)

View file

@ -9,6 +9,7 @@ your application:
package main package main
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
@ -21,60 +22,90 @@ func protectMe(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "secured") fmt.Fprintf(w, "secured")
} }
func main() { func dontProtectMe(w http.ResponseWriter, r *http.Request) {
var authFunc = func(email string, password string) error { fmt.Fprintf(w, "not secured")
// Hard-code a user --- this could easily be a database call, etc. }
func auth(email string, password string) error {
// Hard-code a user
if email != "test" || password != "test" { if email != "test" || password != "test" {
return errors.New("invalid credentials") return errors.New("invalid credentials")
} }
return nil return nil
} }
var claimsFunc = func(userId string) (map[string]interface{}, error) { func setClaims(id string) (map[string]interface{}, error) {
currentTime := time.Now() currentTime := time.Now()
return map[string]interface{}{ return map[string]interface{}{
"iat": currentTime.Unix(), "iat": currentTime.Unix(),
"exp": currentTime.Add(time.Minute * 60 * 24).Unix(), "exp": currentTime.Add(time.Minute * 60 * 24).Unix(),
"sub": userId,
}, nil }, nil
} }
var verifyClaimsFunc = func(claims []byte) error { func verifyClaims(claims []byte, r *http.Request) error {
currentTime := time.Now() currentTime := time.Now()
var c struct { var c struct {
Exp int64
Iat int64 Iat int64
Sub string Exp int64
}
err := json.Unmarshal(claims, &c)
if err != nil {
return err
} }
_ = json.Unmarshal(claims, &c)
if currentTime.After(time.Unix(c.Exp, 0)) { if currentTime.After(time.Unix(c.Exp, 0)) {
return errors.New("this token has expired!") return errors.New("this token has expired")
}
if c.Sub != "test" {
return errors.New("who are you??!")
} }
return nil return nil
} }
func main() {
config := &jwt.Config{ config := &jwt.Config{
Secret: "password", Secret: "password",
Auth: authFunc, Auth: auth,
Claims: claimsFunc, Claims: setClaims,
} }
j, err := jwt.New(config) j, err := jwt.New(config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
protect := http.HandlerFunc(protectMe) protect := http.HandlerFunc(protectMe)
dontProtect := http.HandlerFunc(dontProtectMe)
http.Handle("/authenticate", j.GenerateToken()) http.Handle("/authenticate", j.GenerateToken())
http.Handle("/secure", j.Secure(protect, verifyClaimsFunc)) http.Handle("/secure", j.Secure(protect, verifyClaims))
http.Handle("/insecure", dontProtect)
http.ListenAndServe(":8080", nil) http.ListenAndServe(":8080", nil)
} }
``` ```
```shell
$ http GET :8080/secure
HTTP/1.1 401 Unauthorized
Content-Length: 23
Content-Type: text/plain; charset=utf-8
Date: Fri, 08 May 2015 06:43:35 GMT
please provide a token
$ http POST :8080/authenticate email=test password=test
HTTP/1.1 200 OK
Content-Length: 130
Content-Type: text/plain; charset=utf-8
Date: Fri, 08 May 2015 06:31:42 GMT
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0MzExNTI0ODAsImlhdCI6MTQzMTA2NjA4MH0=.UbJmLqOF4bTH/8+o6CrZfoi1Fu7zTDfCV0kwMQyzmos=
$ http GET :8080/secure Authorization:"Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE0MzExNTI0ODAsImlhdCI6MTQzMTA2NjA4MH0=.UbJmLqOF4bTH/8+o6CrZfoi1Fu7zTDfCV0kwMQyzmos="
HTTP/1.1 200 OK
Content-Length: 7
Content-Type: text/plain; charset=utf-8
Date: Fri, 08 May 2015 06:38:30 GMT
secured
```
# Installation # Installation
$ go get github.com/thermokarst/jwt $ go get github.com/thermokarst/jwt
@ -98,6 +129,10 @@ config := &jwt.Config{
j, err := jwt.New(config) j, err := jwt.New(config)
``` ```
You can also customize the field names by specifying `IdentityField` and
`VerifyField` in the `Config` struct, if you want the credentials to be
something other than `"email"` and `"password"`.
Once the middleware is instantiated, create a route for users to generate a JWT Once the middleware is instantiated, create a route for users to generate a JWT
at. at.

View file

@ -1,6 +1,7 @@
package main package main
import ( import (
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
@ -17,41 +18,52 @@ func dontProtectMe(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "not secured") fmt.Fprintf(w, "not secured")
} }
func main() { func auth(email string, password string) error {
var authFunc = func(email string, password string) error {
// Hard-code a user // Hard-code a user
if email != "test" || password != "test" { if email != "test" || password != "test" {
return errors.New("invalid credentials") return errors.New("invalid credentials")
} }
return nil return nil
} }
var claimsFunc = func(string) (map[string]interface{}, error) { func setClaims(id string) (map[string]interface{}, error) {
currentTime := time.Now() currentTime := time.Now()
return map[string]interface{}{ return map[string]interface{}{
"iat": currentTime.Unix(), "iat": currentTime.Unix(),
"exp": currentTime.Add(time.Minute * 60 * 24).Unix(), "exp": currentTime.Add(time.Minute * 60 * 24).Unix(),
}, nil }, nil
} }
var verifyClaimsFunc = func([]byte) error { func verifyClaims(claims []byte, r *http.Request) error {
// We don't really care about the claims, just approve as-is currentTime := time.Now()
var c struct {
Iat int64
Exp int64
}
_ = json.Unmarshal(claims, &c)
if currentTime.After(time.Unix(c.Exp, 0)) {
return errors.New("this token has expired")
}
return nil return nil
} }
func main() {
config := &jwt.Config{ config := &jwt.Config{
Secret: "password", Secret: "password",
Auth: authFunc, Auth: auth,
Claims: claimsFunc, Claims: setClaims,
} }
j, err := jwt.New(config) j, err := jwt.New(config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
protect := http.HandlerFunc(protectMe) protect := http.HandlerFunc(protectMe)
dontProtect := http.HandlerFunc(dontProtectMe) dontProtect := http.HandlerFunc(dontProtectMe)
http.Handle("/authenticate", j.GenerateToken()) http.Handle("/authenticate", j.GenerateToken())
http.Handle("/secure", j.Secure(protect, verifyClaimsFunc)) http.Handle("/secure", j.Secure(protect, verifyClaims))
http.Handle("/insecure", dontProtect) http.Handle("/insecure", dontProtect)
http.ListenAndServe(":8080", nil) http.ListenAndServe(":8080", nil)
} }

View file

@ -31,15 +31,9 @@ var (
ErrMalformedToken = errors.New("please provide a valid token") ErrMalformedToken = errors.New("please provide a valid token")
ErrInvalidSignature = errors.New("signature could not be verified") ErrInvalidSignature = errors.New("signature could not be verified")
ErrParsingCredentials = errors.New("error parsing credentials") ErrParsingCredentials = errors.New("error parsing credentials")
ErrInvalidMethod = errors.New("invalid request method")
) )
// Config is a container for setting up the JWT middleware.
type Config struct {
Secret string
Auth AuthFunc
Claims ClaimsFunc
}
// AuthFunc is a type for delegating user authentication to the client-code. // AuthFunc is a type for delegating user authentication to the client-code.
type AuthFunc func(string, string) error type AuthFunc func(string, string) error
@ -47,8 +41,17 @@ type AuthFunc func(string, string) error
type ClaimsFunc func(string) (map[string]interface{}, error) type ClaimsFunc func(string) (map[string]interface{}, error)
// VerifyClaimsFunc is a type for processing and validating JWT claims on one // VerifyClaimsFunc is a type for processing and validating JWT claims on one
// or more route's in the client-code. // or more routes in the client-code.
type VerifyClaimsFunc func([]byte) error type VerifyClaimsFunc func([]byte, *http.Request) error
// Config is a container for setting up the JWT middleware.
type Config struct {
Secret string
Auth AuthFunc
Claims ClaimsFunc
IdentityField string
VerifyField string
}
// Middleware is where we store all the specifics related to the client's // Middleware is where we store all the specifics related to the client's
// JWT needs. // JWT needs.
@ -56,6 +59,8 @@ type Middleware struct {
secret string secret string
auth AuthFunc auth AuthFunc
claims ClaimsFunc claims ClaimsFunc
identityField string
verifyField string
} }
// New creates a new Middleware from a user-specified configuration. // New creates a new Middleware from a user-specified configuration.
@ -72,10 +77,18 @@ func New(c *Config) (*Middleware, error) {
if c.Claims == nil { if c.Claims == nil {
return nil, ErrMissingClaimsFunc return nil, ErrMissingClaimsFunc
} }
if c.IdentityField == "" {
c.IdentityField = "email"
}
if c.VerifyField == "" {
c.VerifyField = "password"
}
m := &Middleware{ m := &Middleware{
secret: c.Secret, secret: c.Secret,
auth: c.Auth, auth: c.Auth,
claims: c.Claims, claims: c.Claims,
identityField: c.IdentityField,
verifyField: c.VerifyField,
} }
return m, nil return m, nil
} }
@ -142,7 +155,7 @@ func (m *Middleware) Secure(h http.Handler, v VerifyClaimsFunc) http.Handler {
message: "decoding claims", message: "decoding claims",
} }
} }
err = v(claimSet) err = v(claimSet, r)
if err != nil { if err != nil {
return &jwtError{ return &jwtError{
status: http.StatusUnauthorized, status: http.StatusUnauthorized,
@ -163,6 +176,13 @@ func (m *Middleware) Secure(h http.Handler, v VerifyClaimsFunc) http.Handler {
// the requester. // the requester.
func (m *Middleware) GenerateToken() http.Handler { func (m *Middleware) GenerateToken() http.Handler {
generateHandler := func(w http.ResponseWriter, r *http.Request) *jwtError { generateHandler := func(w http.ResponseWriter, r *http.Request) *jwtError {
if r.Method != "POST" {
return &jwtError{
status: http.StatusBadRequest,
err: ErrInvalidMethod,
message: "receiving request",
}
}
var b map[string]string var b map[string]string
err := json.NewDecoder(r.Body).Decode(&b) err := json.NewDecoder(r.Body).Decode(&b)
if err != nil { if err != nil {
@ -172,7 +192,22 @@ func (m *Middleware) GenerateToken() http.Handler {
message: "parsing authorization", message: "parsing authorization",
} }
} }
err = m.auth(b["email"], b["password"]) // Check if required fields are in the body
if _, ok := b[m.identityField]; !ok {
return &jwtError{
status: http.StatusBadRequest,
err: ErrParsingCredentials,
message: "parsing credentials, missing identity field",
}
}
if _, ok := b[m.verifyField]; !ok {
return &jwtError{
status: http.StatusBadRequest,
err: ErrParsingCredentials,
message: "parsing credentials, missing verify field",
}
}
err = m.auth(b[m.identityField], b[m.verifyField])
if err != nil { if err != nil {
return &jwtError{ return &jwtError{
status: http.StatusInternalServerError, status: http.StatusInternalServerError,
@ -192,7 +227,7 @@ func (m *Middleware) GenerateToken() http.Handler {
} }
// Generate claims for user // Generate claims for user
claims, err := m.claims(b["email"]) claims, err := m.claims(b[m.identityField])
if err != nil { if err != nil {
return &jwtError{ return &jwtError{
status: http.StatusInternalServerError, status: http.StatusInternalServerError,

View file

@ -32,7 +32,7 @@ var claimsFunc = func(id string) (map[string]interface{}, error) {
}, nil }, nil
} }
var verifyClaimsFunc = func(claims []byte) error { var verifyClaimsFunc = func(claims []byte, r *http.Request) error {
currentTime := time.Now() currentTime := time.Now()
var c struct { var c struct {
Exp int64 Exp int64
@ -100,6 +100,12 @@ func TestNewJWTMiddleware(t *testing.T) {
if _, ok := claimsVal["iat"]; !ok { if _, ok := claimsVal["iat"]; !ok {
t.Errorf("wanted a claims set, got %v", claimsVal) t.Errorf("wanted a claims set, got %v", claimsVal)
} }
if middleware.identityField != "email" {
t.Errorf("wanted email, got %v", middleware.identityField)
}
if middleware.verifyField != "password" {
t.Errorf("wanted password, got %v", middleware.verifyField)
}
} }
func TestNewJWTMiddlewareNoConfig(t *testing.T) { func TestNewJWTMiddlewareNoConfig(t *testing.T) {
@ -215,3 +221,14 @@ func TestSecureHandlerGoodToken(t *testing.T) {
t.Errorf("wanted %s, got %s", "test", body) t.Errorf("wanted %s, got %s", "test", body)
} }
} }
func TestGenerateTokenHandlerNotPOST(t *testing.T) {
middleware := newMiddlewareOrFatal(t)
resp := httptest.NewRecorder()
req, _ := http.NewRequest("PUT", "http://example.com", nil)
middleware.GenerateToken().ServeHTTP(resp, req)
body := strings.TrimSpace(resp.Body.String())
if body != ErrInvalidMethod.Error() {
t.Errorf("wanted %q, got %q", ErrInvalidMethod.Error(), body)
}
}