all repos

onasty @ 060f2e6

a one-time notes service

onasty/internal/jwtutil/jwtutil.go (view raw)

Smirnov Oleksandr Smirnov Oleksandr
ss2316544@gmail.com
feat: notes manipulations for the note authors (#117)..., 1 year ago
1
package jwtutil
2
3
import (
4
	"crypto/rand"
5
	"encoding/hex"
6
	"errors"
7
	"time"
8
9
	"github.com/golang-jwt/jwt/v5"
10
)
11
12
var (
13
	ErrUnexpectedSigningMethod = errors.New("unexpected signing method")
14
	ErrTokenExpired            = errors.New("token expired")
15
)
16
17
type JWTTokenizer interface {
18
	// AccessToken generates a new access token with the given [Payload].
19
	AccessToken(pl Payload) (string, error)
20
21
	// RefreshToken generates a random string of 64 chars.
22
	RefreshToken() (string, error)
23
24
	// Parse parses the token and returns its [Payload].
25
	Parse(token string) (Payload, error)
26
}
27
28
// Payload the access token payload
29
type Payload struct {
30
	UserID string
31
}
32
33
var _ JWTTokenizer = (*JWTUtil)(nil)
34
35
type JWTUtil struct {
36
	signingKey     string
37
	accessTokenTTL time.Duration
38
}
39
40
func NewJWTUtil(signingKey string, accessTokenTTL time.Duration) *JWTUtil {
41
	return &JWTUtil{
42
		signingKey:     signingKey,
43
		accessTokenTTL: accessTokenTTL,
44
	}
45
}
46
47
func (j *JWTUtil) AccessToken(pl Payload) (string, error) {
48
	tok := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
49
		Subject:   pl.UserID,
50
		ExpiresAt: jwt.NewNumericDate(time.Now().Add(j.accessTokenTTL)),
51
	})
52
	return tok.SignedString([]byte(j.signingKey))
53
}
54
55
func (j *JWTUtil) RefreshToken() (string, error) {
56
	b := make([]byte, 32)
57
	if _, err := rand.Read(b); err != nil {
58
		return "", err
59
	}
60
	return hex.EncodeToString(b), nil
61
}
62
63
func (j *JWTUtil) Parse(token string) (Payload, error) {
64
	var claims jwt.RegisteredClaims
65
	_, err := jwt.ParseWithClaims(token, &claims, func(t *jwt.Token) (any, error) {
66
		if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
67
			return nil, ErrUnexpectedSigningMethod
68
		}
69
		return []byte(j.signingKey), nil
70
	})
71
72
	if errors.Is(err, jwt.ErrTokenExpired) {
73
		return Payload{}, ErrTokenExpired
74
	}
75
76
	return Payload{
77
		UserID: claims.Subject,
78
	}, err
79
}