Refresh token
This commit is contained in:
		
							parent
							
								
									2295a6e84c
								
							
						
					
					
						commit
						82f5628266
					
				
					 4 changed files with 108 additions and 83 deletions
				
			
		
							
								
								
									
										2
									
								
								Godeps/Godeps.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								Godeps/Godeps.json
									
										
									
										generated
									
									
									
								
							|  | @ -54,7 +54,7 @@ | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"ImportPath": "github.com/thermokarst/jwt", | 			"ImportPath": "github.com/thermokarst/jwt", | ||||||
| 			"Rev": "7752009bbb5cea39ab392a846c467eab4b98478f" | 			"Rev": "88ac9569ee8c8fc9083704a7219334fcc210c6a5" | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			"ImportPath": "golang.org/x/crypto/bcrypt", | 			"ImportPath": "golang.org/x/crypto/bcrypt", | ||||||
|  |  | ||||||
							
								
								
									
										104
									
								
								Godeps/_workspace/src/github.com/thermokarst/jwt/jwt.go
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										104
									
								
								Godeps/_workspace/src/github.com/thermokarst/jwt/jwt.go
									
										
									
										generated
									
									
										vendored
									
									
								
							|  | @ -115,64 +115,12 @@ func (m *Middleware) Secure(h http.Handler, v VerifyClaimsFunc) http.Handler { | ||||||
| 		} else { | 		} else { | ||||||
| 			token = strings.Split(authHeader, " ")[1] | 			token = strings.Split(authHeader, " ")[1] | ||||||
| 		} | 		} | ||||||
| 		tokenParts := strings.Split(token, ".") |  | ||||||
| 		if len(tokenParts) != 3 { |  | ||||||
| 			return &jwtError{status: http.StatusUnauthorized, err: ErrMalformedToken} |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		// First, verify JOSE header | 		if status, err, message := m.VerifyToken(token, v, r); err != nil { | ||||||
| 		header, err := decode(tokenParts[0]) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return &jwtError{ | 			return &jwtError{ | ||||||
| 				status:  http.StatusInternalServerError, | 				status:  status, | ||||||
| 				err:     err, | 				err:     err, | ||||||
| 				message: fmt.Sprintf("decoding header (%v)", tokenParts[0]), | 				message: message, | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		var t struct { |  | ||||||
| 			Typ string |  | ||||||
| 			Alg string |  | ||||||
| 		} |  | ||||||
| 		err = json.Unmarshal(header, &t) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return &jwtError{ |  | ||||||
| 				status:  http.StatusInternalServerError, |  | ||||||
| 				err:     ErrMalformedToken, |  | ||||||
| 				message: fmt.Sprintf("unmarshalling header (%s)", header), |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Then, verify signature |  | ||||||
| 		mac := hmac.New(sha256.New, []byte(m.secret)) |  | ||||||
| 		message := []byte(strings.Join([]string{tokenParts[0], tokenParts[1]}, ".")) |  | ||||||
| 		mac.Write(message) |  | ||||||
| 		expectedMac, err := encode(mac.Sum(nil)) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return &jwtError{status: http.StatusInternalServerError, err: err} |  | ||||||
| 		} |  | ||||||
| 		if !hmac.Equal([]byte(tokenParts[2]), []byte(expectedMac)) { |  | ||||||
| 			return &jwtError{ |  | ||||||
| 				status:  http.StatusUnauthorized, |  | ||||||
| 				err:     ErrInvalidSignature, |  | ||||||
| 				message: fmt.Sprintf("checking signature (%v)", tokenParts[2]), |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		// Finally, check claims |  | ||||||
| 		claimSet, err := decode(tokenParts[1]) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return &jwtError{ |  | ||||||
| 				status:  http.StatusInternalServerError, |  | ||||||
| 				err:     ErrDecoding, |  | ||||||
| 				message: "decoding claims", |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		err = v(claimSet, r) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return &jwtError{ |  | ||||||
| 				status:  http.StatusUnauthorized, |  | ||||||
| 				err:     err, |  | ||||||
| 				message: "handling claims callback", |  | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | @ -279,6 +227,52 @@ func (m *Middleware) CreateToken(identity string) (string, error) { | ||||||
| 	return response, nil | 	return response, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // VerifyToken verifies a token | ||||||
|  | func (m *Middleware) VerifyToken(token string, v VerifyClaimsFunc, r *http.Request) (int, error, string) { | ||||||
|  | 	tokenParts := strings.Split(token, ".") | ||||||
|  | 	if len(tokenParts) != 3 { | ||||||
|  | 		return http.StatusUnauthorized, ErrMalformedToken, "" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// First, verify JOSE header | ||||||
|  | 	header, err := decode(tokenParts[0]) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return http.StatusInternalServerError, err, fmt.Sprintf("decoding header (%v)", tokenParts[0]) | ||||||
|  | 	} | ||||||
|  | 	var t struct { | ||||||
|  | 		Typ string | ||||||
|  | 		Alg string | ||||||
|  | 	} | ||||||
|  | 	err = json.Unmarshal(header, &t) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return http.StatusInternalServerError, ErrMalformedToken, fmt.Sprintf("unmarshalling header (%s)", header) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Then, verify signature | ||||||
|  | 	mac := hmac.New(sha256.New, []byte(m.secret)) | ||||||
|  | 	message := []byte(strings.Join([]string{tokenParts[0], tokenParts[1]}, ".")) | ||||||
|  | 	mac.Write(message) | ||||||
|  | 	expectedMac, err := encode(mac.Sum(nil)) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return http.StatusInternalServerError, err, "" | ||||||
|  | 	} | ||||||
|  | 	if !hmac.Equal([]byte(tokenParts[2]), []byte(expectedMac)) { | ||||||
|  | 		return http.StatusUnauthorized, ErrInvalidSignature, fmt.Sprintf("checking signature (%v)", tokenParts[2]) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Finally, check claims | ||||||
|  | 	claimSet, err := decode(tokenParts[1]) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return http.StatusInternalServerError, ErrDecoding, "decoding claims" | ||||||
|  | 	} | ||||||
|  | 	err = v(claimSet, r) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return http.StatusUnauthorized, err, "handling claims callback" | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 200, nil, "" | ||||||
|  | } | ||||||
|  | 
 | ||||||
| type jwtError struct { | type jwtError struct { | ||||||
| 	status  int | 	status  int | ||||||
| 	err     error | 	err     error | ||||||
|  |  | ||||||
							
								
								
									
										53
									
								
								handlers.go
									
										
									
									
									
								
							
							
						
						
									
										53
									
								
								handlers.go
									
										
									
									
									
								
							|  | @ -33,6 +33,20 @@ type Claims struct { | ||||||
| 	Ref  string | 	Ref  string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func verifyClaims(claims []byte, r *http.Request) error { | ||||||
|  | 	currentTime := time.Now() | ||||||
|  | 	var c Claims | ||||||
|  | 	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") | ||||||
|  | 	} | ||||||
|  | 	context.Set(r, "claims", c) | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func Handler() http.Handler { | func Handler() http.Handler { | ||||||
| 	claimsFunc := func(email string) (map[string]interface{}, error) { | 	claimsFunc := func(email string) (map[string]interface{}, error) { | ||||||
| 		currentTime := time.Now() | 		currentTime := time.Now() | ||||||
|  | @ -46,25 +60,11 @@ func Handler() http.Handler { | ||||||
| 			"sub":  user.Id, | 			"sub":  user.Id, | ||||||
| 			"role": user.Role, | 			"role": user.Role, | ||||||
| 			"iat":  currentTime.Unix(), | 			"iat":  currentTime.Unix(), | ||||||
| 			"exp":  currentTime.Add(time.Minute * 60 * 24).Unix(), | 			"exp":  currentTime.Add(time.Minute * 60).Unix(), | ||||||
| 			"ref":  "", | 			"ref":  "", | ||||||
| 		}, nil | 		}, nil | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	verifyClaims := func(claims []byte, r *http.Request) error { |  | ||||||
| 		currentTime := time.Now() |  | ||||||
| 		var c Claims |  | ||||||
| 		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") |  | ||||||
| 		} |  | ||||||
| 		context.Set(r, "claims", c) |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	config = &jwt.Config{ | 	config = &jwt.Config{ | ||||||
| 		Secret: os.Getenv("SECRET"), | 		Secret: os.Getenv("SECRET"), | ||||||
| 		Auth:   dbAuthenticate, | 		Auth:   dbAuthenticate, | ||||||
|  | @ -85,6 +85,7 @@ func Handler() http.Handler { | ||||||
| 	measurementService := MeasurementService{} | 	measurementService := MeasurementService{} | ||||||
| 
 | 
 | ||||||
| 	m.Handle("/authenticate", tokenHandler(j.Authenticate())).Methods("POST") | 	m.Handle("/authenticate", tokenHandler(j.Authenticate())).Methods("POST") | ||||||
|  | 	m.Handle("/refresh", j.Secure(errorHandler(tokenRefresh(j)), verifyClaims)).Methods("POST") | ||||||
| 
 | 
 | ||||||
| 	// Everything past here is lumped under a genus | 	// Everything past here is lumped under a genus | ||||||
| 	s := m.PathPrefix("/{genus}").Subrouter() | 	s := m.PathPrefix("/{genus}").Subrouter() | ||||||
|  | @ -307,3 +308,25 @@ func (fn errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { | ||||||
| 		fmt.Fprintln(w, err.Error.Error()) | 		fmt.Fprintln(w, err.Error.Error()) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func tokenRefresh(j *jwt.Middleware) errorHandler { | ||||||
|  | 	t := func(w http.ResponseWriter, r *http.Request) *appError { | ||||||
|  | 		claims := getClaims(r) | ||||||
|  | 		user, err := dbGetUserById(claims.Sub) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return newJSONError(err, http.StatusInternalServerError) | ||||||
|  | 		} | ||||||
|  | 		token, err := j.CreateToken(user.Email) | ||||||
|  | 		if err != nil { | ||||||
|  | 			return newJSONError(err, http.StatusInternalServerError) | ||||||
|  | 		} | ||||||
|  | 		data, _ := json.Marshal(struct { | ||||||
|  | 			Token string `json:"token"` | ||||||
|  | 		}{ | ||||||
|  | 			Token: token, | ||||||
|  | 		}) | ||||||
|  | 		w.Write(data) | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 	return t | ||||||
|  | } | ||||||
|  |  | ||||||
							
								
								
									
										32
									
								
								users.go
									
										
									
									
									
								
							
							
						
						
									
										32
									
								
								users.go
									
										
									
									
									
								
							|  | @ -138,20 +138,11 @@ func (u UserService) list(val *url.Values, claims *Claims) (entity, *appError) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (u UserService) get(id int64, dummy string, claims *Claims) (entity, *appError) { | func (u UserService) get(id int64, dummy string, claims *Claims) (entity, *appError) { | ||||||
| 	var user User | 	user, err := dbGetUserById(id) | ||||||
| 	q := `SELECT id, email, 'password' AS password, name, role, | 	if err != nil { | ||||||
| 		created_at, updated_at, deleted_at |  | ||||||
| 		FROM users |  | ||||||
| 		WHERE id=$1 |  | ||||||
| 		AND verified IS TRUE |  | ||||||
| 		AND deleted_at IS NULL;` |  | ||||||
| 	if err := DBH.SelectOne(&user, q, id); err != nil { |  | ||||||
| 		if err == sql.ErrNoRows { |  | ||||||
| 			return nil, ErrUserNotFoundJSON |  | ||||||
| 		} |  | ||||||
| 		return nil, newJSONError(err, http.StatusInternalServerError) | 		return nil, newJSONError(err, http.StatusInternalServerError) | ||||||
| 	} | 	} | ||||||
| 	return &user, nil | 	return user, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (u UserService) update(id int64, e *entity, dummy string, claims *Claims) *appError { | func (u UserService) update(id int64, e *entity, dummy string, claims *Claims) *appError { | ||||||
|  | @ -245,6 +236,23 @@ func dbAuthenticate(email string, password string) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func dbGetUserById(id int64) (*User, error) { | ||||||
|  | 	var user User | ||||||
|  | 	q := `SELECT id, email, 'password' AS password, name, role, | ||||||
|  | 		created_at, updated_at, deleted_at | ||||||
|  | 		FROM users | ||||||
|  | 		WHERE id=$1 | ||||||
|  | 		AND verified IS TRUE | ||||||
|  | 		AND deleted_at IS NULL;` | ||||||
|  | 	if err := DBH.SelectOne(&user, q, id); err != nil { | ||||||
|  | 		if err == sql.ErrNoRows { | ||||||
|  | 			return nil, ErrUserNotFound | ||||||
|  | 		} | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  | 	return &user, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // for thermokarst/jwt: setting user in claims bundle | // for thermokarst/jwt: setting user in claims bundle | ||||||
| func dbGetUserByEmail(email string) (*User, error) { | func dbGetUserByEmail(email string) (*User, error) { | ||||||
| 	var user User | 	var user User | ||||||
|  |  | ||||||
		Reference in a new issue
	
	 Matthew Dillon
						Matthew Dillon