mirror of
https://github.com/nais/wonderwall.git
synced 2026-08-23 21:16:14 +00:00
Each finding was reviewed individually; the annotations record why the flagged code is safe rather than suppressing the rules globally. - G101: viper configuration keys, not credentials - G117: the marshalled value is the plaintext input to the encryption that immediately follows, or a token endpoint response that is required to carry tokens - G118: the background deletion must outlive the request, so the request context deliberately is not used - G124: cookie attributes are validated in config.Cookie.Validate - G710: the redirect targets are relative by construction, validated by the url validators, or read from the provider's metadata document
167 lines
3.6 KiB
Go
167 lines
3.6 KiB
Go
package cookie
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/nais/wonderwall/internal/crypto"
|
|
)
|
|
|
|
const (
|
|
DefaultPrefix = "io.nais.wonderwall"
|
|
)
|
|
|
|
var (
|
|
Login = login(DefaultPrefix)
|
|
LoginCount = loginCount(DefaultPrefix)
|
|
Logout = logout(DefaultPrefix)
|
|
Retry = retry(DefaultPrefix)
|
|
Session = session(DefaultPrefix)
|
|
ErrInvalidValue = errors.New("invalid value")
|
|
ErrDecrypt = errors.New("unable to decrypt, key or scheme mismatch")
|
|
)
|
|
|
|
type Cookie struct {
|
|
*http.Cookie
|
|
}
|
|
|
|
func (in *Cookie) Encrypt(crypter crypto.Crypter) (*Cookie, error) {
|
|
plaintext := []byte(in.Value)
|
|
ciphertext, err := crypter.Encrypt(plaintext)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to encrypt cookie '%s': %w", in.Name, err)
|
|
}
|
|
|
|
value := base64.RawURLEncoding.EncodeToString(ciphertext)
|
|
in.Value = value
|
|
return in, nil
|
|
}
|
|
|
|
func (in *Cookie) Decrypt(crypter crypto.Crypter) (string, error) {
|
|
ciphertext, err := base64.RawURLEncoding.DecodeString(in.Value)
|
|
if err != nil {
|
|
return "", fmt.Errorf("%w: named '%s': %w", ErrInvalidValue, in.Name, err)
|
|
}
|
|
|
|
plaintext, err := crypter.Decrypt(ciphertext)
|
|
if err != nil {
|
|
return "", fmt.Errorf("%w: named '%s': %w", ErrDecrypt, in.Name, err)
|
|
}
|
|
|
|
return string(plaintext), err
|
|
}
|
|
|
|
func Clear(w http.ResponseWriter, name string, opts Options) {
|
|
expires := time.Unix(0, 0)
|
|
maxAge := -1
|
|
|
|
// #nosec G124 -- the Secure and SameSite attributes are validated in config.Cookie.Validate
|
|
cookie := &http.Cookie{
|
|
Expires: expires,
|
|
HttpOnly: true,
|
|
MaxAge: maxAge,
|
|
Name: name,
|
|
Path: "/",
|
|
SameSite: opts.SameSite,
|
|
Secure: opts.Secure,
|
|
}
|
|
|
|
if len(opts.Domain) > 0 {
|
|
cookie.Domain = opts.Domain
|
|
}
|
|
|
|
if len(opts.Path) > 0 {
|
|
cookie.Path = opts.Path
|
|
}
|
|
|
|
http.SetCookie(w, cookie)
|
|
}
|
|
|
|
func Get(r *http.Request, key string) (*Cookie, error) {
|
|
cookie, err := r.Cookie(key)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("no cookie named '%s': %w", key, err)
|
|
}
|
|
|
|
return &Cookie{cookie}, nil
|
|
}
|
|
|
|
func GetDecrypted(r *http.Request, key string, crypter crypto.Crypter) (string, error) {
|
|
encryptedCookie, err := Get(r, key)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return encryptedCookie.Decrypt(crypter)
|
|
}
|
|
|
|
func Make(name, value string, opts Options) *Cookie {
|
|
// #nosec G124 -- the Secure and SameSite attributes are validated in config.Cookie.Validate
|
|
cookie := &http.Cookie{
|
|
HttpOnly: true,
|
|
Name: name,
|
|
Path: "/",
|
|
SameSite: opts.SameSite,
|
|
Secure: opts.Secure,
|
|
Value: value,
|
|
}
|
|
|
|
if len(opts.Domain) > 0 {
|
|
cookie.Domain = opts.Domain
|
|
}
|
|
|
|
if len(opts.Path) > 0 {
|
|
cookie.Path = opts.Path
|
|
}
|
|
|
|
return &Cookie{cookie}
|
|
}
|
|
|
|
func Set(w http.ResponseWriter, cookie *Cookie) {
|
|
http.SetCookie(w, cookie.Cookie)
|
|
}
|
|
|
|
func EncryptAndSet(w http.ResponseWriter, key, value string, opts Options, crypter crypto.Crypter) error {
|
|
encryptedCookie, err := Make(key, value, opts).Encrypt(crypter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
Set(w, encryptedCookie)
|
|
return nil
|
|
}
|
|
|
|
func ConfigureCookieNamesWithPrefix(prefix string) {
|
|
Login = login(prefix)
|
|
Logout = logout(prefix)
|
|
Retry = retry(prefix)
|
|
Session = session(prefix)
|
|
}
|
|
|
|
func withPrefix(prefix, s string) string {
|
|
return fmt.Sprintf("%s.%s", prefix, s)
|
|
}
|
|
|
|
func login(prefix string) string {
|
|
return withPrefix(prefix, "callback")
|
|
}
|
|
|
|
func loginCount(prefix string) string {
|
|
return withPrefix(prefix, "logincount")
|
|
}
|
|
|
|
func logout(prefix string) string {
|
|
return withPrefix(prefix, "logout")
|
|
}
|
|
|
|
func retry(prefix string) string {
|
|
return withPrefix(prefix, "retry")
|
|
}
|
|
|
|
func session(prefix string) string {
|
|
return withPrefix(prefix, "session")
|
|
}
|