Files
member-console/internal/auth/valkey.go
T
cgalo5758 0b28a9dc29 Remediate security audit findings
- Replace gorilla/csrf with net/http CrossOriginProtection
- Require valkey-password and add TLS options for session store
- End session at /logout and revoke refresh tokens
- Re-derive identity and roles from provider every five minutes
- Process each Stripe webhook event in its own Temporal workflow
- Give each outbox entry its own workflow with Temporal retries
- Guard against stale Stripe events with provider timestamps
- Derive transport security from base-url scheme
2026-09-09 13:25:43 -05:00

76 lines
2.9 KiB
Go

// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Commercial
// SPDX-FileCopyrightText: 2025-2026 Christian Galo
package auth
import (
"context"
"crypto/tls"
"log/slog"
"time"
"github.com/gomodule/redigo/redis"
"github.com/spf13/viper"
"git.coopcloud.tech/wiki-cafe/member-console/internal/logging"
)
// Session-store connection tuning. These bound a dial against an unreachable or
// wedged store so the boot probe fails with an error instead of hanging, and so
// a store that stops answering mid-request cannot hold a handler open past the
// server's own write budget.
const (
valkeyConnectTimeout = 5 * time.Second
valkeyReadTimeout = 3 * time.Second
valkeyWriteTimeout = 3 * time.Second
)
// valkeyDialOptions builds the session store's dial options from configuration.
//
// Until the 2026-09 security audit the store was reached with an address and
// nothing else, so anyone who could reach the port could read and forge
// sessions, and sessions carry the person, organization and workspace ids that
// authorization is decided from. A password is now required: config validation
// refuses to start without valkey-password, so the branch below is reached with
// a password in every configuration that boots. It stays a conditional because
// this function is also exercised by tests that build partial configurations.
//
// TLS and the username are opt-in and absent by default, for a store that
// terminates TLS or runs ACL users. A single-host stack that sets neither dials
// as before, now authenticated.
func valkeyDialOptions(ctx context.Context) []redis.DialOption {
options := []redis.DialOption{
redis.DialConnectTimeout(valkeyConnectTimeout),
redis.DialReadTimeout(valkeyReadTimeout),
redis.DialWriteTimeout(valkeyWriteTimeout),
}
// A username without a password is an ACL user relying on a passwordless
// rule; both are passed through as configured rather than second-guessed.
if username := viper.GetString("valkey-username"); username != "" {
options = append(options, redis.DialUsername(username))
}
if password := viper.GetString("valkey-password"); password != "" {
options = append(options, redis.DialPassword(password))
}
if viper.GetBool("valkey-tls") {
options = append(options, redis.DialUseTLS(true))
if viper.GetBool("valkey-tls-skip-verify") {
// Certificate verification off means an attacker who can
// intercept the connection can present any certificate, so the
// encryption stops proving who is on the other end. It exists for
// a store with a self-signed certificate and is worth saying out
// loud at every boot.
logging.FromContext(ctx).Warn("valkey-tls-skip-verify is on: the session store's certificate is NOT verified",
slog.String("env", viper.GetString("env")))
options = append(options,
redis.DialTLSSkipVerify(true),
redis.DialTLSConfig(&tls.Config{InsecureSkipVerify: true}), //nolint:gosec // explicit opt-in, warned above
)
}
}
return options
}