- 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
76 lines
2.9 KiB
Go
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
|
|
}
|