jwt: increase leeway and add some tests

also export a constant for the Cookie name

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino
2025-10-11 14:14:21 +02:00
parent c4bc88cd2e
commit a768dac29d
6 changed files with 42 additions and 9 deletions

View File

@@ -49,8 +49,7 @@ const (
) )
const ( const (
basicRealm = "Basic realm=\"SFTPGo\"" basicRealm = "Basic realm=\"SFTPGo\""
jwtCookieKey = "jwt"
) )
var ( var (
@@ -142,7 +141,7 @@ func createAndSetCookie(w http.ResponseWriter, r *http.Request, claims *jwt.Clai
func setCookie(w http.ResponseWriter, r *http.Request, cookiePath, cookieValue string, duration time.Duration) { func setCookie(w http.ResponseWriter, r *http.Request, cookiePath, cookieValue string, duration time.Duration) {
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: jwtCookieKey, Name: jwt.CookieKey,
Value: cookieValue, Value: cookieValue,
Path: cookiePath, Path: cookiePath,
Expires: time.Now().Add(duration), Expires: time.Now().Add(duration),
@@ -156,7 +155,7 @@ func setCookie(w http.ResponseWriter, r *http.Request, cookiePath, cookieValue s
func removeCookie(w http.ResponseWriter, r *http.Request, cookiePath string) { func removeCookie(w http.ResponseWriter, r *http.Request, cookiePath string) {
invalidateToken(r) invalidateToken(r)
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: jwtCookieKey, Name: jwt.CookieKey,
Value: "", Value: "",
Path: cookiePath, Path: cookiePath,
Expires: time.Unix(0, 0), Expires: time.Unix(0, 0),

View File

@@ -809,7 +809,7 @@ func removeOIDCCookie(w http.ResponseWriter, r *http.Request) {
func canSkipOIDCValidation(r *http.Request) bool { func canSkipOIDCValidation(r *http.Request) bool {
_, err := r.Cookie(oidcCookieKey) _, err := r.Cookie(oidcCookieKey)
if err != nil { if err != nil {
_, err = r.Cookie(jwtCookieKey) _, err = r.Cookie(jwt.CookieKey)
return err == nil return err == nil
} }
return false return false

View File

@@ -845,7 +845,7 @@ func TestSkipOIDCAuth(t *testing.T) {
rr := httptest.NewRecorder() rr := httptest.NewRecorder()
r, err := http.NewRequest(http.MethodGet, webClientLogoutPath, nil) r, err := http.NewRequest(http.MethodGet, webClientLogoutPath, nil)
assert.NoError(t, err) assert.NoError(t, err)
r.Header.Set("Cookie", fmt.Sprintf("%v=%v", jwtCookieKey, tokenString)) r.Header.Set("Cookie", fmt.Sprintf("%v=%v", jwt.CookieKey, tokenString))
server.router.ServeHTTP(rr, r) server.router.ServeHTTP(rr, r)
assert.Equal(t, http.StatusFound, rr.Code) assert.Equal(t, http.StatusFound, rr.Code)
assert.Equal(t, webClientLoginPath, rr.Header().Get("Location")) assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))

View File

@@ -1071,7 +1071,7 @@ func (s *httpdServer) refreshAdminToken(w http.ResponseWriter, r *http.Request,
func (s *httpdServer) updateContextFromCookie(r *http.Request) *http.Request { func (s *httpdServer) updateContextFromCookie(r *http.Request) *http.Request {
_, err := jwt.FromContext(r.Context()) _, err := jwt.FromContext(r.Context())
if err != nil { if err != nil {
_, err = r.Cookie(jwtCookieKey) _, err = r.Cookie(jwt.CookieKey)
if err != nil { if err != nil {
return r return r
} }

View File

@@ -30,6 +30,10 @@ import (
"github.com/rs/xid" "github.com/rs/xid"
) )
const (
CookieKey = "jwt"
)
var ( var (
TokenCtxKey = &contextKey{"Token"} TokenCtxKey = &contextKey{"Token"}
ErrorCtxKey = &contextKey{"Error"} ErrorCtxKey = &contextKey{"Error"}
@@ -235,7 +239,7 @@ func VerifyTokenWithKey(payload string, algo []jose.SignatureAlgorithm, key any)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := claims.ValidateWithLeeway(jwt.Expected{Time: time.Now()}, 15*time.Second); err != nil { if err := claims.ValidateWithLeeway(jwt.Expected{Time: time.Now()}, 30*time.Second); err != nil {
return nil, err return nil, err
} }
return &claims, nil return &claims, nil
@@ -244,7 +248,7 @@ func VerifyTokenWithKey(payload string, algo []jose.SignatureAlgorithm, key any)
// TokenFromCookie tries to retrieve the token string from a cookie named // TokenFromCookie tries to retrieve the token string from a cookie named
// "jwt". // "jwt".
func TokenFromCookie(r *http.Request) string { func TokenFromCookie(r *http.Request) string {
cookie, err := r.Cookie("jwt") cookie, err := r.Cookie(CookieKey)
if err != nil { if err != nil {
return "" return ""
} }

View File

@@ -223,3 +223,33 @@ func TestContext(t *testing.T) {
assert.Equal(t, "jwt context value Token", TokenCtxKey.String()) assert.Equal(t, "jwt context value Token", TokenCtxKey.String())
} }
func TestValidationLeeway(t *testing.T) {
s, err := NewSigner(jose.HS256, util.GenerateRandomBytes(32))
require.NoError(t, err)
claims := &Claims{}
claims.Audience = []string{util.GenerateUniqueID()}
claims.SetIssuedAt(time.Now().Add(10 * time.Second)) // issued at in the future
claims.SetExpiry(time.Now().Add(10 * time.Second))
token, err := s.Sign(claims)
require.NoError(t, err)
_, err = VerifyToken(s, token)
assert.NoError(t, err)
claims = &Claims{}
claims.Audience = []string{util.GenerateUniqueID()}
claims.SetExpiry(time.Now().Add(-10 * time.Second)) // expired
token, err = s.Sign(claims)
require.NoError(t, err)
_, err = VerifyToken(s, token)
assert.NoError(t, err)
claims = &Claims{}
claims.Audience = []string{util.GenerateUniqueID()}
claims.SetExpiry(time.Now().Add(30 * time.Second))
claims.SetNotBefore(time.Now().Add(10 * time.Second)) // not before in the future
token, err = s.Sign(claims)
require.NoError(t, err)
_, err = VerifyToken(s, token)
assert.NoError(t, err)
}