mirror of
https://github.com/nais/wonderwall.git
synced 2026-05-06 08:27:10 +00:00
The default auto-refresh behaviour occurs 5 minutes before tokens expire, at the earliest. Without inactivity however, tokens are still refreshed at any point after this, as long as the session has not ended. This however, means that refreshes don't occur often enough when inactivity timeouts are enabled. In practice, the session is only refreshed if a request is received within the 5 minute leeway window between a token's expiry and the inactivity timeout. This commit will apply auto-refreshes at the half-life of the inactivity timeout instead, so that users' sessions and timeouts are properly extended on activity.
386 lines
11 KiB
Go
386 lines
11 KiB
Go
package session_test
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/nais/wonderwall/pkg/session"
|
|
)
|
|
|
|
func TestData_HasAccessToken(t *testing.T) {
|
|
data := session.Data{}
|
|
assert.False(t, data.HasAccessToken())
|
|
|
|
data.AccessToken = "some-access-token"
|
|
assert.True(t, data.HasAccessToken())
|
|
}
|
|
|
|
func TestData_HasRefreshToken(t *testing.T) {
|
|
data := session.Data{}
|
|
assert.False(t, data.HasRefreshToken())
|
|
|
|
data.RefreshToken = "some-refresh-token"
|
|
assert.True(t, data.HasRefreshToken())
|
|
}
|
|
|
|
func TestNewMetadata(t *testing.T) {
|
|
tokenLifetime := 30 * time.Minute
|
|
sessionLifetime := time.Hour
|
|
|
|
metadata := session.NewMetadata(tokenLifetime, sessionLifetime)
|
|
|
|
maxDelta := time.Second
|
|
|
|
expected := time.Now()
|
|
actual := metadata.Session.CreatedAt
|
|
assert.WithinDuration(t, expected, actual, maxDelta)
|
|
|
|
expected = time.Now().Add(sessionLifetime)
|
|
actual = metadata.Session.EndsAt
|
|
assert.WithinDuration(t, expected, actual, maxDelta)
|
|
|
|
assert.True(t, metadata.Session.TimeoutAt.IsZero())
|
|
|
|
expected = time.Now()
|
|
actual = metadata.Tokens.RefreshedAt
|
|
assert.WithinDuration(t, expected, actual, maxDelta)
|
|
|
|
expected = time.Now().Add(tokenLifetime)
|
|
actual = metadata.Tokens.ExpireAt
|
|
assert.WithinDuration(t, expected, actual, maxDelta)
|
|
}
|
|
|
|
func TestMetadata_WithTimeout(t *testing.T) {
|
|
tokenLifetime := 30 * time.Minute
|
|
sessionLifetime := time.Hour
|
|
sessionInactivityTimeout := 15 * time.Minute
|
|
maxDelta := time.Second
|
|
|
|
metadata := session.NewMetadata(tokenLifetime, sessionLifetime)
|
|
assert.WithinDuration(t, time.Now().Add(tokenLifetime), metadata.Tokens.ExpireAt, maxDelta)
|
|
|
|
metadata.WithTimeout(sessionInactivityTimeout)
|
|
assert.False(t, metadata.Session.TimeoutAt.IsZero())
|
|
assert.WithinDuration(t, time.Now().Add(sessionInactivityTimeout), metadata.Tokens.ExpireAt, maxDelta)
|
|
assert.WithinDuration(t, metadata.Session.TimeoutAt, metadata.Tokens.ExpireAt, maxDelta)
|
|
|
|
expected := time.Now().Add(sessionInactivityTimeout)
|
|
actual := metadata.Session.TimeoutAt
|
|
assert.WithinDuration(t, expected, actual, maxDelta)
|
|
|
|
previousTimeoutAt := metadata.Session.TimeoutAt
|
|
time.Sleep(100 * time.Millisecond)
|
|
metadata.WithTimeout(sessionInactivityTimeout)
|
|
assert.True(t, metadata.Session.TimeoutAt.After(previousTimeoutAt))
|
|
}
|
|
|
|
func TestMetadata_IsExpired(t *testing.T) {
|
|
t.Run("expired", func(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Tokens: session.MetadataTokens{
|
|
ExpireAt: time.Now().Add(-time.Second),
|
|
},
|
|
}
|
|
|
|
assert.True(t, metadata.IsExpired())
|
|
})
|
|
|
|
t.Run("not expired", func(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Tokens: session.MetadataTokens{
|
|
ExpireAt: time.Now().Add(time.Second),
|
|
},
|
|
}
|
|
|
|
assert.False(t, metadata.IsExpired())
|
|
})
|
|
}
|
|
|
|
func TestMetadata_IsRefreshOnCooldown(t *testing.T) {
|
|
t.Run("delta to last refresh below minimum interval", func(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Tokens: session.MetadataTokens{
|
|
RefreshedAt: time.Now(),
|
|
ExpireAt: time.Now().Add(time.Minute),
|
|
},
|
|
}
|
|
|
|
assert.True(t, metadata.IsRefreshOnCooldown())
|
|
})
|
|
|
|
t.Run("delta to last refresh above minimum interval", func(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Tokens: session.MetadataTokens{
|
|
RefreshedAt: time.Now().Add(-2 * time.Minute),
|
|
ExpireAt: time.Now().Add(time.Minute),
|
|
},
|
|
}
|
|
|
|
assert.False(t, metadata.IsRefreshOnCooldown())
|
|
})
|
|
}
|
|
|
|
func TestMetadata_Refresh(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Tokens: session.MetadataTokens{
|
|
RefreshedAt: time.Now(),
|
|
ExpireAt: time.Now().Add(time.Minute),
|
|
},
|
|
}
|
|
|
|
prevRefreshedAt := metadata.Tokens.RefreshedAt
|
|
prevExpireAt := metadata.Tokens.ExpireAt
|
|
|
|
nextExpirySeconds := int64((2 * time.Minute).Seconds())
|
|
metadata.Refresh(nextExpirySeconds)
|
|
|
|
assert.True(t, metadata.Tokens.RefreshedAt.After(prevRefreshedAt))
|
|
assert.True(t, metadata.Tokens.ExpireAt.After(prevExpireAt))
|
|
}
|
|
|
|
func TestMetadata_RefreshCooldown(t *testing.T) {
|
|
t.Run("token lifetime less than interval", func(t *testing.T) {
|
|
tokenLifetime := time.Minute
|
|
|
|
metadata := session.Metadata{
|
|
Tokens: session.MetadataTokens{
|
|
RefreshedAt: time.Now(),
|
|
ExpireAt: time.Now().Add(tokenLifetime),
|
|
},
|
|
}
|
|
|
|
expected := time.Now().Add(tokenLifetime / 2)
|
|
assert.WithinDuration(t, expected, metadata.RefreshCooldown(), time.Second)
|
|
})
|
|
|
|
t.Run("token lifetime longer than interval", func(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Tokens: session.MetadataTokens{
|
|
RefreshedAt: time.Now(),
|
|
ExpireAt: time.Now().Add(time.Hour),
|
|
},
|
|
}
|
|
|
|
expected := metadata.Tokens.RefreshedAt.Add(session.RefreshMinInterval)
|
|
assert.WithinDuration(t, expected, metadata.RefreshCooldown(), time.Second)
|
|
})
|
|
}
|
|
|
|
func TestMetadata_ShouldRefresh(t *testing.T) {
|
|
t.Run("refresh is on cooldown", func(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Tokens: session.MetadataTokens{
|
|
RefreshedAt: time.Now(),
|
|
ExpireAt: time.Now().Add(time.Minute),
|
|
},
|
|
}
|
|
|
|
assert.False(t, metadata.ShouldRefresh())
|
|
})
|
|
|
|
t.Run("token is not within expiry range", func(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Tokens: session.MetadataTokens{
|
|
RefreshedAt: time.Now(),
|
|
ExpireAt: time.Now().Add(time.Hour),
|
|
},
|
|
}
|
|
|
|
assert.False(t, metadata.ShouldRefresh())
|
|
})
|
|
|
|
t.Run("token is about to expire", func(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Tokens: session.MetadataTokens{
|
|
RefreshedAt: time.Now().Add(-5 * time.Minute),
|
|
ExpireAt: time.Now().Add(time.Minute),
|
|
},
|
|
}
|
|
|
|
assert.True(t, metadata.ShouldRefresh())
|
|
})
|
|
|
|
t.Run("token has expired", func(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Tokens: session.MetadataTokens{
|
|
RefreshedAt: time.Now().Add(-5 * time.Minute),
|
|
ExpireAt: time.Now().Add(-5 * time.Minute),
|
|
},
|
|
}
|
|
|
|
assert.True(t, metadata.ShouldRefresh())
|
|
})
|
|
|
|
t.Run("refresh is on cooldown and token has expired", func(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Tokens: session.MetadataTokens{
|
|
RefreshedAt: time.Now(),
|
|
ExpireAt: time.Now().Add(-5 * time.Minute),
|
|
},
|
|
}
|
|
|
|
assert.True(t, metadata.ShouldRefresh())
|
|
})
|
|
|
|
t.Run("time between last refresh and timeout has passed half-life", func(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Session: session.MetadataSession{
|
|
TimeoutAt: time.Now().Add(10 * time.Minute),
|
|
},
|
|
Tokens: session.MetadataTokens{
|
|
RefreshedAt: time.Now().Add(-15 * time.Minute),
|
|
ExpireAt: time.Now().Add(time.Hour),
|
|
},
|
|
}
|
|
|
|
assert.True(t, metadata.ShouldRefresh())
|
|
})
|
|
|
|
t.Run("time between last refresh and timeout has not passed half-life", func(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Session: session.MetadataSession{
|
|
TimeoutAt: time.Now().Add(10 * time.Minute),
|
|
},
|
|
Tokens: session.MetadataTokens{
|
|
RefreshedAt: time.Now().Add(-5 * time.Minute),
|
|
ExpireAt: time.Now().Add(time.Hour),
|
|
},
|
|
}
|
|
|
|
assert.False(t, metadata.ShouldRefresh())
|
|
})
|
|
|
|
t.Run("token expiry occurs before inactivity, token is not within expiry range", func(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Session: session.MetadataSession{
|
|
TimeoutAt: time.Now().Add(2 * time.Hour),
|
|
},
|
|
Tokens: session.MetadataTokens{
|
|
RefreshedAt: time.Now().Add(-5 * time.Minute),
|
|
ExpireAt: time.Now().Add(time.Hour),
|
|
},
|
|
}
|
|
|
|
assert.False(t, metadata.ShouldRefresh())
|
|
})
|
|
|
|
t.Run("token expiry occurs before inactivity, token is about to expire", func(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Session: session.MetadataSession{
|
|
TimeoutAt: time.Now().Add(time.Hour),
|
|
},
|
|
Tokens: session.MetadataTokens{
|
|
RefreshedAt: time.Now().Add(-5 * time.Minute),
|
|
ExpireAt: time.Now().Add(time.Minute),
|
|
},
|
|
}
|
|
|
|
assert.True(t, metadata.ShouldRefresh())
|
|
})
|
|
}
|
|
|
|
func TestMetadata_TokenLifetime(t *testing.T) {
|
|
metadata := session.Metadata{
|
|
Tokens: session.MetadataTokens{
|
|
RefreshedAt: time.Now(),
|
|
ExpireAt: time.Now().Add(time.Minute),
|
|
},
|
|
}
|
|
|
|
assert.Equal(t, time.Minute, metadata.TokenLifetime().Truncate(time.Second))
|
|
}
|
|
|
|
func TestMetadata_Verbose(t *testing.T) {
|
|
tokenLifetime := 30 * time.Minute
|
|
sessionLifetime := time.Hour
|
|
|
|
metadata := session.NewMetadata(tokenLifetime, sessionLifetime)
|
|
|
|
verbose := metadata.Verbose()
|
|
maxDelta := time.Second
|
|
|
|
expected := time.Now().Add(sessionLifetime)
|
|
actual := time.Now().Add(durationSeconds(verbose.Session.EndsInSeconds))
|
|
assert.WithinDuration(t, expected, actual, maxDelta)
|
|
|
|
expected = time.Now().Add(tokenLifetime)
|
|
actual = time.Now().Add(durationSeconds(verbose.Tokens.ExpireInSeconds))
|
|
assert.WithinDuration(t, expected, actual, maxDelta)
|
|
|
|
expected = time.Now().Add(tokenLifetime).Add(-session.RefreshLeeway)
|
|
actual = time.Now().Add(durationSeconds(verbose.Tokens.NextAutoRefreshInSeconds))
|
|
assert.WithinDuration(t, expected, actual, maxDelta)
|
|
|
|
assert.True(t, verbose.Session.Active)
|
|
assert.True(t, verbose.Session.TimeoutAt.IsZero())
|
|
assert.Equal(t, int64(-1), verbose.Session.TimeoutInSeconds)
|
|
|
|
t.Run("refresh on cooldown", func(t *testing.T) {
|
|
assert.True(t, verbose.Tokens.RefreshCooldown)
|
|
|
|
expected = time.Now().Add(session.RefreshMinInterval)
|
|
actual = time.Now().Add(durationSeconds(verbose.Tokens.RefreshCooldownSeconds))
|
|
assert.WithinDuration(t, expected, actual, maxDelta)
|
|
})
|
|
|
|
t.Run("refresh not on cooldown", func(t *testing.T) {
|
|
metadata := session.NewMetadata(tokenLifetime, sessionLifetime)
|
|
metadata.Tokens.RefreshedAt = time.Now().Add(-5 * time.Minute)
|
|
verbose := metadata.Verbose()
|
|
|
|
assert.False(t, verbose.Tokens.RefreshCooldown)
|
|
assert.Equal(t, int64(0), verbose.Tokens.RefreshCooldownSeconds)
|
|
})
|
|
}
|
|
|
|
func TestMetadata_Verbose_WithTimeout(t *testing.T) {
|
|
tokenLifetime := 30 * time.Minute
|
|
sessionLifetime := time.Hour
|
|
timeout := 15 * time.Minute
|
|
|
|
metadata := session.NewMetadata(tokenLifetime, sessionLifetime)
|
|
metadata.WithTimeout(timeout)
|
|
|
|
maxDelta := time.Second
|
|
|
|
verbose := metadata.Verbose()
|
|
|
|
assert.True(t, verbose.Session.Active)
|
|
assert.False(t, verbose.Session.TimeoutAt.IsZero())
|
|
|
|
expected := time.Now().Add(timeout)
|
|
actual := verbose.Session.TimeoutAt
|
|
assert.WithinDuration(t, expected, actual, maxDelta)
|
|
|
|
expected = time.Now().Add(durationSeconds(verbose.Session.TimeoutInSeconds))
|
|
actual = verbose.Session.TimeoutAt
|
|
assert.WithinDuration(t, expected, actual, maxDelta)
|
|
}
|
|
|
|
func TestMetadata_IsTimedOut(t *testing.T) {
|
|
tokenLifetime := 30 * time.Minute
|
|
sessionLifetime := time.Hour
|
|
|
|
t.Run("timeout is zero", func(t *testing.T) {
|
|
metadata := session.NewMetadata(tokenLifetime, sessionLifetime)
|
|
assert.False(t, metadata.IsTimedOut())
|
|
})
|
|
|
|
t.Run("timeout is non-zero", func(t *testing.T) {
|
|
timeout := 15 * time.Minute
|
|
|
|
metadata := session.NewMetadata(tokenLifetime, sessionLifetime)
|
|
metadata.WithTimeout(timeout)
|
|
assert.False(t, metadata.IsTimedOut())
|
|
|
|
metadata.WithTimeout(-timeout)
|
|
assert.True(t, metadata.IsTimedOut())
|
|
})
|
|
}
|
|
|
|
func durationSeconds(seconds int64) time.Duration {
|
|
return time.Duration(seconds) * time.Second
|
|
}
|