2016-12-18 05:21:29 +00:00
|
|
|
package jwt
|
|
|
|
|
|
|
|
import (
|
2020-07-07 21:57:52 +00:00
|
|
|
"errors"
|
|
|
|
|
2021-06-10 22:09:04 +00:00
|
|
|
portainer "github.com/portainer/portainer/api"
|
2016-12-18 05:21:29 +00:00
|
|
|
|
|
|
|
"fmt"
|
2017-03-12 16:24:15 +00:00
|
|
|
"time"
|
|
|
|
|
2016-12-18 05:21:29 +00:00
|
|
|
"github.com/dgrijalva/jwt-go"
|
|
|
|
"github.com/gorilla/securecookie"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Service represents a service for managing JWT tokens.
|
|
|
|
type Service struct {
|
2020-06-09 09:55:36 +00:00
|
|
|
secret []byte
|
|
|
|
userSessionTimeout time.Duration
|
2021-08-31 21:23:21 +00:00
|
|
|
dataStore portainer.DataStore
|
2016-12-18 05:21:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type claims struct {
|
2019-09-09 22:58:26 +00:00
|
|
|
UserID int `json:"id"`
|
|
|
|
Username string `json:"username"`
|
|
|
|
Role int `json:"role"`
|
2016-12-18 05:21:29 +00:00
|
|
|
jwt.StandardClaims
|
|
|
|
}
|
|
|
|
|
2020-07-07 21:57:52 +00:00
|
|
|
var (
|
|
|
|
errSecretGeneration = errors.New("Unable to generate secret key")
|
|
|
|
errInvalidJWTToken = errors.New("Invalid JWT token")
|
|
|
|
)
|
|
|
|
|
2016-12-18 05:21:29 +00:00
|
|
|
// NewService initializes a new service. It will generate a random key that will be used to sign JWT tokens.
|
2021-08-31 21:23:21 +00:00
|
|
|
func NewService(userSessionDuration string, dataStore portainer.DataStore) (*Service, error) {
|
2020-06-09 09:55:36 +00:00
|
|
|
userSessionTimeout, err := time.ParseDuration(userSessionDuration)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-12-18 05:21:29 +00:00
|
|
|
secret := securecookie.GenerateRandomKey(32)
|
|
|
|
if secret == nil {
|
2020-07-07 21:57:52 +00:00
|
|
|
return nil, errSecretGeneration
|
2016-12-18 05:21:29 +00:00
|
|
|
}
|
2020-06-09 09:55:36 +00:00
|
|
|
|
2016-12-18 05:21:29 +00:00
|
|
|
service := &Service{
|
|
|
|
secret,
|
2020-06-09 09:55:36 +00:00
|
|
|
userSessionTimeout,
|
2021-08-31 21:23:21 +00:00
|
|
|
dataStore,
|
2016-12-18 05:21:29 +00:00
|
|
|
}
|
|
|
|
return service, nil
|
|
|
|
}
|
|
|
|
|
2021-08-31 21:23:21 +00:00
|
|
|
func (service *Service) defaultExpireAt() (int64) {
|
|
|
|
return time.Now().Add(service.userSessionTimeout).Unix()
|
|
|
|
}
|
|
|
|
|
2016-12-18 05:21:29 +00:00
|
|
|
// GenerateToken generates a new JWT token.
|
|
|
|
func (service *Service) GenerateToken(data *portainer.TokenData) (string, error) {
|
2021-08-31 21:23:21 +00:00
|
|
|
return service.generateSignedToken(data, service.defaultExpireAt())
|
2021-06-10 22:09:04 +00:00
|
|
|
}
|
2016-12-18 05:21:29 +00:00
|
|
|
|
2021-06-10 22:09:04 +00:00
|
|
|
// GenerateTokenForOAuth generates a new JWT for OAuth login
|
|
|
|
// token expiry time from the OAuth provider is considered
|
|
|
|
func (service *Service) GenerateTokenForOAuth(data *portainer.TokenData, expiryTime *time.Time) (string, error) {
|
2021-08-31 21:23:21 +00:00
|
|
|
expireAt := service.defaultExpireAt()
|
|
|
|
if expiryTime != nil && !expiryTime.IsZero() {
|
|
|
|
expireAt = expiryTime.Unix()
|
|
|
|
}
|
|
|
|
return service.generateSignedToken(data, expireAt)
|
2016-12-18 05:21:29 +00:00
|
|
|
}
|
|
|
|
|
2017-03-12 16:24:15 +00:00
|
|
|
// ParseAndVerifyToken parses a JWT token and verify its validity. It returns an error if token is invalid.
|
|
|
|
func (service *Service) ParseAndVerifyToken(token string) (*portainer.TokenData, error) {
|
|
|
|
parsedToken, err := jwt.ParseWithClaims(token, &claims{}, func(token *jwt.Token) (interface{}, error) {
|
2016-12-18 05:21:29 +00:00
|
|
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
|
|
|
msg := fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
|
|
|
|
return nil, msg
|
|
|
|
}
|
|
|
|
return service.secret, nil
|
|
|
|
})
|
2017-03-12 16:24:15 +00:00
|
|
|
if err == nil && parsedToken != nil {
|
|
|
|
if cl, ok := parsedToken.Claims.(*claims); ok && parsedToken.Valid {
|
|
|
|
tokenData := &portainer.TokenData{
|
2019-09-09 22:58:26 +00:00
|
|
|
ID: portainer.UserID(cl.UserID),
|
|
|
|
Username: cl.Username,
|
|
|
|
Role: portainer.UserRole(cl.Role),
|
2017-03-12 16:24:15 +00:00
|
|
|
}
|
|
|
|
return tokenData, nil
|
|
|
|
}
|
2016-12-18 05:21:29 +00:00
|
|
|
}
|
2017-03-12 16:24:15 +00:00
|
|
|
|
2020-07-07 21:57:52 +00:00
|
|
|
return nil, errInvalidJWTToken
|
2016-12-18 05:21:29 +00:00
|
|
|
}
|
2020-06-09 09:55:36 +00:00
|
|
|
|
|
|
|
// SetUserSessionDuration sets the user session duration
|
|
|
|
func (service *Service) SetUserSessionDuration(userSessionDuration time.Duration) {
|
|
|
|
service.userSessionTimeout = userSessionDuration
|
|
|
|
}
|
2021-06-10 22:09:04 +00:00
|
|
|
|
2021-08-31 21:23:21 +00:00
|
|
|
func (service *Service) generateSignedToken(data *portainer.TokenData, expiresAt int64) (string, error) {
|
2021-06-10 22:09:04 +00:00
|
|
|
cl := claims{
|
|
|
|
UserID: int(data.ID),
|
|
|
|
Username: data.Username,
|
|
|
|
Role: int(data.Role),
|
|
|
|
StandardClaims: jwt.StandardClaims{
|
2021-08-31 21:23:21 +00:00
|
|
|
ExpiresAt: expiresAt,
|
2021-06-10 22:09:04 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, cl)
|
|
|
|
|
|
|
|
signedToken, err := token.SignedString(service.secret)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return signedToken, nil
|
|
|
|
}
|