Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c442a72
Start implementing federation.
fancycode Jun 5, 2024
e08ce21
Include original id in request to join federated room so client can m…
fancycode Jul 17, 2024
43b8ea5
Rewrite more session ids for proxied federated events.
fancycode Jul 17, 2024
148c779
federation: Send "bye" before closing, use message id for error respo…
fancycode Jul 17, 2024
6243ef1
Make sure response to federated room leave contains id from request.
fancycode Jul 17, 2024
f271faa
Add tests for federation code.
fancycode Jul 17, 2024
0451cea
Fix missing events if federated session leaves and joins again.
fancycode Jul 18, 2024
c055b12
Support different federated room ids.
fancycode Jul 18, 2024
4f95416
Send websocket close message before closing connection and don't log …
fancycode Jul 18, 2024
cf2e3aa
Return details for errors while creating federated clients.
fancycode Jul 18, 2024
deb7f71
Add option to disable certificate validation for federation
fancycode Jul 18, 2024
1ec09d6
Support timeouts for federation connections.
fancycode Jul 18, 2024
1873d15
Add information on federation to documentation.
fancycode Jul 18, 2024
c3d24b6
Expect base signaling url for federation requests.
fancycode Jul 18, 2024
ffa70f3
Add missing error handling.
fancycode Jul 18, 2024
a256789
Support reconnecting the internal federated connection.
fancycode Jul 24, 2024
fd651d7
Check for capability feature "federation-v2" for federated connections.
fancycode Jul 24, 2024
8672134
Include "federated" in "join" events for fed. sessions.
fancycode Jul 25, 2024
2f76dea
Detect duplicate join requests for federated clients.
fancycode Jul 25, 2024
f4b6f23
Support switching federated rooms while reusing internal connections.
fancycode Jul 25, 2024
347abb2
Ignore already closed connections when closing websocket.
fancycode Jul 25, 2024
b8b6f37
Use different NATS servers for federation tests.
fancycode Jul 31, 2024
c83c42c
Add special handling for "forceMute" control event.
fancycode Jul 31, 2024
11a1f36
Convert actorId / actorType in participants updates for federated users.
fancycode Aug 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 145 additions & 9 deletions api_signaling.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,31 @@ const (

// Version 2.0 validates auth params encoded as JWT.
HelloVersionV2 = "2.0"

ActorTypeUsers = "users"
ActorTypeFederatedUsers = "federated_users"
)

var (
ErrNoSdp = NewError("no_sdp", "Payload does not contain a SDP.")
ErrInvalidSdp = NewError("invalid_sdp", "Payload does not contain a valid SDP.")
)

func makePtr[T any](v T) *T {
return &v
}

func getStringMapEntry[T any](m map[string]interface{}, key string) (s T, ok bool) {
var defaultValue T
v, found := m[key]
if !found {
return defaultValue, false
}

s, ok = v.(T)
return
}

// ClientMessage is a message that is sent from a client to the server.
type ClientMessage struct {
json.Marshaler
Expand Down Expand Up @@ -121,7 +139,7 @@ func (m *ClientMessage) CheckValid() error {
return nil
}

func (m *ClientMessage) String() string {
func (m ClientMessage) String() string {
data, err := json.Marshal(m)
if err != nil {
return fmt.Sprintf("Could not serialize %#v: %s", m, err)
Expand Down Expand Up @@ -311,9 +329,21 @@ func (m *WelcomeServerMessage) RemoveFeature(feature ...string) {
m.Features = newFeatures
}

func (m *WelcomeServerMessage) HasFeature(feature string) bool {
for _, f := range m.Features {
f = strings.TrimSpace(f)
if f == feature {
return true
}
}

return false
}

const (
HelloClientTypeClient = "client"
HelloClientTypeInternal = "internal"
HelloClientTypeClient = "client"
HelloClientTypeInternal = "internal"
HelloClientTypeFederation = "federation"

HelloClientTypeVirtual = "virtual"
)
Expand Down Expand Up @@ -363,12 +393,53 @@ func (p *HelloV2AuthParams) CheckValid() error {
return nil
}

type AuthTokenClaims interface {
TokenSubject() string
TokenUserData() json.RawMessage

VerifyIssuedAt(cmp time.Time, req bool) bool
VerifyExpiresAt(cmp time.Time, req bool) bool
}

type HelloV2TokenClaims struct {
jwt.RegisteredClaims

UserData json.RawMessage `json:"userdata,omitempty"`
}

func (c *HelloV2TokenClaims) TokenSubject() string {
return c.Subject
}

func (c *HelloV2TokenClaims) TokenUserData() json.RawMessage {
return c.UserData
}

type FederationAuthParams struct {
Token string `json:"token"`
}

func (p *FederationAuthParams) CheckValid() error {
if p.Token == "" {
return fmt.Errorf("token missing")
}
return nil
}

type FederationTokenClaims struct {
jwt.RegisteredClaims

UserData json.RawMessage `json:"userdata,omitempty"`
}

func (c *FederationTokenClaims) TokenSubject() string {
return c.Subject
}

func (c *FederationTokenClaims) TokenUserData() json.RawMessage {
return c.UserData
}

type HelloClientMessageAuth struct {
// The client type that is connecting. Leave empty to use the default
// "HelloClientTypeClient"
Expand All @@ -379,8 +450,9 @@ type HelloClientMessageAuth struct {
Url string `json:"url"`
parsedUrl *url.URL

internalParams ClientTypeInternalAuthParams
helloV2Params HelloV2AuthParams
internalParams ClientTypeInternalAuthParams
helloV2Params HelloV2AuthParams
federationParams FederationAuthParams
}

// Type "hello"
Expand Down Expand Up @@ -409,6 +481,8 @@ func (m *HelloClientMessage) CheckValid() error {
}
switch m.Auth.Type {
case HelloClientTypeClient:
fallthrough
case HelloClientTypeFederation:
if m.Auth.Url == "" {
return fmt.Errorf("url missing")
} else if u, err := url.ParseRequestURI(m.Auth.Url); err != nil {
Expand All @@ -425,10 +499,19 @@ func (m *HelloClientMessage) CheckValid() error {
case HelloVersionV1:
// No additional validation necessary.
case HelloVersionV2:
if err := json.Unmarshal(m.Auth.Params, &m.Auth.helloV2Params); err != nil {
return err
} else if err := m.Auth.helloV2Params.CheckValid(); err != nil {
return err
switch m.Auth.Type {
case HelloClientTypeClient:
if err := json.Unmarshal(m.Auth.Params, &m.Auth.helloV2Params); err != nil {
return err
} else if err := m.Auth.helloV2Params.CheckValid(); err != nil {
return err
}
case HelloClientTypeFederation:
if err := json.Unmarshal(m.Auth.Params, &m.Auth.federationParams); err != nil {
return err
} else if err := m.Auth.federationParams.CheckValid(); err != nil {
return err
}
}
}
case HelloClientTypeInternal:
Expand Down Expand Up @@ -456,6 +539,7 @@ const (
ServerFeatureHelloV2 = "hello-v2"
ServerFeatureSwitchTo = "switchto"
ServerFeatureDialout = "dialout"
ServerFeatureFederation = "federation"

// Features to send to internal clients only.
ServerFeatureInternalVirtualSessions = "virtual-sessions"
Expand All @@ -474,6 +558,7 @@ var (
ServerFeatureHelloV2,
ServerFeatureSwitchTo,
ServerFeatureDialout,
ServerFeatureFederation,
}
DefaultFeaturesInternal = []string{
ServerFeatureInternalVirtualSessions,
Expand All @@ -483,6 +568,7 @@ var (
ServerFeatureHelloV2,
ServerFeatureSwitchTo,
ServerFeatureDialout,
ServerFeatureFederation,
}
DefaultWelcomeFeatures = []string{
ServerFeatureAudioVideoPermissions,
Expand All @@ -493,6 +579,7 @@ var (
ServerFeatureHelloV2,
ServerFeatureSwitchTo,
ServerFeatureDialout,
ServerFeatureFederation,
}
)

Expand Down Expand Up @@ -526,10 +613,56 @@ type ByeServerMessage struct {
type RoomClientMessage struct {
RoomId string `json:"roomid"`
SessionId string `json:"sessionid,omitempty"`

Federation *RoomFederationMessage `json:"federation,omitempty"`
}

func (m *RoomClientMessage) CheckValid() error {
// No additional validation required.
if m.Federation != nil {
if err := m.Federation.CheckValid(); err != nil {
return err
}
}

return nil
}

type RoomFederationMessage struct {
SignalingUrl string `json:"signaling"`
parsedSignalingUrl *url.URL

NextcloudUrl string `json:"url"`
parsedNextcloudUrl *url.URL

RoomId string `json:"roomid,omitempty"`
Token string `json:"token"`
}

func (m *RoomFederationMessage) CheckValid() error {
if m.SignalingUrl == "" {
return errors.New("signaling url missing")
}

if m.SignalingUrl[len(m.SignalingUrl)-1] != '/' {
m.SignalingUrl += "/"
}
if u, err := url.Parse(m.SignalingUrl); err != nil {
return fmt.Errorf("invalid signaling url: %w", err)
} else {
m.parsedSignalingUrl = u
}
if m.NextcloudUrl == "" {
return errors.New("nextcloud url missing")
} else if u, err := url.Parse(m.NextcloudUrl); err != nil {
return fmt.Errorf("invalid nextcloud url: %w", err)
} else {
m.parsedNextcloudUrl = u
}
if m.Token == "" {
return errors.New("token missing")
}

return nil
}

Expand Down Expand Up @@ -909,6 +1042,7 @@ type EventServerMessage struct {
Leave []string `json:"leave,omitempty"`
Change []*EventServerMessageSessionEntry `json:"change,omitempty"`
SwitchTo *EventServerMessageSwitchTo `json:"switchto,omitempty"`
Resumed *bool `json:"resumed,omitempty"`

// Used for target "roomlist" / "participants"
Invite *RoomEventServerMessage `json:"invite,omitempty"`
Expand All @@ -933,6 +1067,7 @@ type EventServerMessageSessionEntry struct {
UserId string `json:"userid"`
User json.RawMessage `json:"user,omitempty"`
RoomSessionId string `json:"roomsessionid,omitempty"`
Federated bool `json:"federated,omitempty"`
}

func (e *EventServerMessageSessionEntry) Clone() *EventServerMessageSessionEntry {
Expand All @@ -941,6 +1076,7 @@ func (e *EventServerMessageSessionEntry) Clone() *EventServerMessageSessionEntry
UserId: e.UserId,
User: e.User,
RoomSessionId: e.RoomSessionId,
Federated: e.Federated,
}
}

Expand Down
Loading