Files
wonderwall/pkg/session/session.go
Trong Huu Nguyen 3143940b08 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.
2025-01-16 10:14:15 +01:00

110 lines
3.0 KiB
Go

package session
import (
"context"
"errors"
"fmt"
"net/http"
"time"
"github.com/nais/wonderwall/pkg/cookie"
"github.com/nais/wonderwall/pkg/crypto"
"github.com/nais/wonderwall/pkg/openid"
)
var (
ErrInactive = errors.New("is inactive")
ErrInvalid = errors.New("session is invalid")
ErrInvalidExternal = errors.New("session has invalid state at identity provider")
ErrNotFound = errors.New("not found")
)
// Reader knows how to read a session.
type Reader interface {
// Get returns the session for a given http.Request, or an error if the session is invalid or not found.
Get(r *http.Request) (*Session, error)
}
// Writer knows how to create, update and delete a session.
type Writer interface {
// Create creates and stores a session in the Store.
Create(r *http.Request, tokens *openid.Tokens, sessionLifetime time.Duration) (*Session, error)
// Delete deletes a session for a given Session.
Delete(ctx context.Context, session *Session) error
// DeleteForExternalID deletes a session for a given external session ID (e.g. front-channel logout).
DeleteForExternalID(ctx context.Context, id string) error
// Refresh refreshes the user's tokens and returns the updated session. If the session should not be
// refreshed, it will return the existing session without modifications.
Refresh(r *http.Request, sess *Session) (*Session, error)
}
// Manager is both a Reader and a Writer.
type Manager interface {
Reader
Writer
// GetOrRefresh returns the session for a given http.Request. If the tokens within the session are expired and the
// session is still valid, it will automatically attempt to refresh and update the session.
GetOrRefresh(r *http.Request) (*Session, error)
}
type Session struct {
data *Data
ticket *Ticket
}
func (in *Session) AccessToken() (string, error) {
if in.data != nil && in.data.HasActiveAccessToken() {
return in.data.AccessToken, nil
}
return "", fmt.Errorf("%w: access token is expired", ErrInvalid)
}
func (in *Session) Acr() string {
if in.data != nil {
return in.data.Acr
}
return ""
}
func (in *Session) ExternalSessionID() string {
if in.data != nil {
return in.data.ExternalSessionID
}
return ""
}
func (in *Session) IDToken() string {
return in.data.IDToken
}
func (in *Session) MetadataVerbose() MetadataVerbose {
return in.data.Metadata.Verbose()
}
func (in *Session) SetCookie(w http.ResponseWriter, opts cookie.Options, crypter crypto.Crypter) error {
return in.ticket.SetCookie(w, opts, crypter)
}
func (in *Session) canRefresh() bool {
return in.data != nil && in.data.HasRefreshToken() && !in.data.Metadata.IsRefreshOnCooldown()
}
func (in *Session) encrypt() (*EncryptedData, error) {
return in.data.Encrypt(in.ticket.Crypter())
}
func (in *Session) key() string {
return in.ticket.Key()
}
func (in *Session) shouldRefresh() bool {
return in.data != nil && in.data.Metadata.ShouldRefresh()
}
func NewSession(data *Data, ticket *Ticket) *Session {
return &Session{data: data, ticket: ticket}
}