newsbot-api/internal/handler/v1/jwt.go

129 lines
3.1 KiB
Go

package v1
import (
"errors"
"strings"
"time"
"git.jamestombleson.com/jtom38/newsbot-api/domain"
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo/v4"
)
const (
ErrJwtMissing = "auth token is missing"
ErrJwtClaimsMissing = "claims missing on token"
ErrJwtExpired = "auth token has expired"
ErrJwtScopeMissing = "required scope is missing"
ErrJwtInvalidIssuer = "incorrect server issued the token"
)
type JwtToken struct {
Exp time.Time `json:"exp"`
Iss string `json:"iss"`
Authorized bool `json:"authorized"`
UserName string `json:"username"`
UserId int64 `json:"userId"`
Scopes []string `json:"scopes"`
jwt.RegisteredClaims
}
func (j JwtToken) IsValid(scope string) error {
err := j.hasExpired()
if err != nil {
return err
}
// Check to see if they have the scope to do anything
// if they do, let them pass
err = j.hasScope(domain.ScopeAll)
if err == nil {
return nil
}
err = j.hasScope(scope)
if err != nil {
return err
}
return nil
}
func (j JwtToken) GetUsername() string {
return j.UserName
}
func (j JwtToken) GetUserId() int64 {
return j.UserId
}
func (j JwtToken) hasExpired() error {
// Check to see if the token has expired
//hasExpired := j.Exp.Compare(time.Now())
hasExpired := time.Now().Compare(j.Exp)
if hasExpired == 1 {
return errors.New(ErrJwtExpired)
}
return nil
}
// This will check the users token to make sure they have the correct scope to access the handler.
// It will evaluate if you have the admin scope or the required scope for the handler.
func (j JwtToken) hasScope(scope string) error {
// they have the scope to access everything, so let them pass.
userScopes := strings.Join(j.Scopes, "")
if strings.Contains(domain.ScopeAll, userScopes) {
return nil
}
if strings.Contains(userScopes, scope) {
return nil
}
return errors.New(ErrJwtScopeMissing)
}
func (h *Handler) generateJwt(username, issuer string, userScopes []string, userId int64) (string, error) {
return h.generateJwtWithExp(username, issuer, userScopes, userId, time.Now().Add(10*time.Minute))
}
func (h *Handler) generateJwtWithExp(username, issuer string, userScopes []string, userId int64, expiresAt time.Time) (string, error) {
secret := []byte(h.config.JwtSecret)
// Anyone who wants to decrypt the key needs to use the same method
token := jwt.New(jwt.SigningMethodHS256)
claims := token.Claims.(jwt.MapClaims)
claims["exp"] = expiresAt
claims["authorized"] = true
claims["username"] = username
claims["iss"] = issuer
claims["userId"] = userId
var scopes []string
scopes = append(scopes, userScopes...)
claims["scopes"] = scopes
tokenString, err := token.SignedString(secret)
if err != nil {
return "", err
}
return tokenString, nil
}
func (h *Handler) getJwtTokenFromContext(c echo.Context) (JwtToken, error) {
// Make sure that the request came with a jwtToken
token, ok := c.Get("user").(*jwt.Token)
if !ok {
return JwtToken{}, errors.New(ErrJwtMissing)
}
// Generate the claims from the token
claims, ok := token.Claims.(*JwtToken)
if !ok {
return JwtToken{}, errors.New(ErrJwtClaimsMissing)
}
return *claims, nil
}