Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
52 changes: 52 additions & 0 deletions cmd/oceanbench/api_v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"strings"
"time"

"github.com/ausocean/cloud/cmd/oceantv/broadcasthost"
"github.com/ausocean/cloud/datastore"
"github.com/ausocean/cloud/gauth"
"github.com/ausocean/cloud/model"
Expand All @@ -60,6 +61,8 @@ func setupAPIV1Routes(api fiber.Router) {
v1.Get("/logs/:mac", getV1LogsByDevice)
v1.Put("/log/:mac", putV1LogByDevice)

v1.Get("/broadcast/stream", getV1BroadcastStreamHandler)

// Endpoints with site scope.
v1.Group("/:"+skeyParamKey).
Get("/broadcasts/", getV1BroadcastIDHandler).
Expand Down Expand Up @@ -501,6 +504,55 @@ func getV1BroadcastHandler(c *fiber.Ctx) error {
return c.JSON(cfg)
}

// getV1BroadcastStreamHandler handles GET /api/v1/broadcast/stream?uuid=<uuid>.
// It returns the HLS playlist URL for an OceanMedia broadcast and a viewer
// token scoped to that broadcast, minted with the shared OceanMedia secret.
func getV1BroadcastStreamHandler(c *fiber.Ctx) error {
Comment thread
elliot-ausocean marked this conversation as resolved.
Resolved
uuid := c.Query("uuid")
if uuid == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "uuid query parameter is required"})
}

ctx := c.UserContext()
p := c.Locals(profileKey).(*gauth.Profile)

broadcastVar, err := model.GetBroadcastVarByUUID(ctx, settingsStore, uuid)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "broadcast not found"})
}
if !isAdmin(ctx, broadcastVar.Skey, p.Email) {
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "insufficient permission, requires admin permission"})
}

cfg, err := broadcastFromVars([]model.Variable{*broadcastVar}, uuid)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "broadcast not found"})
}
if cfg.BroadcastHost != broadcasthost.OceanMediaHostName || cfg.BID == "" {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "broadcast is not an OceanMedia stream"})
}
if len(oceanMediaJWTSecret) == 0 {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "OceanMedia is not configured"})
}

now := time.Now()
token, err := gauth.PutClaims(map[string]interface{}{
"sub": cfg.BID,
"iss": projectID,
"iat": now.Unix(),
"exp": now.Add(oceanMediaTokenTTL).Unix(),
}, oceanMediaJWTSecret)
if err != nil {
log.Printf("could not create OceanMedia token for broadcast %s: %v", cfg.BID, err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "could not create stream token"})
}

return c.JSON(fiber.Map{
"url": mediaURL + "/" + cfg.BID + ".m3u8",
"token": token,
})
}

func getV1BroadcastIDHandler(c *fiber.Ctx) error {
skParam := c.Params(skeyParamKey)
if skParam == "" {
Expand Down
31 changes: 25 additions & 6 deletions cmd/oceanbench/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import (
"strconv"
"strings"
"sync"
"time"

"github.com/ausocean/cloud/backend"
"github.com/ausocean/cloud/datastore"
Expand All @@ -91,12 +92,17 @@ const (
)

const (
projectID = "oceanbench"
oauthClientID = "802166617157-v67emnahdpvfuc13ijiqb7qm3a7sf45b.apps.googleusercontent.com"
oauthMaxAge = 60 * 60 * 24 * 7 // 7 days
tvServiceURL = "https://tv.cloudblue.org"
cronServiceURL = "https://cron.cloudblue.org"
cronServiceAccount = "[email protected]"
projectID = "oceanbench"
oauthClientID = "802166617157-v67emnahdpvfuc13ijiqb7qm3a7sf45b.apps.googleusercontent.com"
oauthMaxAge = 60 * 60 * 24 * 7 // 7 days
tvServiceURL = "https://tv.cloudblue.org"
cronServiceURL = "https://cron.cloudblue.org"
oceanMediaServiceURL = "https://media.cloudblue.org"
cronServiceAccount = "[email protected]"

// oceanMediaTokenTTL is the lifetime of viewer tokens minted for
// OceanMedia playlists.
oceanMediaTokenTTL = 12 * time.Hour
)

// page defines one page of the web app.
Expand Down Expand Up @@ -147,8 +153,13 @@ var (
auth *gauth.UserAuth
channelAuth *ytclient.ChannelAuth
tvURL = tvServiceURL
mediaURL = oceanMediaServiceURL
storePath string
testDataFile string

// oceanMediaJWTSecret is the shared secret used to mint viewer tokens
// for OceanMedia playlists. It must match OceanMedia's jwtSecret.
oceanMediaJWTSecret []byte
)

var (
Expand Down Expand Up @@ -235,6 +246,7 @@ func main() {
flag.IntVar(&port, "port", defaultPort, "Port we listen on in standalone mode")
flag.StringVar(&cronURL, "cronurl", cronServiceURL, "Cron service URL")
flag.StringVar(&tvURL, "tvurl", tvServiceURL, "TV service URL")
flag.StringVar(&mediaURL, "mediaurl", oceanMediaServiceURL, "OceanMedia service URL")
flag.StringVar(&storePath, "filestore", "store", "File store path")
flag.StringVar(&testDataFile, "testdata", "", "Path to a JSON file to populate the datastore")
flag.Parse()
Expand Down Expand Up @@ -266,6 +278,13 @@ func main() {
log.Printf("could not get cronSecret: %v", err)
}

// Shared secret used to mint OceanMedia viewer tokens.
if s, err := gauth.GetSecret(ctx, projectID, "oceanMediaJWTSecret"); err != nil {
log.Printf("could not get oceanMediaJWTSecret: %v", err)
} else {
oceanMediaJWTSecret = []byte(s)
}

// Warmup handler.
app.Get("/_ah/warmup", func(c *fiber.Ctx) error {
log.Println("warmup request received, version: " + version)
Expand Down
209 changes: 208 additions & 1 deletion cmd/oceanbench/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading