-
Notifications
You must be signed in to change notification settings - Fork 43
/
credentials.go
215 lines (173 loc) · 6.74 KB
/
credentials.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
package jwt
import (
"errors"
"log"
"time"
"github.com/adam-hanna/randomstrings"
jwtGo "github.com/dgrijalva/jwt-go"
)
type credentials struct {
CsrfString string
AuthToken *jwtToken
RefreshToken *jwtToken
options credentialsOptions
}
type credentialsOptions struct {
AuthTokenValidTime time.Duration
RefreshTokenValidTime time.Duration
CheckTokenId TokenIdChecker
SigningMethodString string
VerifyOnlyServer bool
Debug bool
}
func (c *credentials) myLog(stoofs interface{}) {
if c.options.Debug {
log.Println(stoofs)
}
}
func (a *Auth) buildCredentialsFromClaims(c *credentials, claims *ClaimsType) *jwtError {
newCsrfString, err := generateNewCsrfString()
if err != nil {
return newJwtError(err, 500)
}
c.CsrfString = newCsrfString
c.options.AuthTokenValidTime = a.options.AuthTokenValidTime
c.options.RefreshTokenValidTime = a.options.RefreshTokenValidTime
c.options.CheckTokenId = a.checkTokenId
c.options.VerifyOnlyServer = a.options.VerifyOnlyServer
c.options.SigningMethodString = a.options.SigningMethodString
c.options.Debug = a.options.Debug
authClaims := *claims
authClaims.Csrf = newCsrfString
authClaims.StandardClaims.ExpiresAt = time.Now().Add(a.options.AuthTokenValidTime).Unix()
c.AuthToken = c.newTokenWithClaims(&authClaims, a.options.AuthTokenValidTime)
refreshClaimsClaims := *claims
refreshClaimsClaims.Csrf = newCsrfString
refreshClaimsClaims.StandardClaims.ExpiresAt = time.Now().Add(a.options.RefreshTokenValidTime).Unix()
c.RefreshToken = c.newTokenWithClaims(&refreshClaimsClaims, a.options.RefreshTokenValidTime)
return nil
}
func (a *Auth) buildCredentialsFromStrings(csrfString string, authTokenString string, refreshTokenString string, c *credentials) *jwtError {
// check inputs
if csrfString == "" || authTokenString == "" || refreshTokenString == "" {
return newJwtError(errors.New("Invalid inputs to build credentials. Inputs cannot be blank"), 401)
}
// inputs are good
c.CsrfString = csrfString
c.options.AuthTokenValidTime = a.options.AuthTokenValidTime
c.options.RefreshTokenValidTime = a.options.RefreshTokenValidTime
c.options.CheckTokenId = a.checkTokenId
c.options.VerifyOnlyServer = a.options.VerifyOnlyServer
c.options.SigningMethodString = a.options.SigningMethodString
c.options.Debug = a.options.Debug
// Note: Don't check for errors because it will be done later
// Also, tokens that have expired will throw err?
c.AuthToken = c.buildTokenWithClaimsFromString(authTokenString, a.verifyKey, a.options.AuthTokenValidTime)
c.RefreshToken = c.buildTokenWithClaimsFromString(refreshTokenString, a.verifyKey, a.options.RefreshTokenValidTime)
return nil
}
func (c *credentials) validateCsrfStringAgainstCredentials() *jwtError {
authTokenClaims, ok := c.AuthToken.Token.Claims.(*ClaimsType)
if !ok {
return newJwtError(errors.New("Cannot read token claims"), 500)
}
// note @adam-hanna: check csrf in refresh token? Careful! These tokens are
// coming from a request, and the csrf in the credential may have been
// updated!
// refreshTokenClaims, ok := c.RefreshToken.Claims.(*ClaimsType)
// if !ok {
// return newJwtError(errors.New("Cannot read token claims"), 500)
// }
if c.CsrfString != authTokenClaims.Csrf {
return newJwtError(errors.New("CSRF token doesn't match value in jwts"), 401)
}
return nil
}
func generateNewCsrfString() (string, *jwtError) {
// note @adam-hanna: allow user's to set length?
newCsrf, err := randomstrings.GenerateRandomString(32)
if err != nil {
return "", newJwtError(err, 500)
}
return newCsrf, nil
}
func (c *credentials) updateAuthTokenFromRefreshToken() *jwtError {
refreshTokenClaims, ok := c.RefreshToken.Token.Claims.(*ClaimsType)
if !ok {
return newJwtError(errors.New("Cannot read token claims"), 500)
}
// check if the refresh token has been revoked
if c.options.CheckTokenId(refreshTokenClaims.StandardClaims.Id) {
c.myLog("Refresh token has not been revoked")
// has it expired?
if c.RefreshToken.Token.Valid {
c.myLog("Refresh token is not expired")
// nope, the refresh token has not expired
// issue a new tokens with a new csrf and update all expiries
newCsrfString, err := generateNewCsrfString()
if err != nil {
return newJwtError(err, 500)
}
c.CsrfString = newCsrfString
err = c.AuthToken.updateTokenExpiryAndCsrf(newCsrfString)
if err != nil {
return newJwtError(err, 500)
}
err = c.RefreshToken.updateTokenExpiryAndCsrf(newCsrfString)
return err
}
c.myLog("Refresh token is invalid")
return newJwtError(errors.New("Refresh token is invalid. Cannot refresh auth token."), 401)
}
c.myLog("Refresh token has been revoked")
return newJwtError(errors.New("Refresh token has been revoked. Cannot update auth token"), 401)
}
func (c *credentials) validateAndUpdateCredentials() *jwtError {
// first, check that the csrf token matches what's in the jwts
err := c.validateCsrfStringAgainstCredentials()
if err != nil {
return newJwtError(err, 500)
}
// next, check the auth token in a stateless manner
if c.AuthToken.Token.Valid {
// auth token has not expired and is valid
c.myLog("Auth token has not expired and is valid")
// note @ adam-hanna: we want this to be purely stateless
// don't update any tokens, here
// If this server is allowed to issue new tokens...
// create a new csrf string and update the expiration time of the refresh token.
// We don't want to update the auth expiry here bc that would necessitate checking the...
// validity of the refresh token (which requires a db lookup, and hence isn't statelss)
// if !c.options.VerifyOnlyServer {
// newCsrfString, err := generateNewCsrfString()
// if err != nil {
// return newJwtError(err, 500)
// }
// c.CsrfString = newCsrfString
// err = c.AuthToken.updateTokenCsrf(newCsrfString)
// if err != nil {
// return newJwtError(err, 500)
// }
// err = c.RefreshToken.updateTokenExpiryAndCsrf(newCsrfString)
// return err
// }
return nil
} else if ve, ok := c.AuthToken.ParseErr.(*jwtGo.ValidationError); ok {
c.myLog("Auth token is not valid")
if ve.Errors&(jwtGo.ValidationErrorExpired) != 0 {
c.myLog("Auth token is expired")
if !c.options.VerifyOnlyServer {
// attempt to update the tokens
err = c.updateAuthTokenFromRefreshToken()
return err
}
c.myLog("Auth token is expired and server is not authorized to issue new tokens")
return newJwtError(errors.New("Auth token is expired and server is not authorized to issue new tokens"), 401)
}
c.myLog("Error in auth token")
return newJwtError(errors.New("Auth token is not valid, and not because it has expired"), 401)
} else {
c.myLog("Error in auth token")
return newJwtError(errors.New("Auth token is not valid, and not because it has expired"), 401)
}
}