Skip to content

Commit

Permalink
LiveRoom: Support live room secret with stream URL. v5.13.11
Browse files Browse the repository at this point in the history
  • Loading branch information
winlinvip committed Jan 15, 2024
1 parent 42351af commit 2c5123e
Show file tree
Hide file tree
Showing 37 changed files with 2,330 additions and 16 deletions.
5 changes: 5 additions & 0 deletions DEVELOPER.md
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,10 @@ Platform, with token authentication:
* `/terraform/v1/hooks/record/remove` Hooks: Remove the Record files.
* `/terraform/v1/hooks/record/end` Record: As stream is unpublished, finish the record task quickly.
* `/terraform/v1/hooks/record/files` Hooks: List the Record files.
* `/terraform/v1/live/room/create` Live: Create a new live room.
* `/terraform/v1/live/room/query` Live: Query a new live room.
* `/terraform/v1/live/room/remove`: Live: Remove a live room.
* `/terraform/v1/live/room/list` Live: List all available live rooms.
* `/terraform/v1/ffmpeg/forward/secret` FFmpeg: Setup the forward secret to live streaming platforms.
* `/terraform/v1/ffmpeg/forward/streams` FFmpeg: Query the forwarding streams.
* `/terraform/v1/ffmpeg/vlive/secret` Setup the Virtual Live streaming secret.
Expand Down Expand Up @@ -1068,6 +1072,7 @@ The following are the update records for the SRS Stack server.
* RTSP: Rebuild the URL with escaped user info. v5.13.8
* VLive: Support SRT URL filter. [v5.13.9](https://github.com/ossrs/srs-stack/releases/tag/v5.13.9)
* FFmpeg: Monitor and restart FFmpeg if stuck. v5.13.10
* LiveRoom: Support live room secret with stream URL. v5.13.11
* v5.12
* Refine local variable name conf to config. v5.12.1
* Add forced exit on timeout for program termination. v5.12.1
Expand Down
193 changes: 193 additions & 0 deletions platform/live-room.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package main

import (
"context"
"encoding/json"
"fmt"
"github.com/go-redis/redis/v8"
"github.com/google/uuid"
"github.com/ossrs/go-oryx-lib/errors"
ohttp "github.com/ossrs/go-oryx-lib/http"
"github.com/ossrs/go-oryx-lib/logger"
"net/http"
"os"
"strings"
"time"
)

func handleLiveRoomService(ctx context.Context, handler *http.ServeMux) error {
ep := "/terraform/v1/live/room/create"
logger.Tf(ctx, "Handle %v", ep)
handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) {
if err := func() error {
var token, title string
if err := ParseBody(ctx, r.Body, &struct {
Token *string `json:"token"`
Title *string `json:"title"`
}{
Token: &token, Title: &title,
}); err != nil {
return errors.Wrapf(err, "parse body")
}

apiSecret := os.Getenv("SRS_PLATFORM_SECRET")
if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil {
return errors.Wrapf(err, "authenticate")
}

room := &SrsLiveRoom{
UUID: uuid.NewString(),
// The title of live room.
Title: title,
// The secret of live room.
Secret: strings.ReplaceAll(uuid.NewString(), "-", ""),
// Create time.
CreatedAt: time.Now().Format(time.RFC3339),
}
if b, err := json.Marshal(room); err != nil {
return errors.Wrapf(err, "marshal room")
} else if err := rdb.HSet(ctx, SRS_LIVE_ROOM, room.UUID, string(b)).Err(); err != nil {
return errors.Wrapf(err, "hset %v %v %v", SRS_LIVE_ROOM, room.UUID, string(b))
}

ohttp.WriteData(ctx, w, r, &room)
logger.Tf(ctx, "srs live room create ok, title=%v, room=%v", title, room.String())
return nil
}(); err != nil {
ohttp.WriteError(ctx, w, r, err)
}
})

ep = "/terraform/v1/live/room/query"
logger.Tf(ctx, "Handle %v", ep)
handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) {
if err := func() error {
var token, uuid string
if err := ParseBody(ctx, r.Body, &struct {
Token *string `json:"token"`
UUID *string `json:"uuid"`
}{
Token: &token, UUID: &uuid,
}); err != nil {
return errors.Wrapf(err, "parse body")
}

apiSecret := os.Getenv("SRS_PLATFORM_SECRET")
if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil {
return errors.Wrapf(err, "authenticate")
}

var room SrsLiveRoom
if r0, err := rdb.HGet(ctx, SRS_LIVE_ROOM, uuid).Result(); err != nil && err != redis.Nil {
return errors.Wrapf(err, "hget %v %v", SRS_LIVE_ROOM, uuid)
} else if r0 == "" {
return errors.Errorf("live room %v not exists", uuid)
} else if err = json.Unmarshal([]byte(r0), &room); err != nil {
return errors.Wrapf(err, "unmarshal %v %v", uuid, r0)
}

ohttp.WriteData(ctx, w, r, &room)
logger.Tf(ctx, "srs live room query ok, uuid=%v, room=%v", uuid, room.String())
return nil
}(); err != nil {
ohttp.WriteError(ctx, w, r, err)
}
})

ep = "/terraform/v1/live/room/list"
logger.Tf(ctx, "Handle %v", ep)
handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) {
if err := func() error {
var token string
if err := ParseBody(ctx, r.Body, &struct {
Token *string `json:"token"`
}{
Token: &token,
}); err != nil {
return errors.Wrapf(err, "parse body")
}

apiSecret := os.Getenv("SRS_PLATFORM_SECRET")
if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil {
return errors.Wrapf(err, "authenticate")
}

var rooms []*SrsLiveRoom
if configs, err := rdb.HGetAll(ctx, SRS_LIVE_ROOM).Result(); err != nil && err != redis.Nil {
return errors.Wrapf(err, "hgetall %v", SRS_LIVE_ROOM)
} else {
for k, v := range configs {
var obj SrsLiveRoom
if err = json.Unmarshal([]byte(v), &obj); err != nil {
return errors.Wrapf(err, "unmarshal %v %v", k, v)
}
rooms = append(rooms, &obj)
}
}

ohttp.WriteData(ctx, w, r, &struct {
Rooms []*SrsLiveRoom `json:"rooms"`
}{
Rooms: rooms,
})
logger.Tf(ctx, "srs live room create ok, rooms=%v", len(rooms))
return nil
}(); err != nil {
ohttp.WriteError(ctx, w, r, err)
}
})

ep = "/terraform/v1/live/room/remove"
logger.Tf(ctx, "Handle %v", ep)
handler.HandleFunc(ep, func(w http.ResponseWriter, r *http.Request) {
if err := func() error {
var token, uuid string
if err := ParseBody(ctx, r.Body, &struct {
Token *string `json:"token"`
UUID *string `json:"uuid"`
}{
Token: &token, UUID: &uuid,
}); err != nil {
return errors.Wrapf(err, "parse body")
}

apiSecret := os.Getenv("SRS_PLATFORM_SECRET")
if err := Authenticate(ctx, apiSecret, token, r.Header); err != nil {
return errors.Wrapf(err, "authenticate")
}

if r0, err := rdb.HGet(ctx, SRS_LIVE_ROOM, uuid).Result(); err != nil && err != redis.Nil {
return errors.Wrapf(err, "hget %v %v", SRS_LIVE_ROOM, uuid)
} else if r0 == "" {
return errors.Errorf("live room %v not exists", uuid)
}

if err := rdb.HDel(ctx, SRS_LIVE_ROOM, uuid).Err(); err != nil && err != redis.Nil {
return errors.Wrapf(err, "hdel %v %v", SRS_LIVE_ROOM, uuid)
}

ohttp.WriteData(ctx, w, r, nil)
logger.Tf(ctx, "srs remove room ok, uuid=%v", uuid)
return nil
}(); err != nil {
ohttp.WriteError(ctx, w, r, err)
}
})

return nil
}

type SrsLiveRoom struct {
// Live room UUID.
UUID string `json:"uuid"`
// Live room title.
Title string `json:"title"`
// Live room secret.
Secret string `json:"secret"`
// Create time.
CreatedAt string `json:"created_at"`
}

func (v *SrsLiveRoom) String() string {
return fmt.Sprintf("uuid=%v, title=%v, secret=%v", v.UUID, v.Title, v.Secret)
}
4 changes: 4 additions & 0 deletions platform/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ func handleHTTPService(ctx context.Context, handler *http.ServeMux) error {
return errors.Wrapf(err, "handle hooks")
}

if err := handleLiveRoomService(ctx, handler); err != nil {
return errors.Wrapf(err, "handle live room")
}

var ep string

handleHostVersions(ctx, handler)
Expand Down
38 changes: 30 additions & 8 deletions platform/srs-hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,8 @@ func handleHooksService(ctx context.Context, handler *http.ServeMux) error {
return errors.Wrapf(err, "json unmarshal %v", string(b))
}

verifiedBy := "noVerify"
if action == SrsActionOnPublish {
publish, err := rdb.HGet(ctx, SRS_AUTH_SECRET, "pubSecret").Result()
if err != nil && err != redis.Nil {
return errors.Wrapf(err, "hget %v pubSecret", SRS_AUTH_SECRET)
}

// Note that we allow pass secret by params or in stream name, for example, some encoder does not support params
// with ?secret=xxx, so it will fail when url is:
// rtmp://ip/live/livestream?secret=xxx
Expand All @@ -110,8 +106,33 @@ func handleHooksService(ctx context.Context, handler *http.ServeMux) error {
// or simply use secret as stream:
// rtmp://ip/live/xxx
// in this situation, the secret is part of stream name.
if publish != "" && !strings.Contains(streamObj.Param, publish) && !strings.Contains(streamObj.Stream, publish) {
return errors.Errorf("invalid stream=%v, param=%v, action=%v", streamObj.Stream, streamObj.Param, action)
isSecretOK := func(publish, stream, param string) bool {
return publish == "" || strings.Contains(param, publish) || strings.Contains(stream, publish)
}

// Use live room secret to verify if stream name matches.
if r0, err := rdb.HGet(ctx, SRS_LIVE_ROOM, streamObj.Stream).Result(); (err == nil || err == redis.Nil) && r0 != "" {
var room SrsLiveRoom
if err = json.Unmarshal([]byte(r0), &room); err != nil {
return errors.Wrapf(err, "unmarshal %v %v", streamObj.Stream, r0)
}

if !isSecretOK(room.Secret, streamObj.Stream, streamObj.Param) {
return errors.Errorf("invalid live room stream=%v, param=%v, action=%v", streamObj.Stream, streamObj.Param, action)
}

verifiedBy = "LiveRoom"
} else {
// Use global publish secret to verify
publish, err := rdb.HGet(ctx, SRS_AUTH_SECRET, "pubSecret").Result()
if err != nil && err != redis.Nil {
return errors.Wrapf(err, "hget %v pubSecret", SRS_AUTH_SECRET)
}
if !isSecretOK(publish, streamObj.Stream, streamObj.Param) {
return errors.Errorf("invalid normal stream=%v, param=%v, action=%v", streamObj.Stream, streamObj.Param, action)
}

verifiedBy = "NormalStream"
}
}

Expand Down Expand Up @@ -176,7 +197,8 @@ func handleHooksService(ctx context.Context, handler *http.ServeMux) error {
}

ohttp.WriteData(ctx, w, r, nil)
logger.Tf(ctx, "srs hooks ok, action=%v, %v, %v", action, streamObj.String(), requestBody)
logger.Tf(ctx, "srs hooks ok, action=%v, verifiedBy=%v, %v, %v",
action, verifiedBy, streamObj.String(), requestBody)
return nil
}(); err != nil {
ohttp.WriteError(ctx, w, r, err)
Expand Down
2 changes: 2 additions & 0 deletions platform/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,8 @@ const (
SRS_STAT_COUNTER = "SRS_STAT_COUNTER"
// For container and images.
SRS_CONTAINER_DISABLED = "SRS_CONTAINER_DISABLED"
// For live stream and rooms.
SRS_LIVE_ROOM = "SRS_LIVE_ROOM"
// For system settings.
SRS_LOCALE = "SRS_LOCALE"
SRS_SECRET_PUBLISH = "SRS_SECRET_PUBLISH"
Expand Down
1 change: 1 addition & 0 deletions test/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module test
go 1.16

require (
github.com/google/uuid v1.5.0 // indirect
github.com/joho/godotenv v1.5.1
github.com/ossrs/go-oryx-lib v0.0.9
)
2 changes: 2 additions & 0 deletions test/go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/ossrs/go-oryx-lib v0.0.9 h1:piZkzit/1hqAcXP31/mvDEDpHVjCmBMmvzF3hN8hUuQ=
Expand Down
Loading

0 comments on commit 2c5123e

Please sign in to comment.