taskcafe/internal/auth/auth.go
Jordan Knott 52c60abcd7 fix: secret key is no longer hard coded
the secret key for signing JWT tokens is now read from server.secret.

if that does not exist, then a random UUID v4 is generated and used
instead. a log warning is also shown.
2020-09-12 18:03:17 -05:00

122 lines
3.6 KiB
Go

package auth
import (
"time"
"github.com/dgrijalva/jwt-go"
log "github.com/sirupsen/logrus"
)
// RestrictedMode is used restrict JWT access to just the install route
type RestrictedMode string
const (
// Unrestricted is the code to allow access to all routes
Unrestricted RestrictedMode = "unrestricted"
// InstallOnly is the code to restrict access ONLY to install route
InstallOnly = "install_only"
)
// Role is the role code for the user
type Role string
const (
// RoleAdmin is the code for the admin role
RoleAdmin Role = "admin"
// RoleMember is the code for the member role
RoleMember Role = "member"
)
// AccessTokenClaims is the claims the access JWT token contains
type AccessTokenClaims struct {
UserID string `json:"userId"`
Restricted RestrictedMode `json:"restricted"`
OrgRole Role `json:"orgRole"`
jwt.StandardClaims
}
// ErrExpiredToken is the error returned if the token has expired
type ErrExpiredToken struct{}
// Error returns the error message for ErrExpiredToken
func (r *ErrExpiredToken) Error() string {
return "token is expired"
}
// ErrMalformedToken is the error returned if the token has malformed
type ErrMalformedToken struct{}
// Error returns the error message for ErrMalformedToken
func (r *ErrMalformedToken) Error() string {
return "token is malformed"
}
// NewAccessToken generates a new JWT access token with the correct claims
func NewAccessToken(userID string, restrictedMode RestrictedMode, orgRole string, jwtKey []byte) (string, error) {
role := RoleMember
if orgRole == "admin" {
role = RoleAdmin
}
accessExpirationTime := time.Now().Add(5 * time.Second)
accessClaims := &AccessTokenClaims{
UserID: userID,
Restricted: restrictedMode,
OrgRole: role,
StandardClaims: jwt.StandardClaims{ExpiresAt: accessExpirationTime.Unix()},
}
accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
accessTokenString, err := accessToken.SignedString(jwtKey)
if err != nil {
return "", err
}
return accessTokenString, nil
}
// NewAccessTokenCustomExpiration creates an access token with a custom duration
func NewAccessTokenCustomExpiration(userID string, dur time.Duration, jwtKey []byte) (string, error) {
accessExpirationTime := time.Now().Add(dur)
accessClaims := &AccessTokenClaims{
UserID: userID,
Restricted: Unrestricted,
OrgRole: RoleMember,
StandardClaims: jwt.StandardClaims{ExpiresAt: accessExpirationTime.Unix()},
}
accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
accessTokenString, err := accessToken.SignedString(jwtKey)
if err != nil {
return "", err
}
return accessTokenString, nil
}
// ValidateAccessToken validates a JWT access token and returns the contained claims or an error if it's invalid
func ValidateAccessToken(accessTokenString string, jwtKey []byte) (AccessTokenClaims, error) {
accessClaims := &AccessTokenClaims{}
accessToken, err := jwt.ParseWithClaims(accessTokenString, accessClaims, func(token *jwt.Token) (interface{}, error) {
return jwtKey, nil
})
if err != nil {
return *accessClaims, nil
}
if accessToken.Valid {
log.WithFields(log.Fields{
"token": accessTokenString,
"timeToExpire": time.Unix(accessClaims.ExpiresAt, 0),
}).Debug("token is valid")
return *accessClaims, nil
}
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return AccessTokenClaims{}, &ErrMalformedToken{}
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
return AccessTokenClaims{}, &ErrExpiredToken{}
}
}
return AccessTokenClaims{}, err
}