1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package auth
16
17 import (
18 "context"
19 "crypto/ecdsa"
20 "crypto/rsa"
21 "errors"
22 "time"
23
24 "github.com/golang-jwt/jwt/v4"
25 "go.uber.org/zap"
26 )
27
28 type tokenJWT struct {
29 lg *zap.Logger
30 signMethod jwt.SigningMethod
31 key interface{}
32 ttl time.Duration
33 verifyOnly bool
34 }
35
36 func (t *tokenJWT) enable() {}
37 func (t *tokenJWT) disable() {}
38 func (t *tokenJWT) invalidateUser(string) {}
39 func (t *tokenJWT) genTokenPrefix() (string, error) { return "", nil }
40
41 func (t *tokenJWT) info(ctx context.Context, token string, rev uint64) (*AuthInfo, bool) {
42
43 var (
44 username string
45 revision float64
46 )
47
48 parsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
49 if token.Method.Alg() != t.signMethod.Alg() {
50 return nil, errors.New("invalid signing method")
51 }
52 switch k := t.key.(type) {
53 case *rsa.PrivateKey:
54 return &k.PublicKey, nil
55 case *ecdsa.PrivateKey:
56 return &k.PublicKey, nil
57 default:
58 return t.key, nil
59 }
60 })
61
62 if err != nil {
63 t.lg.Warn(
64 "failed to parse a JWT token",
65 zap.String("token", token),
66 zap.Error(err),
67 )
68 return nil, false
69 }
70
71 claims, ok := parsed.Claims.(jwt.MapClaims)
72 if !parsed.Valid || !ok {
73 t.lg.Warn("invalid JWT token", zap.String("token", token))
74 return nil, false
75 }
76
77 username, ok = claims["username"].(string)
78 if !ok {
79 t.lg.Warn("failed to obtain user claims from jwt token")
80 return nil, false
81 }
82
83 revision, ok = claims["revision"].(float64)
84 if !ok {
85 t.lg.Warn("failed to obtain revision claims from jwt token")
86 return nil, false
87 }
88
89 return &AuthInfo{Username: username, Revision: uint64(revision)}, true
90 }
91
92 func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64) (string, error) {
93 if t.verifyOnly {
94 return "", ErrVerifyOnly
95 }
96
97
98
99 tk := jwt.NewWithClaims(t.signMethod,
100 jwt.MapClaims{
101 "username": username,
102 "revision": revision,
103 "exp": time.Now().Add(t.ttl).Unix(),
104 })
105
106 token, err := tk.SignedString(t.key)
107 if err != nil {
108 t.lg.Debug(
109 "failed to sign a JWT token",
110 zap.String("user-name", username),
111 zap.Uint64("revision", revision),
112 zap.Error(err),
113 )
114 return "", err
115 }
116
117 t.lg.Debug(
118 "created/assigned a new JWT token",
119 zap.String("user-name", username),
120 zap.Uint64("revision", revision),
121 zap.String("token", token),
122 )
123 return token, err
124 }
125
126 func newTokenProviderJWT(lg *zap.Logger, optMap map[string]string) (*tokenJWT, error) {
127 if lg == nil {
128 lg = zap.NewNop()
129 }
130 var err error
131 var opts jwtOptions
132 err = opts.ParseWithDefaults(optMap)
133 if err != nil {
134 lg.Error("problem loading JWT options", zap.Error(err))
135 return nil, ErrInvalidAuthOpts
136 }
137
138 var keys = make([]string, 0, len(optMap))
139 for k := range optMap {
140 if !knownOptions[k] {
141 keys = append(keys, k)
142 }
143 }
144 if len(keys) > 0 {
145 lg.Warn("unknown JWT options", zap.Strings("keys", keys))
146 }
147
148 key, err := opts.Key()
149 if err != nil {
150 return nil, err
151 }
152
153 t := &tokenJWT{
154 lg: lg,
155 ttl: opts.TTL,
156 signMethod: opts.SignMethod,
157 key: key,
158 }
159
160 switch t.signMethod.(type) {
161 case *jwt.SigningMethodECDSA:
162 if _, ok := t.key.(*ecdsa.PublicKey); ok {
163 t.verifyOnly = true
164 }
165 case *jwt.SigningMethodRSA, *jwt.SigningMethodRSAPSS:
166 if _, ok := t.key.(*rsa.PublicKey); ok {
167 t.verifyOnly = true
168 }
169 }
170
171 return t, nil
172 }
173
View as plain text