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

@ -89,7 +89,7 @@ There are several other matchers that can be added. To match path prefixes:
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
return r.ProtoMajor == 0 return r.ProtoMajor == 0
}) })
...and finally, it is possible to combine several matchers in a single route: ...and finally, it is possible to combine several matchers in a single route:
@ -164,8 +164,8 @@ This also works for host variables:
// url.String() will be "http://news.domain.com/articles/technology/42" // url.String() will be "http://news.domain.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news", url, err := r.Get("article").URL("subdomain", "news",
"category", "technology", "category", "technology",
"id", "42") "id", "42")
All variables defined in the route are required, and their values must All variables defined in the route are required, and their values must
conform to the corresponding patterns. These requirements guarantee that a conform to the corresponding patterns. These requirements guarantee that a
@ -193,7 +193,7 @@ as well:
// "http://news.domain.com/articles/technology/42" // "http://news.domain.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news", url, err := r.Get("article").URL("subdomain", "news",
"category", "technology", "category", "technology",
"id", "42") "id", "42")
*/ */
package mux package mux

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,72 +9,103 @@ your application:
package main package main
import ( import (
"errors" "encoding/json"
"fmt" "errors"
"net/http" "fmt"
"time" "net/http"
"time"
"github.com/thermokarst/jwt" "github.com/thermokarst/jwt"
) )
func protectMe(w http.ResponseWriter, r *http.Request) { func protectMe(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "secured") fmt.Fprintf(w, "secured")
}
func dontProtectMe(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "not secured")
}
func auth(email string, password string) error {
// Hard-code a user
if email != "test" || password != "test" {
return errors.New("invalid credentials")
}
return nil
}
func setClaims(id string) (map[string]interface{}, error) {
currentTime := time.Now()
return map[string]interface{}{
"iat": currentTime.Unix(),
"exp": currentTime.Add(time.Minute * 60 * 24).Unix(),
}, nil
}
func verifyClaims(claims []byte, r *http.Request) error {
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
} }
func main() { func main() {
var authFunc = func(email string, password string) error { config := &jwt.Config{
// Hard-code a user --- this could easily be a database call, etc. Secret: "password",
if email != "test" || password != "test" { Auth: auth,
return errors.New("invalid credentials") Claims: setClaims,
} }
return nil
}
var claimsFunc = func(userId string) (map[string]interface{}, error) { j, err := jwt.New(config)
currentTime := time.Now() if err != nil {
return map[string]interface{}{ panic(err)
"iat": currentTime.Unix(), }
"exp": currentTime.Add(time.Minute * 60 * 24).Unix(),
"sub": userId,
}, nil
}
var verifyClaimsFunc = func(claims []byte) error { protect := http.HandlerFunc(protectMe)
currentTime := time.Now() dontProtect := http.HandlerFunc(dontProtectMe)
var c struct {
Exp int64
Iat int64
Sub string
}
err := json.Unmarshal(claims, &c)
if err != nil {
return err
}
if currentTime.After(time.Unix(c.Exp, 0)) {
return errors.New("this token has expired!")
}
if c.Sub != "test" {
return errors.New("who are you??!")
}
return nil
}
config := &jwt.Config{ http.Handle("/authenticate", j.GenerateToken())
Secret: "password", http.Handle("/secure", j.Secure(protect, verifyClaims))
Auth: authFunc, http.Handle("/insecure", dontProtect)
Claims: claimsFunc, http.ListenAndServe(":8080", nil)
}
j, err := jwt.New(config)
if err != nil {
panic(err)
}
protect := http.HandlerFunc(protectMe)
http.Handle("/authenticate", j.GenerateToken())
http.Handle("/secure", j.Secure(protect, verifyClaimsFunc))
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 auth(email string, password string) error {
// Hard-code a user
if email != "test" || password != "test" {
return errors.New("invalid credentials")
}
return nil
}
func setClaims(id string) (map[string]interface{}, error) {
currentTime := time.Now()
return map[string]interface{}{
"iat": currentTime.Unix(),
"exp": currentTime.Add(time.Minute * 60 * 24).Unix(),
}, nil
}
func verifyClaims(claims []byte, r *http.Request) error {
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
}
func main() { func main() {
var authFunc = func(email string, password string) error {
// Hard-code a user
if email != "test" || password != "test" {
return errors.New("invalid credentials")
}
return nil
}
var claimsFunc = func(string) (map[string]interface{}, error) {
currentTime := time.Now()
return map[string]interface{}{
"iat": currentTime.Unix(),
"exp": currentTime.Add(time.Minute * 60 * 24).Unix(),
}, nil
}
var verifyClaimsFunc = func([]byte) error {
// We don't really care about the claims, just approve as-is
return nil
}
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,15 +41,26 @@ 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.
type Middleware struct { 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)
}
}