add room closing

pull/6/head
Namekuji 2022-12-04 20:11:44 -05:00
rodzic 8865aaefaf
commit 7b8457d509
7 zmienionych plików z 209 dodań i 30 usunięć

1
go.mod
Wyświetl plik

@ -17,6 +17,7 @@ require (
)
require (
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect

1
go.sum
Wyświetl plik

@ -15,6 +15,7 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frostbyte73/go-throttle v0.0.0-20210621200530-8018c891361d h1:rvSueMilKro0jF+VfxoVR42wazKPl+cUBL3rFbiBGso=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=

Wyświetl plik

@ -83,7 +83,7 @@ func oauthHandler(c echo.Context) (err error) {
if errMsg := c.QueryParam("error"); errMsg == "access_denied" {
return c.Redirect(http.StatusFound, "/login")
}
return echo.NewHTTPError(http.StatusBadRequest, "authentication code needed")
return echo.NewHTTPError(http.StatusBadRequest, "auth_code_required")
}
data, err := getSessionData(c)
@ -153,7 +153,9 @@ func authMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
}
if data.AudonID != "" {
if _, err := findUserByID(c.Request().Context(), data.AudonID); err == nil {
if user, err := findUserByID(c.Request().Context(), data.AudonID); err == nil {
c.Set("user", user)
c.Set("session", data)
return next(c)
}
}

151
room.go
Wyświetl plik

@ -1,22 +1,25 @@
package main
import (
"context"
"errors"
"net/http"
"time"
"github.com/jaevor/go-nanoid"
"github.com/labstack/echo/v4"
"github.com/livekit/protocol/auth"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)
// handler for POST to /api/room
func createRoomHandler(c echo.Context) (err error) {
func createRoomHandler(c echo.Context) error {
room := new(Room)
if err = c.Bind(room); err != nil {
if err := c.Bind(room); err != nil {
return ErrInvalidRequestFormat
}
if err = mainValidator.StructExcept(room, "RoomID"); err != nil { // New RoomID will be created, so one in request doesn't matter
if err := mainValidator.StructExcept(room, "RoomID"); err != nil { // New RoomID will be created, so one in request doesn't matter
return wrapValidationError(err)
}
@ -26,27 +29,15 @@ func createRoomHandler(c echo.Context) (err error) {
}
room.RoomID = canonic()
sessData, err := getSessionData(c)
if err != nil {
return err
}
var host *AudonUser
host, err = findUserByID(c.Request().Context(), sessData.AudonID)
if err == mongo.ErrNoDocuments {
return c.JSON(http.StatusNotFound, []string{sessData.AudonID})
} else if err != nil {
c.Logger().Error(err)
return echo.NewHTTPError(http.StatusInternalServerError)
}
host := c.Get("user").(*AudonUser)
room.Host = host
now := time.Now().UTC()
if now.Sub(room.ScheduledAt) > 0 {
if now.After(room.ScheduledAt) {
room.ScheduledAt = now
}
// If CoHosts are already registered, retrieve their data from DB
// if cohosts are already registered, retrieve their data from DB
for i, cohost := range room.CoHost {
cohostUser, err := findUserByRemote(c.Request().Context(), cohost.RemoteID, cohost.RemoteURL)
if err == nil {
@ -64,13 +55,129 @@ func createRoomHandler(c echo.Context) (err error) {
return c.String(http.StatusCreated, room.RoomID)
}
func getHostToken(room *Room) (string, error) {
func joinRoomHandler(c echo.Context) error {
roomID := c.Param("id")
if err := mainValidator.Var(&roomID, "required,printascii"); err != nil {
return wrapValidationError(err)
}
user := c.Get("user").(*AudonUser)
room, err := findRoomByID(c.Request().Context(), roomID)
if err != nil {
return echo.NewHTTPError(http.StatusNotFound)
}
now := time.Now().UTC()
// check if room is not yet started
if room.ScheduledAt.After(now) {
return echo.NewHTTPError(http.StatusConflict, "not_yet_started")
}
// check if room has already ended
if !room.EndedAt.IsZero() && room.EndedAt.Before(now) {
return echo.NewHTTPError(http.StatusGone, "already_ended")
}
// when host or cohost joins
if room.IsHost(user) || room.IsCoHost(user) {
token, err := getRoomToken(room, user.AudonID, true) // host and cohost can talk from the beginning
if err != nil {
c.Logger().Error(err)
return echo.NewHTTPError(http.StatusInternalServerError)
}
return c.JSON(http.StatusOK, token)
}
// return 403 if one has been kicked
for _, kicked := range room.Kicked {
if kicked.Equal(user) {
return echo.NewHTTPError(http.StatusForbidden)
}
}
// when one is neither host nor cohost
token, err := getRoomToken(room, user.AudonID, false) // listener needs a permission to talk
if err != nil {
c.Logger().Error(err)
return echo.NewHTTPError(http.StatusInternalServerError)
}
return c.JSON(http.StatusOK, token)
}
// intended to be called by room's host
func closeRoomHandler(c echo.Context) error {
roomID := c.Param("id")
if err := mainValidator.Var(&roomID, "required,printascii"); err != nil {
return wrapValidationError(err)
}
// retrieve room info from the given room ID
room, err := findRoomByID(c.Request().Context(), roomID)
if err == mongo.ErrNoDocuments {
return c.String(http.StatusNotFound, "room_not_found")
} else if err != nil {
c.Logger().Error(err)
return echo.NewHTTPError(http.StatusInternalServerError)
}
// only host can close the room
user := c.Get("user").(*AudonUser)
if !room.IsHost(user) {
return c.String(http.StatusForbidden, "must_be_host")
}
if err := endRoom(c.Request().Context(), room); err != nil {
c.Logger().Error(err)
return echo.NewHTTPError(http.StatusInternalServerError)
}
return c.NoContent(http.StatusOK)
}
func getRoomToken(room *Room, identity string, canTalk bool) (string, error) {
at := auth.NewAccessToken(mainConfig.Livekit.APIKey, mainConfig.Livekit.APISecret)
grant := &auth.VideoGrant{
Room: room.RoomID,
RoomJoin: true,
Room: room.RoomID,
RoomJoin: true,
CanPublish: &canTalk,
}
at.AddGrant(grant).SetIdentity(room.Host.AudonID).SetValidFor(10 * time.Minute)
at.AddGrant(grant).SetIdentity(identity).SetValidFor(10 * time.Minute)
return at.ToJWT()
}
func findRoomByID(ctx context.Context, roomID string) (*Room, error) {
var room Room
collRoom := mainDB.Collection(COLLECTION_ROOM)
if err := collRoom.FindOne(ctx, bson.D{{Key: "room_id", Value: roomID}}).Decode(&room); err != nil {
return nil, err
}
return &room, nil
}
func endRoom(ctx context.Context, room *Room) error {
if room == nil {
return errors.New("room cannot be nil")
}
if !room.EndedAt.IsZero() {
return nil
}
now := time.Now().UTC()
collRoom := mainDB.Collection(COLLECTION_ROOM)
if _, err := collRoom.UpdateOne(ctx,
bson.D{{Key: "room_id", Value: room.RoomID}},
bson.D{
{Key: "$set", Value: bson.D{{Key: "ended_at", Value: now}}},
}); err != nil {
return err
}
return nil
}

Wyświetl plik

@ -33,7 +33,9 @@ type (
FollowingOnly bool `bson:"following_only" json:"following_only"`
FollowerOnly bool `bson:"follower_only" json:"follower_only"`
MutualOnly bool `bson:"mutual_only" json:"mutual_only"`
Kicked []*AudonUser `bson:"kicked" json:"kicked"`
ScheduledAt time.Time `bson:"scheduled_at" json:"scheduled_at"`
EndedAt time.Time `bson:"ended_at" json:"ended_at"`
CreatedAt time.Time `bson:"created_at" json:"created_at"`
}
)
@ -43,6 +45,32 @@ const (
COLLECTION_ROOM = "room"
)
func (a *AudonUser) Equal(u *AudonUser) bool {
if a == nil {
return false
}
return a.AudonID == u.AudonID || (a.RemoteID == u.RemoteID && a.RemoteURL == u.RemoteURL)
}
func (r *Room) IsCoHost(u *AudonUser) bool {
if r == nil {
return false
}
for _, cohost := range r.CoHost {
if cohost.Equal(u) {
return true
}
}
return false
}
func (r *Room) IsHost(u *AudonUser) bool {
return r != nil && r.Host.Equal(u)
}
func createIndexes(ctx context.Context) error {
userColl := mainDB.Collection(COLLECTION_USER)
userIndexes, err := userColl.Indexes().ListSpecifications(ctx)

Wyświetl plik

@ -3,7 +3,6 @@ package main
import (
"context"
"encoding/gob"
"errors"
"html/template"
"io"
"log"
@ -34,11 +33,10 @@ type (
)
var (
err_invalid_cookie error = errors.New("invalid cookie")
mastAppConfigBase *mastodon.AppConfig = nil
mainDB *mongo.Database = nil
mainValidator = validator.New()
mainConfig *AppConfig
mastAppConfigBase *mastodon.AppConfig = nil
mainDB *mongo.Database = nil
mainValidator = validator.New()
mainConfig *AppConfig
)
func init() {
@ -72,6 +70,7 @@ func main() {
os.Exit(3)
}
// Setup echo server
e := echo.New()
defer e.Close()
@ -80,6 +79,8 @@ func main() {
}
e.Renderer = t
e.Validator = &CustomValidator{validator: mainValidator}
// Setup session middleware (currently Audon stores all client data in cookie)
cookieStore := sessions.NewCookieStore([]byte(mainConfig.SeesionSecret))
cookieStore.Options = &sessions.Options{
Path: "/",
@ -106,6 +107,10 @@ func main() {
api := e.Group("/api", authMiddleware)
api.POST("/room", createRoomHandler)
api.GET("/room/:id", joinRoomHandler)
api.DELETE("/room/:id", closeRoomHandler)
e.POST("/app/webhook", livekitWebhookHandler)
e.Logger.Debug(e.Start(":1323"))
}

35
webhooks.go 100644
Wyświetl plik

@ -0,0 +1,35 @@
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/livekit/protocol/auth"
"github.com/livekit/protocol/webhook"
)
func livekitWebhookHandler(c echo.Context) error {
authProvider := auth.NewSimpleKeyProvider(mainConfig.Livekit.APIKey, mainConfig.Livekit.APISecret)
event, err := webhook.ReceiveWebhookEvent(c.Request(), authProvider)
if err == webhook.ErrNoAuthHeader {
return echo.NewHTTPError(http.StatusForbidden)
}
if event.GetEvent() == webhook.EventRoomFinished {
roomID := event.GetRoom().GetName()
if err := mainValidator.Var(&roomID, "required,printascii"); err == nil {
room, err := findRoomByID(c.Request().Context(), roomID)
if err == nil {
if err := endRoom(c.Request().Context(), room); err != nil {
c.Logger().Error(err)
return echo.NewHTTPError(http.StatusInternalServerError)
}
}
}
return c.NoContent(http.StatusOK)
}
return echo.NewHTTPError(http.StatusNotFound)
}