mirror of
https://github.com/nais/wonderwall.git
synced 2026-05-20 07:12:48 +00:00
refactor: separate refresh-specific fields from session info; enable endpoint without refresh feature
This commit is contained in:
12
README.md
12
README.md
@@ -42,12 +42,12 @@ Wonderwall exposes and owns these endpoints (which means they will never be prox
|
||||
|
||||
Endpoints that are available for use by applications:
|
||||
|
||||
| Path | Description |
|
||||
|---------------------------|------------------------------------------------------|
|
||||
| `/oauth2/login` | Initiates the OpenID Connect Authorization Code flow |
|
||||
| `/oauth2/logout` | Initiates local and global/single-logout |
|
||||
| `/oauth2/session` | Returns the current user's session metadata |
|
||||
| `/oauth2/session/refresh` | Refreshes the tokens for the user's session |
|
||||
| Path | Description |
|
||||
|---------------------------|------------------------------------------------------------------------------------------------|
|
||||
| `/oauth2/login` | Initiates the OpenID Connect Authorization Code flow |
|
||||
| `/oauth2/logout` | Initiates local and global/single-logout |
|
||||
| `/oauth2/session` | Returns the current user's session metadata |
|
||||
| `/oauth2/session/refresh` | Refreshes the tokens for the user's session. Requires the `session.refresh` flag to be enabled |
|
||||
|
||||
Endpoints that should be registered at and only be triggered by identity providers:
|
||||
|
||||
|
||||
@@ -27,7 +27,13 @@ func (h *Handler) SessionInfo(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(data.Metadata.Verbose())
|
||||
|
||||
if h.Config.Session.Refresh {
|
||||
err = json.NewEncoder(w).Encode(data.Metadata.VerboseWithRefresh())
|
||||
} else {
|
||||
err = json.NewEncoder(w).Encode(data.Metadata.Verbose())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Warnf("session/info: marshalling metadata: %+v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
@@ -41,7 +41,7 @@ func (h *Handler) SessionRefresh(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(data.Metadata.Verbose())
|
||||
err = json.NewEncoder(w).Encode(data.Metadata.VerboseWithRefresh())
|
||||
if err != nil {
|
||||
logger.Warnf("session/refresh: marshalling metadata: %+v", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
|
||||
@@ -188,6 +188,41 @@ func TestHandler_SessionInfo(t *testing.T) {
|
||||
// 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)
|
||||
}
|
||||
|
||||
func TestHandler_SessionInfo_WithRefresh(t *testing.T) {
|
||||
cfg := mock.Config()
|
||||
cfg.Session.Refresh = 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)
|
||||
|
||||
// 1 second < next token refresh <= seconds until token expires
|
||||
assert.LessOrEqual(t, data.Tokens.NextAutoRefreshInSeconds, data.Tokens.ExpireInSeconds)
|
||||
@@ -199,21 +234,6 @@ func TestHandler_SessionInfo(t *testing.T) {
|
||||
assert.Greater(t, data.Tokens.RefreshCooldownSeconds, int64(1))
|
||||
}
|
||||
|
||||
func TestHandler_SessionInfo_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 := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
||||
}
|
||||
|
||||
func TestHandler_SessionRefresh(t *testing.T) {
|
||||
cfg := mock.Config()
|
||||
cfg.Session.Refresh = true
|
||||
@@ -229,7 +249,7 @@ func TestHandler_SessionRefresh(t *testing.T) {
|
||||
resp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var data session.MetadataVerbose
|
||||
var data session.MetadataVerboseWithRefresh
|
||||
err := json.Unmarshal([]byte(resp.Body), &data)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -245,7 +265,7 @@ func TestHandler_SessionRefresh(t *testing.T) {
|
||||
resp := sessionInfo(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var temp session.MetadataVerbose
|
||||
var temp session.MetadataVerboseWithRefresh
|
||||
err = json.Unmarshal([]byte(resp.Body), &temp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -259,7 +279,7 @@ func TestHandler_SessionRefresh(t *testing.T) {
|
||||
resp = sessionRefresh(t, idp, rpClient)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
|
||||
var refreshedData session.MetadataVerbose
|
||||
var refreshedData session.MetadataVerboseWithRefresh
|
||||
err = json.Unmarshal([]byte(resp.Body), &refreshedData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
||||
@@ -36,9 +36,9 @@ func New(handler *handler.Handler) chi.Router {
|
||||
r.Get(paths.Logout, handler.Logout)
|
||||
r.Get(paths.FrontChannelLogout, handler.FrontChannelLogout)
|
||||
r.Get(paths.LogoutCallback, handler.LogoutCallback)
|
||||
r.Get(paths.Session, handler.SessionInfo)
|
||||
|
||||
if handler.Config.Session.Refresh {
|
||||
r.Get(paths.Session, handler.SessionInfo)
|
||||
r.Get(paths.SessionRefresh, handler.SessionRefresh)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -187,7 +187,6 @@ func (in *Metadata) Verbose() MetadataVerbose {
|
||||
|
||||
expireTime := in.Tokens.ExpireAt
|
||||
endTime := in.Session.EndsAt
|
||||
nextRefreshTime := in.NextRefresh()
|
||||
|
||||
return MetadataVerbose{
|
||||
Session: MetadataSessionVerbose{
|
||||
@@ -195,8 +194,22 @@ func (in *Metadata) Verbose() MetadataVerbose {
|
||||
EndsInSeconds: toSeconds(endTime.Sub(now)),
|
||||
},
|
||||
Tokens: MetadataTokensVerbose{
|
||||
MetadataTokens: in.Tokens,
|
||||
ExpireInSeconds: toSeconds(expireTime.Sub(now)),
|
||||
MetadataTokens: in.Tokens,
|
||||
ExpireInSeconds: toSeconds(expireTime.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)),
|
||||
@@ -209,6 +222,11 @@ 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"`
|
||||
@@ -216,7 +234,11 @@ type MetadataSessionVerbose struct {
|
||||
|
||||
type MetadataTokensVerbose struct {
|
||||
MetadataTokens
|
||||
ExpireInSeconds int64 `json:"expire_in_seconds"`
|
||||
ExpireInSeconds int64 `json:"expire_in_seconds"`
|
||||
}
|
||||
|
||||
type MetadataTokensVerboseWithRefresh struct {
|
||||
MetadataTokensVerbose
|
||||
NextAutoRefreshInSeconds int64 `json:"next_auto_refresh_in_seconds"`
|
||||
RefreshCooldown bool `json:"refresh_cooldown"`
|
||||
RefreshCooldownSeconds int64 `json:"refresh_cooldown_seconds"`
|
||||
|
||||
@@ -214,6 +214,24 @@ func TestMetadata_Verbose(t *testing.T) {
|
||||
expected = time.Now().Add(tokenLifetime)
|
||||
actual = time.Now().Add(durationSeconds(verbose.Tokens.ExpireInSeconds))
|
||||
assert.WithinDuration(t, expected, actual, maxDelta)
|
||||
}
|
||||
|
||||
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))
|
||||
@@ -230,7 +248,7 @@ func TestMetadata_Verbose(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.Verbose()
|
||||
verbose := metadata.VerboseWithRefresh()
|
||||
|
||||
assert.False(t, verbose.Tokens.RefreshCooldown)
|
||||
assert.Equal(t, int64(0), verbose.Tokens.RefreshCooldownSeconds)
|
||||
|
||||
Reference in New Issue
Block a user