mirror of
https://github.com/nais/wonderwall.git
synced 2026-02-14 17:49:54 +00:00
feat: remove feature flags for session refresh
These feature flags were enabled by default. We specifically disallowed the use of automatic refresh with the SSO mode, though this poses some complexity if using the forward-auth feature. To simplify configuration and code, we remove the flags in their entirety as session refresh behaviour is mostly already handled by the implementation of GetSession() in the handlers. Specifically: - the Standalone handler needs to refresh sessions when reverse-proxying to the upstream. - the SSO server handler needs to refresh sessions only when using the forward-auth feature. It does not have an upstream to reverse proxy to. - the SSO proxy handler is a read-only upstream proxy and does not possess the ability to refresh sessions itself, though it will delegate traffic for the session endpoints to the configured SSO server. Automatic refreshing is thus only disabled when running in SSO mode without the forward-auth feature.
This commit is contained in:
@@ -51,8 +51,6 @@ The following flags are available:
|
||||
| `session.inactivity` | boolean | `false` | Automatically expire user sessions if they have not refreshed their tokens within a given duration. |
|
||||
| `session.inactivity-timeout` | duration | `30m` | Inactivity timeout for user sessions. |
|
||||
| `session.max-lifetime` | duration | `10h` | Max lifetime for user sessions. |
|
||||
| `session.refresh` | boolean | `true` | Enable refresh tokens. |
|
||||
| `session.refresh-auto` | boolean | `true` | Enable automatic refresh of tokens. Only available in standalone mode. Will automatically refresh tokens if they are expired as long as the session is valid (i.e. not exceeding `session.max-lifetime` or `session.inactivity-timeout`). |
|
||||
| `shutdown-graceful-period` | duration | `30s` | Graceful shutdown period when receiving a shutdown signal after which the server is forcibly exited. |
|
||||
| `shutdown-wait-before-period` | duration | `0s` | Wait period when receiving a shutdown signal before actually starting a graceful shutdown. Useful for allowing propagation of Endpoint updates in Kubernetes. |
|
||||
| `sso.domain` | string | | The domain that the session cookies should be set for, usually the second-level domain name (e.g. `example.com`). |
|
||||
|
||||
@@ -6,14 +6,14 @@ Wonderwall exposes and owns these endpoints (which means they will never be prox
|
||||
|
||||
Endpoints that are available for use by applications:
|
||||
|
||||
| Path | Description | Notes |
|
||||
|-----------------------------------|----------------------------------------------------------------------|---------------------------------------------------|
|
||||
| `GET /oauth2/login` | Initiates the OpenID Connect Authorization Code flow | |
|
||||
| `GET /oauth2/logout` | Performs local logout and redirects the user to global/single-logout | |
|
||||
| `GET /oauth2/logout/local` | Performs local logout only | Disabled when `openid.provider` is `idporten`. |
|
||||
| `GET /oauth2/session` | Returns the current user's session metadata | |
|
||||
| `POST /oauth2/session/refresh` | Refreshes the tokens for the user's session. | Requires the `session.refresh` flag to be enabled |
|
||||
| `GET /oauth2/session/forwardauth` | Checks the user's session and refreshes it, if necessary. | |
|
||||
| Path | Description | Notes |
|
||||
|-----------------------------------|----------------------------------------------------------------------|------------------------------------------------|
|
||||
| `GET /oauth2/login` | Initiates the OpenID Connect Authorization Code flow | |
|
||||
| `GET /oauth2/logout` | Performs local logout and redirects the user to global/single-logout | |
|
||||
| `GET /oauth2/logout/local` | Performs local logout only | Disabled when `openid.provider` is `idporten`. |
|
||||
| `GET /oauth2/session` | Returns the current user's session metadata | |
|
||||
| `POST /oauth2/session/refresh` | Refreshes the tokens for the user's session. | |
|
||||
| `GET /oauth2/session/forwardauth` | Checks the user's session and refreshes it, if necessary. | |
|
||||
|
||||
## Endpoints for Identity Providers
|
||||
|
||||
@@ -129,7 +129,10 @@ Content-Type: application/json
|
||||
"tokens": {
|
||||
"expire_at": "2022-08-31T14:03:47.318251953Z",
|
||||
"refreshed_at": "2022-08-31T12:53:58.318251953Z",
|
||||
"expire_in_seconds": 4166
|
||||
"expire_in_seconds": 4166,
|
||||
"next_auto_refresh_in_seconds": -1,
|
||||
"refresh_cooldown": false,
|
||||
"refresh_cooldown_seconds": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -145,40 +148,6 @@ Content-Type: application/json
|
||||
| `tokens.expire_at` | The timestamp that denotes when the tokens within the session will expire. |
|
||||
| `tokens.expire_in_seconds` | The number of seconds until `tokens.expire_at`. |
|
||||
| `tokens.refreshed_at` | The timestamp that denotes when the tokens within the session was last refreshed. |
|
||||
|
||||
If the `session.refresh` flag is enabled, the metadata response will contain a few additional fields:
|
||||
|
||||
#### Request:
|
||||
|
||||
```
|
||||
GET /oauth2/session
|
||||
```
|
||||
|
||||
#### Response:
|
||||
|
||||
```
|
||||
HTTP/2 200 OK
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"session": {
|
||||
...
|
||||
},
|
||||
"tokens": {
|
||||
...
|
||||
"next_auto_refresh_in_seconds": -1,
|
||||
"refresh_cooldown": false,
|
||||
"refresh_cooldown_seconds": 0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
(fields shown earlier are omitted from this example for brevity)
|
||||
|
||||
| Field | Description |
|
||||
|---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `tokens.next_auto_refresh_in_seconds` | The number of seconds until the earliest time where the tokens will automatically be refreshed. A value of -1 means that automatic refreshing is not enabled. |
|
||||
| `tokens.refresh_cooldown` | A boolean indicating whether or not the refresh operation is on cooldown or not. |
|
||||
| `tokens.refresh_cooldown_seconds` | The number of seconds until the refresh operation is no longer on cooldown. |
|
||||
@@ -187,8 +156,6 @@ Content-Type: application/json
|
||||
|
||||
### `/oauth2/session/refresh`
|
||||
|
||||
This endpoint only exists if the `session.refresh` flag is enabled.
|
||||
|
||||
Perform a `POST` request from the user agent to this endpoint to manually refresh the tokens for the user's session.
|
||||
|
||||
The endpoint will respond with a `HTTP 401 Unauthorized` if the session is [_inactive_](sessions.md#session-inactivity).
|
||||
|
||||
@@ -31,14 +31,12 @@ This is indicated by the `tokens.expire_at` and `tokens.expire_in_seconds` field
|
||||
|
||||
If you've configured a session lifetime that is longer than the token expiry, you'll probably want to _refresh_ the tokens to avoid redirecting end-users to the `/oauth2/login` endpoint whenever the access tokens have expired.
|
||||
|
||||
The ability to refresh tokens requires the `session.refresh` flag to be enabled.
|
||||
|
||||
### Automatic vs Manual Refresh
|
||||
|
||||
The behaviour for refreshing depends on the [runtime mode](configuration.md#modes) for Wonderwall.
|
||||
|
||||
In standalone mode, tokens can automatically be refreshed by enabling the `session.refresh-auto` flag.
|
||||
If enabled, token will at the _earliest_ automatically be renewed 5 minutes before they expire.
|
||||
In standalone mode, tokens are automatically refreshed.
|
||||
Tokens will at the _earliest_ automatically be renewed 5 minutes before they expire.
|
||||
If the token already _has_ expired, a refresh attempt is still automatically triggered as long as the session itself not has ended or is marked as inactive.
|
||||
|
||||
Automatic refreshes happens whenever the end-user visits or requests any path that is proxied to the upstream application.
|
||||
@@ -53,7 +51,7 @@ This happens if the time since the last _refresh_ exceeds the given _inactivity
|
||||
An _inactive_ session _cannot_ be refreshed; a new session must be acquired by redirecting the user to the `/oauth2/login` endpoint.
|
||||
This is useful if you want to ensure that an end-user can re-authenticate with the identity provider if they've been gone from an authenticated session for some time.
|
||||
|
||||
Inactivity support is enabled with the `session.inactivity` option, which also requires `session.refresh`.
|
||||
Inactivity support is enabled with the `session.inactivity` option.
|
||||
|
||||
The activity state of the session is indicated by the `session.active` field in the session metadata.
|
||||
|
||||
|
||||
@@ -138,10 +138,6 @@ func (c *Config) Validate() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.Session.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.SSO.Validate(c); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -157,6 +153,10 @@ func (c *Config) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) AutoRefreshDisabled() bool {
|
||||
return c.SSO.Enabled && !c.Session.ForwardAuth
|
||||
}
|
||||
|
||||
func (c *Config) validateUpstream() error {
|
||||
if c.UpstreamIP == "" && c.UpstreamPort == 0 {
|
||||
return nil
|
||||
|
||||
@@ -40,20 +40,6 @@ func TestConfig_Validate(t *testing.T) {
|
||||
cfg.Cookie.SameSite = "invalid"
|
||||
},
|
||||
},
|
||||
{
|
||||
"session inactivity without session refresh",
|
||||
func(cfg *config.Config) {
|
||||
cfg.Session.Inactivity = true
|
||||
cfg.Session.Refresh = false
|
||||
},
|
||||
},
|
||||
{
|
||||
"session auto refresh without session refresh",
|
||||
func(cfg *config.Config) {
|
||||
cfg.Session.RefreshAuto = true
|
||||
cfg.Session.Refresh = false
|
||||
},
|
||||
},
|
||||
{
|
||||
"upstream ip must be set if port is set",
|
||||
func(cfg *config.Config) {
|
||||
@@ -111,7 +97,6 @@ func TestConfig_Validate(t *testing.T) {
|
||||
server.SSO.Domain = "example.com"
|
||||
server.SSO.SessionCookieName = "some-cookie"
|
||||
server.SSO.ServerDefaultRedirectURL = "https://default.local"
|
||||
server.Session.RefreshAuto = false
|
||||
server.Redis.Address = "localhost:6379"
|
||||
|
||||
run("sso server", &server, []test{
|
||||
@@ -127,12 +112,6 @@ func TestConfig_Validate(t *testing.T) {
|
||||
cfg.SSO.SessionCookieName = ""
|
||||
},
|
||||
},
|
||||
{
|
||||
"with session auto refresh",
|
||||
func(cfg *config.Config) {
|
||||
cfg.Session.RefreshAuto = true
|
||||
},
|
||||
},
|
||||
{
|
||||
"missing session cookie name",
|
||||
func(cfg *config.Config) {
|
||||
@@ -164,7 +143,6 @@ func TestConfig_Validate(t *testing.T) {
|
||||
proxy.SSO.Mode = config.SSOModeProxy
|
||||
proxy.SSO.ServerURL = "https://sso-server.local"
|
||||
proxy.SSO.SessionCookieName = "some-cookie"
|
||||
proxy.Session.RefreshAuto = false
|
||||
proxy.Redis.Address = "localhost:6379"
|
||||
|
||||
run("sso proxy", &proxy, []test{
|
||||
@@ -180,12 +158,6 @@ func TestConfig_Validate(t *testing.T) {
|
||||
cfg.SSO.SessionCookieName = ""
|
||||
},
|
||||
},
|
||||
{
|
||||
"with session auto refresh",
|
||||
func(cfg *config.Config) {
|
||||
cfg.Session.RefreshAuto = true
|
||||
},
|
||||
},
|
||||
{
|
||||
"missing session cookie name",
|
||||
func(cfg *config.Config) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
flag "github.com/spf13/pflag"
|
||||
@@ -12,20 +11,6 @@ type Session struct {
|
||||
Inactivity bool `json:"inactivity"`
|
||||
InactivityTimeout time.Duration `json:"inactivity-timeout"`
|
||||
MaxLifetime time.Duration `json:"max-lifetime"`
|
||||
Refresh bool `json:"refresh"`
|
||||
RefreshAuto bool `json:"refresh-auto"`
|
||||
}
|
||||
|
||||
func (s Session) Validate() error {
|
||||
if s.Inactivity && !s.Refresh {
|
||||
return fmt.Errorf("%q cannot be enabled without %q", SessionInactivity, SessionRefresh)
|
||||
}
|
||||
|
||||
if s.RefreshAuto && !s.Refresh {
|
||||
return fmt.Errorf("%q cannot be enabled without %q", SessionRefreshAuto, SessionRefresh)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -33,8 +18,6 @@ const (
|
||||
SessionInactivity = "session.inactivity"
|
||||
SessionInactivityTimeout = "session.inactivity-timeout"
|
||||
SessionMaxLifetime = "session.max-lifetime"
|
||||
SessionRefresh = "session.refresh"
|
||||
SessionRefreshAuto = "session.refresh-auto"
|
||||
)
|
||||
|
||||
func sessionFlags() {
|
||||
@@ -42,6 +25,4 @@ func sessionFlags() {
|
||||
flag.Bool(SessionInactivity, false, "Automatically expire user sessions if they have not refreshed their tokens within a given duration.")
|
||||
flag.Duration(SessionInactivityTimeout, 30*time.Minute, "Inactivity timeout for user sessions.")
|
||||
flag.Duration(SessionMaxLifetime, 10*time.Hour, "Max lifetime for user sessions.")
|
||||
flag.Bool(SessionRefresh, true, "Enable refresh tokens.")
|
||||
flag.Bool(SessionRefreshAuto, true, "Enable automatic refresh of tokens. Only available in standalone mode. Will automatically refresh tokens if they are expired as long as the session is valid (i.e. not exceeding 'session.max-lifetime' or 'session.inactivity-timeout').")
|
||||
}
|
||||
|
||||
@@ -36,10 +36,6 @@ func (s SSO) Validate(c *Config) error {
|
||||
return fmt.Errorf("at least one of %q or %q must be set when %s is set", RedisAddress, RedisURI, SSOEnabled)
|
||||
}
|
||||
|
||||
if c.Session.RefreshAuto {
|
||||
return fmt.Errorf("%q cannot be enabled when %q is enabled", SessionRefreshAuto, SSOEnabled)
|
||||
}
|
||||
|
||||
if len(s.SessionCookieName) == 0 {
|
||||
return fmt.Errorf("%q must not be empty when %s is set", SSOSessionCookieName, SSOEnabled)
|
||||
}
|
||||
|
||||
@@ -112,6 +112,9 @@ func (s *Standalone) GetPath(r *http.Request) string {
|
||||
}
|
||||
|
||||
func (s *Standalone) GetSession(r *http.Request) (*session.Session, error) {
|
||||
if s.Config.AutoRefreshDisabled() {
|
||||
return s.SessionManager.Get(r)
|
||||
}
|
||||
return s.SessionManager.GetOrRefresh(r)
|
||||
}
|
||||
|
||||
@@ -389,11 +392,6 @@ func (s *Standalone) Session(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (s *Standalone) SessionRefresh(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.Config.Session.Refresh {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
logger := mw.LogEntryFrom(r)
|
||||
|
||||
sess, err := s.SessionManager.Get(r)
|
||||
@@ -428,12 +426,8 @@ func (s *Standalone) SessionRefresh(w http.ResponseWriter, r *http.Request) {
|
||||
func (s *Standalone) sessionWriteMetadataResponse(w http.ResponseWriter, r *http.Request, sess *session.Session) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
if !s.Config.Session.Refresh {
|
||||
return json.NewEncoder(w).Encode(sess.MetadataVerbose())
|
||||
}
|
||||
|
||||
metadata := sess.MetadataVerboseRefresh()
|
||||
if !s.Config.Session.RefreshAuto {
|
||||
metadata := sess.MetadataVerbose()
|
||||
if s.Config.AutoRefreshDisabled() {
|
||||
metadata.Tokens.NextAutoRefreshInSeconds = int64(-1)
|
||||
}
|
||||
|
||||
@@ -446,7 +440,7 @@ func (s *Standalone) SessionForwardAuth(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
_, err := s.SessionManager.GetOrRefresh(r)
|
||||
_, err := s.GetSession(r)
|
||||
if err != nil {
|
||||
logger := mw.LogEntryFrom(r)
|
||||
if errors.Is(err, session.ErrInvalidExternal) || errors.Is(err, session.ErrInvalid) || errors.Is(err, session.ErrNotFound) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/lestrrat-go/jwx/v2/jwt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/nais/wonderwall/pkg/config"
|
||||
"github.com/nais/wonderwall/pkg/cookie"
|
||||
"github.com/nais/wonderwall/pkg/mock"
|
||||
"github.com/nais/wonderwall/pkg/session"
|
||||
@@ -203,240 +204,24 @@ func TestFrontChannelLogout(t *testing.T) {
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestSessionRefresh(t *testing.T) {
|
||||
cfg := mock.Config()
|
||||
cfg.Session.Refresh = true
|
||||
|
||||
idp := mock.NewIdentityProvider(cfg)
|
||||
idp.ProviderHandler.TokenDuration = 5 * time.Second
|
||||
defer idp.Close()
|
||||
|
||||
rpClient := idp.RelyingPartyClient()
|
||||
noSessionResp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusUnauthorized, noSessionResp.StatusCode)
|
||||
|
||||
login(t, rpClient, idp)
|
||||
|
||||
// get initial session info
|
||||
resp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var data session.MetadataVerboseWithRefresh
|
||||
err := json.Unmarshal([]byte(resp.Body), &data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// wait until refresh cooldown has reached zero before refresh
|
||||
waitForRefreshCooldownTimer(t, idp, rpClient)
|
||||
|
||||
resp = sessionRefresh(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var refreshedData session.MetadataVerboseWithRefresh
|
||||
err = json.Unmarshal([]byte(resp.Body), &refreshedData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// session create and end times should be unchanged
|
||||
assert.WithinDuration(t, data.Session.CreatedAt, refreshedData.Session.CreatedAt, 0)
|
||||
assert.WithinDuration(t, data.Session.EndsAt, refreshedData.Session.EndsAt, 0)
|
||||
|
||||
// token expiration and refresh times should be later than before
|
||||
assert.True(t, refreshedData.Tokens.ExpireAt.After(data.Tokens.ExpireAt))
|
||||
assert.True(t, refreshedData.Tokens.RefreshedAt.After(data.Tokens.RefreshedAt))
|
||||
|
||||
allowedSkew := 5 * time.Second
|
||||
assert.WithinDuration(t, time.Now().Add(idp.ProviderHandler.TokenDuration), refreshedData.Tokens.ExpireAt, allowedSkew)
|
||||
assert.WithinDuration(t, time.Now(), refreshedData.Tokens.RefreshedAt, allowedSkew)
|
||||
|
||||
sessionEndDuration := time.Duration(refreshedData.Session.EndsInSeconds) * time.Second
|
||||
// 1 second < time until session ends <= configured max session lifetime
|
||||
assert.LessOrEqual(t, sessionEndDuration, cfg.Session.MaxLifetime)
|
||||
assert.Greater(t, sessionEndDuration, time.Second)
|
||||
|
||||
tokenExpiryDuration := time.Duration(refreshedData.Tokens.ExpireInSeconds) * time.Second
|
||||
// 1 second < time until token expires <= max duration for tokens from IDP
|
||||
assert.LessOrEqual(t, tokenExpiryDuration, idp.ProviderHandler.TokenDuration)
|
||||
assert.Greater(t, tokenExpiryDuration, time.Second)
|
||||
|
||||
// auto refresh is not enabled
|
||||
assert.Less(t, refreshedData.Tokens.NextAutoRefreshInSeconds, refreshedData.Tokens.ExpireInSeconds)
|
||||
assert.Equal(t, refreshedData.Tokens.NextAutoRefreshInSeconds, int64(-1))
|
||||
|
||||
assert.True(t, refreshedData.Tokens.RefreshCooldown)
|
||||
// 1 second < refresh cooldown <= minimum refresh interval
|
||||
assert.LessOrEqual(t, refreshedData.Tokens.RefreshCooldownSeconds, session.RefreshMinInterval)
|
||||
assert.Greater(t, refreshedData.Tokens.RefreshCooldownSeconds, int64(1))
|
||||
|
||||
assert.True(t, data.Session.Active)
|
||||
assert.True(t, refreshedData.Session.Active)
|
||||
|
||||
assert.True(t, data.Session.TimeoutAt.IsZero())
|
||||
assert.True(t, refreshedData.Session.TimeoutAt.IsZero())
|
||||
|
||||
assert.Equal(t, int64(-1), data.Session.TimeoutInSeconds)
|
||||
assert.Equal(t, int64(-1), refreshedData.Session.TimeoutInSeconds)
|
||||
}
|
||||
|
||||
func TestSessionRefresh_Disabled(t *testing.T) {
|
||||
cfg := mock.Config()
|
||||
cfg.Session.Refresh = false
|
||||
|
||||
idp := mock.NewIdentityProvider(cfg)
|
||||
idp.ProviderHandler.TokenDuration = 5 * time.Second
|
||||
defer idp.Close()
|
||||
|
||||
rpClient := idp.RelyingPartyClient()
|
||||
login(t, rpClient, idp)
|
||||
|
||||
resp := sessionRefresh(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestSessionRefresh_WithInactivity(t *testing.T) {
|
||||
cfg := mock.Config()
|
||||
cfg.Session.Refresh = true
|
||||
cfg.Session.Inactivity = true
|
||||
cfg.Session.InactivityTimeout = 10 * time.Minute
|
||||
|
||||
idp := mock.NewIdentityProvider(cfg)
|
||||
idp.ProviderHandler.TokenDuration = 5 * time.Second
|
||||
defer idp.Close()
|
||||
|
||||
rpClient := idp.RelyingPartyClient()
|
||||
login(t, rpClient, idp)
|
||||
|
||||
// get initial session info
|
||||
resp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var data session.MetadataVerboseWithRefresh
|
||||
err := json.Unmarshal([]byte(resp.Body), &data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// wait until refresh cooldown has reached zero before refresh
|
||||
waitForRefreshCooldownTimer(t, idp, rpClient)
|
||||
|
||||
resp = sessionRefresh(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var refreshedData session.MetadataVerboseWithRefresh
|
||||
err = json.Unmarshal([]byte(resp.Body), &refreshedData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
maxDelta := 5 * time.Second
|
||||
|
||||
assert.True(t, data.Session.Active)
|
||||
assert.True(t, refreshedData.Session.Active)
|
||||
|
||||
assert.False(t, data.Session.TimeoutAt.IsZero())
|
||||
assert.False(t, refreshedData.Session.TimeoutAt.IsZero())
|
||||
|
||||
expectedTimeoutAt := time.Now().Add(cfg.Session.InactivityTimeout)
|
||||
assert.WithinDuration(t, expectedTimeoutAt, data.Session.TimeoutAt, maxDelta)
|
||||
assert.WithinDuration(t, expectedTimeoutAt, refreshedData.Session.TimeoutAt, maxDelta)
|
||||
|
||||
assert.True(t, refreshedData.Session.TimeoutAt.After(data.Session.TimeoutAt))
|
||||
|
||||
previousTimeoutDuration := time.Duration(data.Session.TimeoutInSeconds) * time.Second
|
||||
assert.WithinDuration(t, expectedTimeoutAt, time.Now().Add(previousTimeoutDuration), maxDelta)
|
||||
|
||||
refreshedTimeoutDuration := time.Duration(refreshedData.Session.TimeoutInSeconds) * time.Second
|
||||
assert.WithinDuration(t, expectedTimeoutAt, time.Now().Add(refreshedTimeoutDuration), maxDelta)
|
||||
}
|
||||
|
||||
func TestSessionRefresh_WithRefreshAuto(t *testing.T) {
|
||||
cfg := mock.Config()
|
||||
cfg.Session.Refresh = true
|
||||
cfg.Session.RefreshAuto = true
|
||||
|
||||
idp := mock.NewIdentityProvider(cfg)
|
||||
idp.ProviderHandler.TokenDuration = 5 * time.Second
|
||||
defer idp.Close()
|
||||
|
||||
rpClient := idp.RelyingPartyClient()
|
||||
login(t, rpClient, idp)
|
||||
|
||||
// get initial session info
|
||||
resp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var data session.MetadataVerboseWithRefresh
|
||||
err := json.Unmarshal([]byte(resp.Body), &data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// wait until refresh cooldown has reached zero before refresh
|
||||
waitForRefreshCooldownTimer(t, idp, rpClient)
|
||||
|
||||
resp = sessionRefresh(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var refreshedData session.MetadataVerboseWithRefresh
|
||||
err = json.Unmarshal([]byte(resp.Body), &refreshedData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 1 second < next token refresh <= seconds until token expires
|
||||
assert.LessOrEqual(t, refreshedData.Tokens.NextAutoRefreshInSeconds, refreshedData.Tokens.ExpireInSeconds)
|
||||
assert.Greater(t, refreshedData.Tokens.NextAutoRefreshInSeconds, int64(1))
|
||||
}
|
||||
|
||||
func TestSession(t *testing.T) {
|
||||
cfg := mock.Config()
|
||||
idp := mock.NewIdentityProvider(cfg)
|
||||
idp.ProviderHandler.TokenDuration = 5 * time.Minute
|
||||
defer idp.Close()
|
||||
|
||||
rpClient := idp.RelyingPartyClient()
|
||||
noSessionResp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusUnauthorized, noSessionResp.StatusCode)
|
||||
|
||||
login(t, rpClient, idp)
|
||||
|
||||
resp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var data session.MetadataVerbose
|
||||
err := json.Unmarshal([]byte(resp.Body), &data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
allowedSkew := 5 * time.Second
|
||||
assert.WithinDuration(t, time.Now(), data.Session.CreatedAt, allowedSkew)
|
||||
assert.WithinDuration(t, time.Now().Add(cfg.Session.MaxLifetime), data.Session.EndsAt, allowedSkew)
|
||||
assert.WithinDuration(t, time.Now().Add(idp.ProviderHandler.TokenDuration), data.Tokens.ExpireAt, allowedSkew)
|
||||
assert.WithinDuration(t, time.Now(), data.Tokens.RefreshedAt, allowedSkew)
|
||||
|
||||
sessionEndDuration := time.Duration(data.Session.EndsInSeconds) * time.Second
|
||||
// 1 second < time until session ends <= configured max session lifetime
|
||||
assert.LessOrEqual(t, sessionEndDuration, cfg.Session.MaxLifetime)
|
||||
assert.Greater(t, sessionEndDuration, time.Second)
|
||||
|
||||
tokenExpiryDuration := time.Duration(data.Tokens.ExpireInSeconds) * time.Second
|
||||
// 1 second < time until token expires <= max duration for tokens from IDP
|
||||
assert.LessOrEqual(t, tokenExpiryDuration, idp.ProviderHandler.TokenDuration)
|
||||
assert.Greater(t, tokenExpiryDuration, time.Second)
|
||||
|
||||
assert.True(t, data.Session.Active)
|
||||
assert.True(t, data.Session.TimeoutAt.IsZero())
|
||||
assert.Equal(t, int64(-1), data.Session.TimeoutInSeconds)
|
||||
data := assertSessionInfo(t, idp, cfg)
|
||||
assertAutoRefreshEnabled(t, data)
|
||||
}
|
||||
|
||||
func TestSession_WithInactivity(t *testing.T) {
|
||||
cfg := mock.Config()
|
||||
cfg.Session.Refresh = true
|
||||
cfg.Session.Inactivity = true
|
||||
cfg.Session.InactivityTimeout = 10 * time.Minute
|
||||
|
||||
idp := mock.NewIdentityProvider(cfg)
|
||||
defer idp.Close()
|
||||
|
||||
rpClient := idp.RelyingPartyClient()
|
||||
login(t, rpClient, idp)
|
||||
|
||||
resp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var data session.MetadataVerbose
|
||||
err := json.Unmarshal([]byte(resp.Body), &data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
data := assertSessionInfo(t, idp, cfg)
|
||||
maxDelta := 5 * time.Second
|
||||
|
||||
assert.True(t, data.Session.Active)
|
||||
@@ -449,76 +234,92 @@ func TestSession_WithInactivity(t *testing.T) {
|
||||
assert.WithinDuration(t, expectedTimeoutAt, time.Now().Add(actualTimeoutDuration), maxDelta)
|
||||
}
|
||||
|
||||
func TestSession_WithRefresh(t *testing.T) {
|
||||
func TestSession_WithSSO(t *testing.T) {
|
||||
cfg := mock.Config()
|
||||
cfg.Session.Refresh = true
|
||||
cfg.SSO.Enabled = true
|
||||
|
||||
idp := mock.NewIdentityProvider(cfg)
|
||||
idp.ProviderHandler.TokenDuration = 5 * time.Minute
|
||||
defer idp.Close()
|
||||
|
||||
rpClient := idp.RelyingPartyClient()
|
||||
login(t, rpClient, idp)
|
||||
|
||||
resp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var data session.MetadataVerboseWithRefresh
|
||||
err := json.Unmarshal([]byte(resp.Body), &data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
allowedSkew := 5 * time.Second
|
||||
assert.WithinDuration(t, time.Now(), data.Session.CreatedAt, allowedSkew)
|
||||
assert.WithinDuration(t, time.Now().Add(cfg.Session.MaxLifetime), data.Session.EndsAt, allowedSkew)
|
||||
assert.WithinDuration(t, time.Now().Add(idp.ProviderHandler.TokenDuration), data.Tokens.ExpireAt, allowedSkew)
|
||||
assert.WithinDuration(t, time.Now(), data.Tokens.RefreshedAt, allowedSkew)
|
||||
|
||||
sessionEndDuration := time.Duration(data.Session.EndsInSeconds) * time.Second
|
||||
// 1 second < time until session ends <= configured max session lifetime
|
||||
assert.LessOrEqual(t, sessionEndDuration, cfg.Session.MaxLifetime)
|
||||
assert.Greater(t, sessionEndDuration, time.Second)
|
||||
|
||||
tokenExpiryDuration := time.Duration(data.Tokens.ExpireInSeconds) * time.Second
|
||||
// 1 second < time until token expires <= max duration for tokens from IDP
|
||||
assert.LessOrEqual(t, tokenExpiryDuration, idp.ProviderHandler.TokenDuration)
|
||||
assert.Greater(t, tokenExpiryDuration, time.Second)
|
||||
|
||||
// auto refresh is not enabled
|
||||
assert.Less(t, data.Tokens.NextAutoRefreshInSeconds, data.Tokens.ExpireInSeconds)
|
||||
assert.Equal(t, data.Tokens.NextAutoRefreshInSeconds, int64(-1))
|
||||
|
||||
assert.True(t, data.Tokens.RefreshCooldown)
|
||||
// 1 second < refresh cooldown <= minimum refresh interval
|
||||
assert.LessOrEqual(t, data.Tokens.RefreshCooldownSeconds, session.RefreshMinInterval)
|
||||
assert.Greater(t, data.Tokens.RefreshCooldownSeconds, int64(1))
|
||||
|
||||
assert.True(t, data.Session.Active)
|
||||
assert.True(t, data.Session.TimeoutAt.IsZero())
|
||||
assert.Equal(t, int64(-1), data.Session.TimeoutInSeconds)
|
||||
data := assertSessionInfo(t, idp, cfg)
|
||||
assertAutoRefreshDisabled(t, data)
|
||||
}
|
||||
|
||||
func TestSession_WithRefreshAuto(t *testing.T) {
|
||||
func TestSession_WithForwardAuth(t *testing.T) {
|
||||
cfg := mock.Config()
|
||||
cfg.Session.Refresh = true
|
||||
cfg.Session.RefreshAuto = true
|
||||
cfg.SSO.Enabled = true
|
||||
cfg.Session.ForwardAuth = true
|
||||
|
||||
idp := mock.NewIdentityProvider(cfg)
|
||||
idp.ProviderHandler.TokenDuration = 5 * time.Minute
|
||||
defer idp.Close()
|
||||
|
||||
rpClient := idp.RelyingPartyClient()
|
||||
login(t, rpClient, idp)
|
||||
data := assertSessionInfo(t, idp, cfg)
|
||||
assertAutoRefreshEnabled(t, data)
|
||||
}
|
||||
|
||||
resp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
func TestSessionRefresh(t *testing.T) {
|
||||
cfg := mock.Config()
|
||||
idp := mock.NewIdentityProvider(cfg)
|
||||
defer idp.Close()
|
||||
|
||||
var data session.MetadataVerboseWithRefresh
|
||||
err := json.Unmarshal([]byte(resp.Body), &data)
|
||||
assert.NoError(t, err)
|
||||
sess := assertSessionRefresh(t, idp, cfg)
|
||||
assertAutoRefreshEnabled(t, sess.initial)
|
||||
assertAutoRefreshEnabled(t, sess.refreshed)
|
||||
}
|
||||
|
||||
// 1 second < next token refresh <= seconds until token expires
|
||||
assert.LessOrEqual(t, data.Tokens.NextAutoRefreshInSeconds, data.Tokens.ExpireInSeconds)
|
||||
assert.Greater(t, data.Tokens.NextAutoRefreshInSeconds, int64(1))
|
||||
func TestSessionRefresh_WithInactivity(t *testing.T) {
|
||||
cfg := mock.Config()
|
||||
cfg.Session.Inactivity = true
|
||||
cfg.Session.InactivityTimeout = 10 * time.Minute
|
||||
|
||||
idp := mock.NewIdentityProvider(cfg)
|
||||
defer idp.Close()
|
||||
|
||||
sess := assertSessionRefresh(t, idp, cfg)
|
||||
assertAutoRefreshEnabled(t, sess.initial)
|
||||
assertAutoRefreshEnabled(t, sess.refreshed)
|
||||
|
||||
maxDelta := 5 * time.Second
|
||||
|
||||
assert.False(t, sess.initial.Session.TimeoutAt.IsZero())
|
||||
assert.False(t, sess.refreshed.Session.TimeoutAt.IsZero())
|
||||
|
||||
expectedTimeoutAt := time.Now().Add(cfg.Session.InactivityTimeout)
|
||||
assert.WithinDuration(t, expectedTimeoutAt, sess.initial.Session.TimeoutAt, maxDelta)
|
||||
assert.WithinDuration(t, expectedTimeoutAt, sess.refreshed.Session.TimeoutAt, maxDelta)
|
||||
|
||||
assert.True(t, sess.refreshed.Session.TimeoutAt.After(sess.initial.Session.TimeoutAt))
|
||||
|
||||
previousTimeoutDuration := time.Duration(sess.initial.Session.TimeoutInSeconds) * time.Second
|
||||
assert.WithinDuration(t, expectedTimeoutAt, time.Now().Add(previousTimeoutDuration), maxDelta)
|
||||
|
||||
refreshedTimeoutDuration := time.Duration(sess.refreshed.Session.TimeoutInSeconds) * time.Second
|
||||
assert.WithinDuration(t, expectedTimeoutAt, time.Now().Add(refreshedTimeoutDuration), maxDelta)
|
||||
}
|
||||
|
||||
func TestSessionRefresh_WithSSO(t *testing.T) {
|
||||
cfg := mock.Config()
|
||||
cfg.SSO.Enabled = true
|
||||
|
||||
idp := mock.NewIdentityProvider(cfg)
|
||||
defer idp.Close()
|
||||
|
||||
sess := assertSessionRefresh(t, idp, cfg)
|
||||
assertAutoRefreshDisabled(t, sess.initial)
|
||||
assertAutoRefreshDisabled(t, sess.refreshed)
|
||||
}
|
||||
|
||||
func TestSessionRefresh_WithForwardAuth(t *testing.T) {
|
||||
cfg := mock.Config()
|
||||
cfg.SSO.Enabled = true
|
||||
cfg.Session.ForwardAuth = true
|
||||
|
||||
idp := mock.NewIdentityProvider(cfg)
|
||||
defer idp.Close()
|
||||
|
||||
sess := assertSessionRefresh(t, idp, cfg)
|
||||
assertAutoRefreshEnabled(t, sess.initial)
|
||||
assertAutoRefreshEnabled(t, sess.refreshed)
|
||||
}
|
||||
|
||||
func TestSessionForwardAuth(t *testing.T) {
|
||||
@@ -738,7 +539,7 @@ func waitForRefreshCooldownTimer(t *testing.T, idp *mock.IdentityProvider, rpCli
|
||||
resp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var temp session.MetadataVerboseWithRefresh
|
||||
var temp session.MetadataVerbose
|
||||
err := json.Unmarshal([]byte(resp.Body), &temp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -882,3 +683,135 @@ func getCookieFromJar(name string, cookies []*http.Cookie) *http.Cookie {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func assertAutoRefreshEnabled(t *testing.T, data session.MetadataVerbose) {
|
||||
assert.LessOrEqual(t, data.Tokens.NextAutoRefreshInSeconds, data.Tokens.ExpireInSeconds)
|
||||
assert.Greater(t, data.Tokens.NextAutoRefreshInSeconds, int64(1))
|
||||
}
|
||||
|
||||
func assertAutoRefreshDisabled(t *testing.T, data session.MetadataVerbose) {
|
||||
assert.Less(t, data.Tokens.NextAutoRefreshInSeconds, data.Tokens.ExpireInSeconds)
|
||||
assert.Equal(t, int64(-1), data.Tokens.NextAutoRefreshInSeconds)
|
||||
}
|
||||
|
||||
func assertSessionInfo(t *testing.T, idp *mock.IdentityProvider, cfg *config.Config) session.MetadataVerbose {
|
||||
rpClient := idp.RelyingPartyClient()
|
||||
noSessionResp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusUnauthorized, noSessionResp.StatusCode)
|
||||
|
||||
login(t, rpClient, idp)
|
||||
|
||||
resp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var data session.MetadataVerbose
|
||||
err := json.Unmarshal([]byte(resp.Body), &data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
allowedSkew := 5 * time.Second
|
||||
assert.WithinDuration(t, time.Now(), data.Session.CreatedAt, allowedSkew)
|
||||
assert.WithinDuration(t, time.Now().Add(cfg.Session.MaxLifetime), data.Session.EndsAt, allowedSkew)
|
||||
assert.WithinDuration(t, time.Now().Add(idp.ProviderHandler.TokenDuration), data.Tokens.ExpireAt, allowedSkew)
|
||||
assert.WithinDuration(t, time.Now(), data.Tokens.RefreshedAt, allowedSkew)
|
||||
|
||||
sessionEndDuration := time.Duration(data.Session.EndsInSeconds) * time.Second
|
||||
// 1 second < time until session ends <= configured max session lifetime
|
||||
assert.LessOrEqual(t, sessionEndDuration, cfg.Session.MaxLifetime)
|
||||
assert.Greater(t, sessionEndDuration, time.Second)
|
||||
|
||||
tokenExpiryDuration := time.Duration(data.Tokens.ExpireInSeconds) * time.Second
|
||||
// 1 second < time until token expires <= max duration for tokens from IDP
|
||||
assert.LessOrEqual(t, tokenExpiryDuration, idp.ProviderHandler.TokenDuration)
|
||||
assert.Greater(t, tokenExpiryDuration, time.Second)
|
||||
|
||||
assert.True(t, data.Tokens.RefreshCooldown)
|
||||
// 1 second < refresh cooldown <= minimum refresh interval
|
||||
assert.LessOrEqual(t, data.Tokens.RefreshCooldownSeconds, session.RefreshMinInterval)
|
||||
assert.Greater(t, data.Tokens.RefreshCooldownSeconds, int64(1))
|
||||
|
||||
assert.True(t, data.Session.Active)
|
||||
|
||||
if !cfg.Session.Inactivity {
|
||||
assert.True(t, data.Session.TimeoutAt.IsZero())
|
||||
assert.Equal(t, int64(-1), data.Session.TimeoutInSeconds)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
type sessionRefreshLifecycle struct {
|
||||
initial session.MetadataVerbose
|
||||
refreshed session.MetadataVerbose
|
||||
}
|
||||
|
||||
func assertSessionRefresh(t *testing.T, idp *mock.IdentityProvider, cfg *config.Config) sessionRefreshLifecycle {
|
||||
idp.ProviderHandler.TokenDuration = 5 * time.Second
|
||||
|
||||
rpClient := idp.RelyingPartyClient()
|
||||
noSessionResp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusUnauthorized, noSessionResp.StatusCode)
|
||||
|
||||
login(t, rpClient, idp)
|
||||
|
||||
// get initial session info
|
||||
resp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var initial session.MetadataVerbose
|
||||
err := json.Unmarshal([]byte(resp.Body), &initial)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// wait until refresh cooldown has reached zero before attempting refresh
|
||||
waitForRefreshCooldownTimer(t, idp, rpClient)
|
||||
|
||||
// do the refresh
|
||||
resp = sessionRefresh(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var refreshed session.MetadataVerbose
|
||||
err = json.Unmarshal([]byte(resp.Body), &refreshed)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// session create and end times should be unchanged
|
||||
assert.WithinDuration(t, initial.Session.CreatedAt, refreshed.Session.CreatedAt, 0)
|
||||
assert.WithinDuration(t, initial.Session.EndsAt, refreshed.Session.EndsAt, 0)
|
||||
|
||||
// token expiration and refresh times should be later than initial
|
||||
assert.True(t, refreshed.Tokens.ExpireAt.After(initial.Tokens.ExpireAt))
|
||||
assert.True(t, refreshed.Tokens.RefreshedAt.After(initial.Tokens.RefreshedAt))
|
||||
|
||||
allowedSkew := 5 * time.Second
|
||||
assert.WithinDuration(t, time.Now().Add(idp.ProviderHandler.TokenDuration), refreshed.Tokens.ExpireAt, allowedSkew)
|
||||
assert.WithinDuration(t, time.Now(), refreshed.Tokens.RefreshedAt, allowedSkew)
|
||||
|
||||
sessionEndDuration := time.Duration(refreshed.Session.EndsInSeconds) * time.Second
|
||||
// 1 second < time until session ends <= configured max session lifetime
|
||||
assert.LessOrEqual(t, sessionEndDuration, cfg.Session.MaxLifetime)
|
||||
assert.Greater(t, sessionEndDuration, time.Second)
|
||||
|
||||
tokenExpiryDuration := time.Duration(refreshed.Tokens.ExpireInSeconds) * time.Second
|
||||
// 1 second < time until token expires <= max duration for tokens from IDP
|
||||
assert.LessOrEqual(t, tokenExpiryDuration, idp.ProviderHandler.TokenDuration)
|
||||
assert.Greater(t, tokenExpiryDuration, time.Second)
|
||||
|
||||
assert.True(t, refreshed.Tokens.RefreshCooldown)
|
||||
// 1 second < refresh cooldown <= minimum refresh interval
|
||||
assert.LessOrEqual(t, refreshed.Tokens.RefreshCooldownSeconds, session.RefreshMinInterval)
|
||||
assert.Greater(t, refreshed.Tokens.RefreshCooldownSeconds, int64(1))
|
||||
|
||||
assert.True(t, initial.Session.Active)
|
||||
assert.True(t, refreshed.Session.Active)
|
||||
|
||||
if !cfg.Session.Inactivity {
|
||||
assert.True(t, initial.Session.TimeoutAt.IsZero())
|
||||
assert.True(t, refreshed.Session.TimeoutAt.IsZero())
|
||||
|
||||
assert.Equal(t, int64(-1), initial.Session.TimeoutInSeconds)
|
||||
assert.Equal(t, int64(-1), refreshed.Session.TimeoutInSeconds)
|
||||
}
|
||||
|
||||
return sessionRefreshLifecycle{
|
||||
initial: initial,
|
||||
refreshed: refreshed,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,40 +234,27 @@ func (in *Metadata) Verbose() MetadataVerbose {
|
||||
endTime := in.Session.EndsAt
|
||||
timeoutTime := in.Session.TimeoutAt
|
||||
|
||||
mv := MetadataVerbose{
|
||||
Session: MetadataSessionVerbose{
|
||||
MetadataSession: in.Session,
|
||||
EndsInSeconds: toSeconds(endTime.Sub(now)),
|
||||
Active: !in.IsTimedOut(),
|
||||
TimeoutInSeconds: toSeconds(timeoutTime.Sub(now)),
|
||||
},
|
||||
Tokens: MetadataTokensVerbose{
|
||||
MetadataTokens: in.Tokens,
|
||||
ExpireInSeconds: toSeconds(expireTime.Sub(now)),
|
||||
},
|
||||
session := MetadataSessionVerbose{
|
||||
MetadataSession: in.Session,
|
||||
EndsInSeconds: toSeconds(endTime.Sub(now)),
|
||||
Active: !in.IsTimedOut(),
|
||||
TimeoutInSeconds: toSeconds(timeoutTime.Sub(now)),
|
||||
}
|
||||
|
||||
if timeoutTime.IsZero() {
|
||||
mv.Session.TimeoutInSeconds = int64(-1)
|
||||
session.TimeoutInSeconds = int64(-1)
|
||||
}
|
||||
|
||||
return mv
|
||||
}
|
||||
tokens := MetadataTokensVerbose{
|
||||
MetadataTokens: in.Tokens,
|
||||
ExpireInSeconds: toSeconds(expireTime.Sub(now)),
|
||||
NextAutoRefreshInSeconds: toSeconds(in.NextRefresh().Sub(now)),
|
||||
RefreshCooldown: in.IsRefreshOnCooldown(),
|
||||
RefreshCooldownSeconds: toSeconds(in.RefreshCooldown().Sub(now)),
|
||||
}
|
||||
|
||||
func (in *Metadata) VerboseWithRefresh() MetadataVerboseWithRefresh {
|
||||
now := time.Now()
|
||||
|
||||
verbose := in.Verbose()
|
||||
nextRefreshTime := in.NextRefresh()
|
||||
|
||||
return MetadataVerboseWithRefresh{
|
||||
Session: verbose.Session,
|
||||
Tokens: MetadataTokensVerboseWithRefresh{
|
||||
MetadataTokensVerbose: verbose.Tokens,
|
||||
NextAutoRefreshInSeconds: toSeconds(nextRefreshTime.Sub(now)),
|
||||
RefreshCooldown: in.IsRefreshOnCooldown(),
|
||||
RefreshCooldownSeconds: toSeconds(in.RefreshCooldown().Sub(now)),
|
||||
},
|
||||
return MetadataVerbose{
|
||||
Session: session,
|
||||
Tokens: tokens,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,11 +263,6 @@ type MetadataVerbose struct {
|
||||
Tokens MetadataTokensVerbose `json:"tokens"`
|
||||
}
|
||||
|
||||
type MetadataVerboseWithRefresh struct {
|
||||
Session MetadataSessionVerbose `json:"session"`
|
||||
Tokens MetadataTokensVerboseWithRefresh `json:"tokens"`
|
||||
}
|
||||
|
||||
type MetadataSessionVerbose struct {
|
||||
MetadataSession
|
||||
EndsInSeconds int64 `json:"ends_in_seconds"`
|
||||
@@ -290,11 +272,7 @@ type MetadataSessionVerbose struct {
|
||||
|
||||
type MetadataTokensVerbose struct {
|
||||
MetadataTokens
|
||||
ExpireInSeconds int64 `json:"expire_in_seconds"`
|
||||
}
|
||||
|
||||
type MetadataTokensVerboseWithRefresh struct {
|
||||
MetadataTokensVerbose
|
||||
ExpireInSeconds int64 `json:"expire_in_seconds"`
|
||||
NextAutoRefreshInSeconds int64 `json:"next_auto_refresh_in_seconds"`
|
||||
RefreshCooldown bool `json:"refresh_cooldown"`
|
||||
RefreshCooldownSeconds int64 `json:"refresh_cooldown_seconds"`
|
||||
|
||||
@@ -277,32 +277,14 @@ func TestMetadata_Verbose(t *testing.T) {
|
||||
actual = time.Now().Add(durationSeconds(verbose.Tokens.ExpireInSeconds))
|
||||
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)
|
||||
}
|
||||
|
||||
func TestMetadata_VerboseWithRefresh(t *testing.T) {
|
||||
tokenLifetime := 30 * time.Minute
|
||||
sessionLifetime := time.Hour
|
||||
|
||||
metadata := session.NewMetadata(tokenLifetime, sessionLifetime)
|
||||
|
||||
verbose := metadata.VerboseWithRefresh()
|
||||
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)
|
||||
|
||||
@@ -314,7 +296,7 @@ func TestMetadata_VerboseWithRefresh(t *testing.T) {
|
||||
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.VerboseWithRefresh()
|
||||
verbose := metadata.Verbose()
|
||||
|
||||
assert.False(t, verbose.Tokens.RefreshCooldown)
|
||||
assert.Equal(t, int64(0), verbose.Tokens.RefreshCooldownSeconds)
|
||||
|
||||
@@ -84,10 +84,6 @@ func (in *Session) MetadataVerbose() MetadataVerbose {
|
||||
return in.data.Metadata.Verbose()
|
||||
}
|
||||
|
||||
func (in *Session) MetadataVerboseRefresh() MetadataVerboseWithRefresh {
|
||||
return in.data.Metadata.VerboseWithRefresh()
|
||||
}
|
||||
|
||||
func (in *Session) SetCookie(w http.ResponseWriter, opts cookie.Options, crypter crypto.Crypter) error {
|
||||
return in.ticket.SetCookie(w, opts, crypter)
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ func (in *manager) GetOrRefresh(r *http.Request) (*Session, error) {
|
||||
return nil, fmt.Errorf("getting session: %w", err)
|
||||
}
|
||||
|
||||
if !in.cfg.Session.RefreshAuto || !sess.shouldRefresh() {
|
||||
if !sess.shouldRefresh() {
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ func (in *manager) GetOrRefresh(r *http.Request) (*Session, error) {
|
||||
}
|
||||
|
||||
func (in *manager) Refresh(r *http.Request, sess *Session) (*Session, error) {
|
||||
if !in.cfg.Session.Refresh || !sess.canRefresh() {
|
||||
if !sess.canRefresh() {
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user