Files
Cyrene_v2/internal/channel/session/session.go
T
AskaEth af8591b78a fix(channel,user): owner 车道只认白名单,堵住群主/伪造 role 冒充主人(裁决 06.4.2/06.4.3/06.4.4)
- 适配器只描述平台身份:qq_obv11 的 owner/admin 一律上报 RoleMember,不再上报 RoleOwner
- session 层唯一判主点只认白名单(owner.yaml Accounts ∪ 每渠道 adminIds),nil 档案 fail-closed 拒绝
- 新增「每渠道管理员层」:channels.json ChannelAccount.adminIds[] → runtimeconfig.AdminIDs → OwnerProfile.SetPlatformAdmins(整体替换语义,热重载后移除的 id 立即失效)
- 组合根接线:启动即播种、面板保存先灌白名单再重载适配器(cmd/cyrene/wire_channels.go + 4 条端到端单测)
- 06.4.3 节流与参与度状态按会话独立计数(ConversationKey 三处统一,空会话 GC);06.4.4 未知平台主动发言保守默认 1 条/分钟
2026-10-04 12:07:40 +08:00

82 lines
3.5 KiB
Go

// Package session maps IM channels to internal sessions and resolves the
// strongly typed actor identity that gates every downstream permission decision.
//
// It is the M6 home of three cross-cutting concerns wired together at the
// channel layer, per DevDocs/design/11 §0/§2:
//
// - Channel↔session mapping: a persistent (platform, channel_id) → session_id
// map (SQLite table channel_sessions), so the same conversation always maps
// to the same internal session across restarts.
// - Owner recognition: a structural, never-prompt rule. ResolveActor decides
// owner vs group_member vs stranger purely from channel-level actor metadata
// matched against the user.OwnerProfile account whitelist. Nothing here reads
// message text to infer identity — that is a project red line.
// - Proactive push policy: per-platform rules from design/08 §3 (weixin-ilink
// 24h window, OneBot burst rate limit, telegram/webhook unrestricted), with a
// conservative strict limit as the default for unknown platforms (裁决 06.4.4).
//
// The package is concurrency-safe: the manager guards its cache with an
// RWMutex and the rate limiter uses its own mutex, so multiple adapters may
// resolve sessions and issue pushes concurrently.
package session
import (
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"time"
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter"
)
// Package-level sentinel errors. Each is wrapped with %w at the call site so
// callers can use errors.Is.
var (
// ErrNilDB is returned when SessionManager is built with a nil database.
ErrNilDB = errors.New("session: nil database handle")
// ErrInvalidChannel is returned when a message has no usable channel id.
ErrInvalidChannel = errors.New("session: empty channel id")
)
// sessionIDPrefix distinguishes generated session ids from foreign identifiers.
const sessionIDPrefix = "sess-"
// Session is the resolved channel↔session binding for one inbound message. It
// carries the identity metadata (ActorRole, Owner, IsGroup) that the policy
// engine and memory layer read, plus the isolation flag MemWritable which says
// whether this conversation may write to shared memory.
//
// The memory rule follows design/02 §3 and design/11 §2: only the owner's
// private conversation may read/write memory shared with the Web main session;
// group chats and stranger conversations may read memory read-only and may
// never persist anything that could leak owner-sensitive information.
type Session struct {
// Platform is the normalized platform identifier (e.g. "weixin-ilink").
Platform string
// ChannelID is the stable channel key used for session mapping.
ChannelID string
// SessionID is the internal session binding the channel maps to.
SessionID string
// ActorRole is the strongly typed role of the resolved actor.
ActorRole adapter.Role
// Owner reports whether the actor is the bound owner account.
Owner bool
// IsGroup reports whether this is a group/room conversation.
IsGroup bool
// MemWritable reports whether this conversation may write shared memory.
// It is true only for the owner's private conversation.
MemWritable bool
}
// newSessionID returns a random, collision-resistant session identifier.
func newSessionID() string {
var b [16]byte
if _, err := rand.Read(b[:]); err != nil {
// rand.Read should never fail; fall back to a nanosecond-named id so a
// session can still be created rather than failing the whole call.
return fmt.Sprintf("%s%d", sessionIDPrefix, time.Now().UnixNano())
}
return sessionIDPrefix + hex.EncodeToString(b[:])
}