fix: resolve 7 audit issues - webhook tokens, cookie security, avatar save, TUI fallback, dead code, valkey default
This commit is contained in:
@@ -9,7 +9,7 @@ import (
|
||||
// SetSessionCookie writes the session cookie with secure defaults.
|
||||
// Shared by password login, registration, and WebAuthn login.
|
||||
func SetSessionCookie(w http.ResponseWriter, cookieName, token string, duration time.Duration) {
|
||||
secure := os.Getenv("COOKIE_SECURE") == "true"
|
||||
secure := os.Getenv("COOKIE_SECURE") != "false"
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: cookieName,
|
||||
@@ -17,7 +17,7 @@ func SetSessionCookie(w http.ResponseWriter, cookieName, token string, duration
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: secure,
|
||||
SameSite: http.SameSiteDefaultMode,
|
||||
SameSite: http.SameSiteStrictMode,
|
||||
MaxAge: int(duration.Seconds()),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -210,6 +210,7 @@ type loginRequest struct {
|
||||
}
|
||||
|
||||
type updateProfileRequest struct {
|
||||
AvatarURL string `json:"avatar_url"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Bio string `json:"bio"`
|
||||
AccentColor string `json:"accent_color"`
|
||||
@@ -482,9 +483,9 @@ func (h *Handler) UpdateProfile(w http.ResponseWriter, r *http.Request) {
|
||||
_, err := h.db.ExecContext(r.Context(), `
|
||||
UPDATE users
|
||||
SET display_name = $2, bio = $3, accent_color = $4, status_text = $5, status = COALESCE(NULLIF($6, ''), status),
|
||||
banner_url = NULLIF($7, ''), tagline = NULLIF($8, ''), pronouns = NULLIF($9, ''), social_links = $10
|
||||
banner_url = NULLIF($7, ''), tagline = NULLIF($8, ''), pronouns = NULLIF($9, ''), social_links = $10, avatar = COALESCE(NULLIF($11, ''), avatar)
|
||||
WHERE id = $1
|
||||
`, userID, req.DisplayName, req.Bio, req.AccentColor, req.StatusText, req.Status, req.BannerURL, req.Tagline, req.Pronouns, socialLinksJSON)
|
||||
`, userID, req.DisplayName, req.Bio, req.AccentColor, req.StatusText, req.Status, req.BannerURL, req.Tagline, req.Pronouns, socialLinksJSON, req.AvatarURL)
|
||||
if err != nil {
|
||||
http.Error(w, `{"error":"update failed"}`, http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
@@ -108,7 +108,7 @@ func Load() *Config {
|
||||
SSLMode: getEnv("POSTGRES_SSLMODE", "require"),
|
||||
},
|
||||
Valkey: ValkeyConfig{
|
||||
URL: getEnv("VALKEY_URL", "redis://localhost:***@example.com"),
|
||||
URL: getEnv("VALKEY_URL", "redis://localhost:6379"),
|
||||
},
|
||||
MinIO: MinIOConfig{
|
||||
Endpoint: getEnv("MINIO_ENDPOINT", ""),
|
||||
|
||||
@@ -189,6 +189,9 @@ CREATE INDEX IF NOT EXISTS idx_webhooks_token ON webhooks(token);
|
||||
ALTER TABLE webhooks ADD COLUMN IF NOT EXISTS token_hash VARCHAR(64);
|
||||
CREATE INDEX IF NOT EXISTS idx_webhooks_token_hash ON webhooks(token_hash);
|
||||
|
||||
-- Drop plaintext token column — token_hash is the only stored credential
|
||||
ALTER TABLE webhooks DROP COLUMN IF EXISTS token;
|
||||
|
||||
-- Ensure reply_to column exists on messages (added after initial table creation)
|
||||
ALTER TABLE messages ADD COLUMN IF NOT EXISTS reply_to UUID REFERENCES messages(id) ON DELETE SET NULL;
|
||||
|
||||
|
||||
@@ -28,12 +28,6 @@ type Handler struct {
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
type Handler_old struct {
|
||||
db *sql.DB
|
||||
hub *gateway.Hub
|
||||
mentions *MentionHandler
|
||||
}
|
||||
|
||||
func NewHandler(db *sql.DB, hub *gateway.Hub, pushHandler *push.Handler, logger *slog.Logger) *Handler {
|
||||
return &Handler{
|
||||
db: db,
|
||||
|
||||
@@ -5,66 +5,6 @@ import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// ComputeChannelPermissions returns effective permissions for a user in a specific channel,
|
||||
// layering channel overrides on top of server-wide role permissions.
|
||||
func (c *Checker) ComputeChannelPermissions(ctx context.Context, serverID, channelID, userID string) (int64, error) {
|
||||
base, err := c.GetUserPermissions(ctx, serverID, userID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if Has(base, ADMINISTRATOR) {
|
||||
return base, nil
|
||||
}
|
||||
|
||||
// Everyone role override applies first.
|
||||
var everyoneID string
|
||||
_ = c.db.QueryRowContext(ctx, `SELECT id FROM roles WHERE server_id = $1 AND is_default = TRUE LIMIT 1`, serverID).Scan(&everyoneID)
|
||||
if everyoneID != "" {
|
||||
allow, deny, err := c.getOverride(ctx, channelID, "role", everyoneID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
base |= allow
|
||||
base &^= deny
|
||||
}
|
||||
|
||||
// Member-specific role overrides.
|
||||
rows, err := c.db.QueryContext(ctx, `
|
||||
SELECT r.id FROM roles r
|
||||
INNER JOIN member_roles mr ON mr.role_id = r.id
|
||||
WHERE mr.user_id = $1 AND mr.server_id = $2
|
||||
`, userID, serverID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var roleID string
|
||||
if err := rows.Scan(&roleID); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
allow, deny, err := c.getOverride(ctx, channelID, "role", roleID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
base |= allow
|
||||
base &^= deny
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// User-specific override wins last.
|
||||
allow, deny, err := c.getOverride(ctx, channelID, "user", userID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
base |= allow
|
||||
base &^= deny
|
||||
|
||||
return base, nil
|
||||
}
|
||||
|
||||
func (c *Checker) getOverride(ctx context.Context, channelID, targetType, targetID string) (allow, deny int64, err error) {
|
||||
err = c.db.QueryRowContext(ctx, `
|
||||
SELECT allow_bitflags, deny_bitflags FROM channel_overrides
|
||||
|
||||
@@ -143,10 +143,10 @@ func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
var avatar sql.NullString
|
||||
var createdAt sql.NullString
|
||||
err = h.db.QueryRowContext(r.Context(), `
|
||||
INSERT INTO webhooks (channel_id, name, token, token_hash, avatar, created_by)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
INSERT INTO webhooks (channel_id, name, token_hash, avatar, created_by)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, channel_id, name, avatar, created_by, created_at::text
|
||||
`, channelID, req.Name, token, tokenHash, req.Avatar, userID).Scan(
|
||||
`, channelID, req.Name, tokenHash, req.Avatar, userID).Scan(
|
||||
&wh.ID, &wh.ChannelID, &wh.Name, &avatar, &wh.CreatedBy, &createdAt,
|
||||
)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user