jwt.Instance.GetConnectedUserID   B
last analyzed

Complexity

Conditions 6

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 15
nop 2
dl 0
loc 28
rs 8.6666
c 0
b 0
f 0
1
package jwt
2
3
import (
4
	"context"
5
	"strings"
6
	"sync"
7
	"time"
8
9
	"github.com/golang-jwt/jwt/v5"
10
	"github.com/memnix/memnix-rest/pkg/utils"
11
	"github.com/pkg/errors"
12
	"golang.org/x/crypto/ed25519"
13
)
14
15
type InstanceSingleton struct {
16
	jwtInstance Instance
17
}
18
19
var (
20
	jwtInstance *InstanceSingleton //nolint:gochecknoglobals //Singleton
21
	jwtOnce     sync.Once          //nolint:gochecknoglobals //Singleton
22
)
23
24
func GetJwtInstance() *InstanceSingleton {
25
	jwtOnce.Do(func() {
26
		jwtInstance = &InstanceSingleton{}
27
	})
28
	return jwtInstance
29
}
30
31
func (j *InstanceSingleton) GetJwt() Instance {
32
	return j.jwtInstance
33
}
34
35
func (j *InstanceSingleton) SetJwt(instance Instance) {
36
	j.jwtInstance = instance
37
}
38
39
type Instance struct {
40
	SigningMethod         jwt.SigningMethod
41
	PublicKey             ed25519.PublicKey
42
	PrivateKey            ed25519.PrivateKey
43
	HeaderLen             int
44
	ExpirationTimeInHours int
45
}
46
47
// NewJWTInstance return a new JwtInstance with the given parameters.
48
func NewJWTInstance(headerLen, expirationTime int,
49
	publicKey ed25519.PublicKey, privateKey ed25519.PrivateKey,
50
) Instance {
51
	return Instance{
52
		HeaderLen:             headerLen,
53
		PublicKey:             publicKey,
54
		PrivateKey:            privateKey,
55
		SigningMethod:         jwt.SigningMethodEdDSA,
56
		ExpirationTimeInHours: expirationTime,
57
	}
58
}
59
60
// GenerateToken generates a jwt token from a user id
61
// and returns the token and an error
62
//
63
// It's signing method is defined in utils.JwtSigningMethod
64
// It's expiration time is defined in utils.GetExpirationTime
65
// It's secret key is defined in the environment variable SECRET_KEY
66
// see: utils/config.go for more information
67
func (instance Instance) GenerateToken(_ context.Context, userID int32) (string, error) {
68
	// Create the Claims for the token
69
	claims := jwt.NewWithClaims(instance.SigningMethod, jwt.RegisteredClaims{
70
		Issuer:    utils.ConvertInt32ToStr(userID),    // Issuer is the user id
71
		ExpiresAt: instance.CalculateExpirationTime(), // ExpiresAt is the expiration time
72
	})
73
74
	// Sign and get the complete encoded token as a string using the secret
75
	token, err := claims.SignedString(instance.PrivateKey)
76
	if err != nil {
77
		return "", errors.Wrap(err, "failed to sign")
78
	}
79
80
	return token, nil
81
}
82
83
// VerifyToken verifies a jwt token
84
// and returns the user id and an error.
85
func (Instance) VerifyToken(token *jwt.Token) (int32, error) {
86
	// claims is of type jwt.MapClaims
87
	if claims, ok := token.Claims.(jwt.MapClaims); token.Valid && ok {
88
		// Get the issuer from the claims and convert it to uint
89
		userID, err := utils.ConvertStrToInt32(claims["iss"].(string))
90
		if err != nil {
91
			return 0, err
92
		}
93
94
		return userID, nil
95
	}
96
97
	return 0, errors.New("invalid token")
98
}
99
100
// GetToken gets a jwt.Token token from a string
101
// and returns the jwt.Token and an error.
102
func (instance Instance) GetToken(_ context.Context, token string) (*jwt.Token, error) {
103
	// Parse takes the token string and a function for looking up the key.
104
	return jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
105
		if _, ok := token.Method.(*jwt.SigningMethodEd25519); !ok {
106
			return nil, errors.New("unexpected signing method")
107
		}
108
		return instance.PublicKey, nil // Return the secret key as the signing key
109
	})
110
}
111
112
func (Instance) GetExpirationTime(token *jwt.Token) int64 {
113
	// Safe type assertion
114
	claims, ok := token.Claims.(jwt.MapClaims)
115
	if !ok {
116
		return 0
117
	}
118
	return int64(claims["exp"].(float64))
119
}
120
121
// ExtractToken function to extract token from header.
122
func (instance Instance) ExtractToken(token string) string {
123
	// Normally Authorization HTTP header.
124
	onlyToken := strings.Split(token, " ") // Split token.
125
	if len(onlyToken) == instance.HeaderLen {
126
		return onlyToken[1] // Return only token.
127
	}
128
	return "" // Return empty string.
129
}
130
131
// GetConnectedUserID gets the user id from a jwt token.
132
func (instance Instance) GetConnectedUserID(ctx context.Context, tokenHeader string) (int32, error) {
133
	if tokenHeader == "" {
134
		return 0, errors.New("empty token")
135
	}
136
137
	// Get the token from the Authorization header.
138
	tokenString := instance.ExtractToken(tokenHeader)
139
140
	if tokenString == "" {
141
		return 0, errors.New("empty token")
142
	}
143
144
	token, err := instance.GetToken(ctx, tokenString)
145
	if err != nil {
146
		return 0, errors.New("invalid token")
147
	}
148
149
	// Check if the token is valid.
150
	userID, err := instance.VerifyToken(token)
151
	if err != nil {
152
		return 0, err
153
	}
154
155
	if userID == 0 {
156
		return 0, errors.New("invalid token")
157
	}
158
159
	return userID, nil
160
}
161
162
// CalculateExpirationTime returns the expiration time.
163
func (instance Instance) CalculateExpirationTime() *jwt.NumericDate {
164
	return jwt.NewNumericDate(time.Now().Add(time.Hour * time.Duration(instance.ExpirationTimeInHours)))
165
}
166