Files
wonderwall/pkg/session/ticket.go
2025-01-30 14:03:39 +01:00

86 lines
2.6 KiB
Go

package session
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"github.com/nais/liberator/pkg/keygen"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
"github.com/nais/wonderwall/internal/crypto"
"github.com/nais/wonderwall/pkg/cookie"
)
// Ticket contains the user agent's data required to access their associated session.
type Ticket struct {
// SessionKey identifies the session.
SessionKey string `json:"id"`
// EncryptionKey is the data encryption key (DEK) used to encrypt the session's data.
// Its size is equal to the expected key size for the used AEAD, defined in crypto.KeySize.
EncryptionKey []byte `json:"dek"`
crypter crypto.Crypter
}
func NewTicket(sessionKey string) (*Ticket, error) {
encKey, err := keygen.Keygen(crypto.KeySize)
if err != nil {
return nil, fmt.Errorf("generate encryption key: %w", err)
}
return &Ticket{SessionKey: sessionKey, EncryptionKey: encKey}, nil
}
// Crypter returns a crypto.Crypter initialized with the session's data encryption key.
func (c *Ticket) Crypter() crypto.Crypter {
if c.crypter == nil {
c.crypter = crypto.NewCrypter(c.EncryptionKey)
}
return c.crypter
}
// Key returns the key that identifies the session.
func (c *Ticket) Key() string {
return c.SessionKey
}
// SetCookie marshals the Ticket, encrypts the value with the given crypto.Crypter, and writes the resulting cookie to the
// given http.ResponseWriter, applying any cookie.Options to the cookie itself.
func (c *Ticket) SetCookie(w http.ResponseWriter, opts cookie.Options, crypter crypto.Crypter) error {
b, err := json.Marshal(c)
if err != nil {
return fmt.Errorf("marshalling ticket: %w", err)
}
return cookie.EncryptAndSet(w, cookie.Session, string(b), opts, crypter)
}
// getTicket returns a Ticket from the session cookie found in the http.Request, given a crypto.Crypter that
// can decrypt the cookie is provided.
func getTicket(r *http.Request, crypter crypto.Crypter) (*Ticket, error) {
span := trace.SpanFromContext(r.Context())
span.SetAttributes(attribute.Bool("session.valid_ticket", false))
ticketJson, err := cookie.GetDecrypted(r, cookie.Session, crypter)
if errors.Is(err, http.ErrNoCookie) {
return nil, fmt.Errorf("ticket: cookie %w", ErrNotFound)
}
if errors.Is(err, cookie.ErrInvalidValue) || errors.Is(err, cookie.ErrDecrypt) {
return nil, fmt.Errorf("ticket: cookie: %w: %w", ErrInvalid, err)
}
if err != nil {
return nil, err
}
var ticket Ticket
err = json.Unmarshal([]byte(ticketJson), &ticket)
if err != nil {
return nil, fmt.Errorf("ticket: unmarshalling: %w", err)
}
span.SetAttributes(attribute.Bool("session.valid_ticket", true))
return &ticket, nil
}