package auth import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/base64" "encoding/gob" "fmt" "io" "time" ) // Session holds the claims we care about after a successful OIDC login. type Session struct { Subject string // email or sub claim Groups []string // groups claim from Authelia IssuedAt time.Time // when we issued this session cookie } // seal gob-encodes v and AES-256-GCM encrypts it into a base64url string. func seal(key [32]byte, v any) (string, error) { var buf bytes.Buffer if err := gob.NewEncoder(&buf).Encode(v); err != nil { return "", fmt.Errorf("seal gob: %w", err) } block, err := aes.NewCipher(key[:]) if err != nil { return "", err } gcm, err := cipher.NewGCM(block) if err != nil { return "", err } nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { return "", err } // nonce || ciphertext+tag ciphertext := gcm.Seal(nonce, nonce, buf.Bytes(), nil) return base64.RawURLEncoding.EncodeToString(ciphertext), nil } // open decrypts and gob-decodes a value produced by seal into v. // Returns an error if the value was tampered with or sealed with a different key. func open(key [32]byte, encoded string, v any) error { data, err := base64.RawURLEncoding.DecodeString(encoded) if err != nil { return fmt.Errorf("open base64: %w", err) } block, err := aes.NewCipher(key[:]) if err != nil { return err } gcm, err := cipher.NewGCM(block) if err != nil { return err } if len(data) < gcm.NonceSize() { return fmt.Errorf("sealed value too short") } nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():] plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) if err != nil { return fmt.Errorf("open decrypt: %w", err) } if err := gob.NewDecoder(bytes.NewReader(plaintext)).Decode(v); err != nil { return fmt.Errorf("open gob: %w", err) } return nil } // EncodeSession serialises and encrypts a Session into a cookie value. func EncodeSession(s Session, key [32]byte) (string, error) { return seal(key, s) } // DecodeSession decrypts and deserialises a cookie value produced by EncodeSession. func DecodeSession(encoded string, key [32]byte) (Session, error) { var s Session if err := open(key, encoded, &s); err != nil { return Session{}, err } return s, nil }