Files
Warp/server/internal/matrix/auth/state.go
T
tajniak81andClaude Opus 5.5 2fbd5f53ce
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / Complement (federation off) (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
Phase 2: the event core
Implements PLAN.md Phase 2: the library every later phase is built on,
as pure code under server/internal/matrix with no HTTP and no storage.

canonicaljson: the canonical JSON encoding, hand-written because
encoding/json escapes <, > and & and accepts floats. Integers only, in
[-(2^53)+1, 2^53-1]; lone surrogate escapes rejected rather than turned
into U+FFFD.

signing: the Signing JSON algorithm alongside the existing key handling,
and one lenient base64 decoder shared by the packages above it.

id: identifier grammars as they appear inside events, deliberately laxer
than userapi's account rules because rooms carry historical user IDs.

roomversion: the registry. Versions 3 to 12 are supported, each written
out in full as a Rules value with the flags the other packages read.
Versions 1 and 2 are known but refused: their event format and state
resolution v1 wait for federation. createRoom will default to 11, as the
spec asks for a while after 1.16, with 12 available.

event: PDU parsing and validation, content and reference hashes, event
IDs, the redaction algorithm for every supported version, signing and
verification, and a Builder.

auth: the authorisation rules in spec order, power levels with their
defaults, v12 creators with infinite power, the auth events selection
algorithm, rule 3's auth_events checks, and the separate test for
whether a redaction applies. Comments use the room version 12 rule
numbering, with the version 11 numbers given where they differ.

stateres: state resolution v2, and version 12's amendment (an empty
starting state and the conflicted state subgraph).

Tested against the spec's own vectors byte for byte: the canonical JSON
examples, both JSON signing vectors and both event signing vectors.
State resolution ports scenarios from Synapse's suite. canonicaljson
and event.Parse have fuzz targets.

The capabilities endpoint now advertises the registry instead of a
hard-coded "11". docs/event-core.md describes the packages and the
known gaps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 22:23:35 +02:00

96 lines
3.2 KiB
Go

package auth
import (
"github.com/dariuszjaniak/warp/server/internal/matrix/event"
)
// StateKey identifies one piece of room state: an event type and a state
// key together. Room state is a map from this to an event.
type StateKey struct {
Type string
StateKey string
}
// State is the room state an event is authorised against.
//
// It is only ever the handful of events the rules actually read — the
// create event, the power levels, the join rules, the members involved and
// any third-party invite — not the whole state of the room. Two callers
// build it differently: event intake builds it from the event's own
// auth_events, and state resolution builds it from the state it has
// resolved so far.
type State map[StateKey]*event.Event
// NewState indexes a set of state events.
//
// A non-state event in the input is ignored rather than refused: the usual
// source is an event's auth_events, and a server that cited something odd
// there should fail the auth_events checks, not crash the indexer.
func NewState(events []*event.Event) State {
st := make(State, len(events))
for _, e := range events {
if sk, ok := e.StateKey(); ok {
st[StateKey{Type: e.Type(), StateKey: sk}] = e
}
}
return st
}
// Get returns one state event, or nil.
func (s State) Get(eventType, stateKey string) *event.Event {
return s[StateKey{Type: eventType, StateKey: stateKey}]
}
// Create returns the m.room.create event, or nil if the state does not
// contain one — which for any event but the create event itself means the
// event cannot be authorised at all.
func (s State) Create() *event.Event { return s.Get(event.TypeCreate, "") }
// PowerLevels returns the m.room.power_levels event, or nil. A room
// without one is not broken: until the creator sends one, the defaults
// apply and the creator alone has power.
func (s State) PowerLevels() *event.Event { return s.Get(event.TypePowerLevels, "") }
// JoinRules returns the m.room.join_rules event, or nil.
func (s State) JoinRules() *event.Event { return s.Get(event.TypeJoinRules, "") }
// Member returns a user's m.room.member event, or nil.
func (s State) Member(userID string) *event.Event { return s.Get(event.TypeMember, userID) }
// ThirdPartyInvite returns the m.room.third_party_invite event for a token,
// or nil.
func (s State) ThirdPartyInvite(token string) *event.Event {
return s.Get(event.TypeThirdPartyInvite, token)
}
// Membership returns a user's membership in the room.
//
// A user with no m.room.member event has never been in the room, which the
// rules treat exactly like having left: every rule that cares asks whether
// the membership *is* something, never whether it is absent.
func (s State) Membership(userID string) string {
e := s.Member(userID)
if e == nil {
return event.MembershipLeave
}
membership, err := e.Membership()
if err != nil || membership == "" {
return event.MembershipLeave
}
return membership
}
// JoinRule returns the room's join rule, defaulting to "invite" as the
// spec does for a room with no m.room.join_rules event.
func (s State) JoinRule() string {
e := s.JoinRules()
if e == nil {
return JoinRuleInvite
}
rule, err := joinRuleOf(e)
if err != nil || rule == "" {
return JoinRuleInvite
}
return rule
}