diff --git a/server/handlers/init.go b/server/handlers/init.go index 93f8a56..1ed538a 100644 --- a/server/handlers/init.go +++ b/server/handlers/init.go @@ -9,6 +9,7 @@ import ( "github.com/synctv-org/synctv/internal/conf" "github.com/synctv-org/synctv/public" "github.com/synctv-org/synctv/room" + "github.com/synctv-org/synctv/server/middlewares" "github.com/synctv-org/synctv/utils" rtmps "github.com/zijiren233/livelib/server" ) @@ -54,6 +55,9 @@ func Init(e *gin.Engine, s *rtmps.Server, r *room.Rooms) { { api := e.Group("/api") + needAuthApi := api.Group("") + needAuthApi.Use(middlewares.AuthRoom) + { public := api.Group("/public") @@ -62,6 +66,7 @@ func Init(e *gin.Engine, s *rtmps.Server, r *room.Rooms) { { room := api.Group("/room") + needAuthRoom := needAuthApi.Group("/room") room.GET("/ws", NewWebSocketHandler(utils.NewWebSocketServer())) @@ -75,42 +80,43 @@ func Init(e *gin.Engine, s *rtmps.Server, r *room.Rooms) { room.POST("/login", LoginRoom) - room.POST("/delete", DeleteRoom) + needAuthRoom.POST("/delete", DeleteRoom) - room.POST("/pwd", SetPassword) + needAuthRoom.POST("/pwd", SetPassword) - room.PUT("/admin", AddAdmin) + needAuthRoom.PUT("/admin", AddAdmin) - room.DELETE("/admin", DelAdmin) + needAuthRoom.DELETE("/admin", DelAdmin) } { movie := api.Group("/movie") + needAuthMovie := needAuthApi.Group("/movie") - movie.GET("/list", MovieList) + needAuthMovie.GET("/list", MovieList) - movie.GET("/movies", Movies) + needAuthMovie.GET("/current", CurrentMovie) - movie.GET("/current", CurrentMovie) + needAuthMovie.GET("/movies", Movies) - movie.POST("/current", ChangeCurrentMovie) + needAuthMovie.POST("/current", ChangeCurrentMovie) - movie.POST("/push", PushMovie) + needAuthMovie.POST("/push", PushMovie) - movie.POST("/edit", EditMovie) + needAuthMovie.POST("/edit", EditMovie) - movie.POST("/swap", SwapMovie) + needAuthMovie.POST("/swap", SwapMovie) - movie.POST("/delete", DelMovie) + needAuthMovie.POST("/delete", DelMovie) - movie.POST("/clear", ClearMovies) + needAuthMovie.POST("/clear", ClearMovies) movie.HEAD("/proxy/:roomId/:pullKey", ProxyMovie) movie.GET("/proxy/:roomId/:pullKey", ProxyMovie) { - live := movie.Group("/live") + live := needAuthMovie.Group("/live") live.POST("/publishKey", NewPublishKey) @@ -119,11 +125,12 @@ func Init(e *gin.Engine, s *rtmps.Server, r *room.Rooms) { } { - user := api.Group("/user") + // user := api.Group("/user") + needAuthUser := needAuthApi.Group("/user") - user.GET("/me", Me) + needAuthUser.GET("/me", Me) - user.POST("/pwd", SetUserPassword) + needAuthUser.POST("/pwd", SetUserPassword) } } diff --git a/server/handlers/movie.go b/server/handlers/movie.go index 78f3f6a..c590496 100644 --- a/server/handlers/movie.go +++ b/server/handlers/movie.go @@ -20,6 +20,7 @@ import ( pb "github.com/synctv-org/synctv/proto" "github.com/synctv-org/synctv/proxy" "github.com/synctv-org/synctv/room" + "github.com/synctv-org/synctv/server/model" "github.com/synctv-org/synctv/utils" "github.com/zijiren233/livelib/av" "github.com/zijiren233/livelib/container/flv" @@ -45,22 +46,17 @@ func GetPageItems[T any](ctx *gin.Context, items []T) ([]T, error) { } func MovieList(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } + user := ctx.Value("user").(*room.User) ml := user.Movies() movies, err := GetPageItems(ctx, ml) if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } - ctx.JSON(http.StatusOK, NewApiDataResp(gin.H{ + ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "current": user.Room().Current(), "total": len(ml), "movies": movies, @@ -68,35 +64,25 @@ func MovieList(ctx *gin.Context) { } func CurrentMovie(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } + user := ctx.Value("user").(*room.User) - ctx.JSON(http.StatusOK, NewApiDataResp(gin.H{ + ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "current": user.Room().Current(), })) } func Movies(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } + user := ctx.Value("user").(*room.User) ml := user.Movies() movies, err := GetPageItems(ctx, ml) if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } - ctx.JSON(http.StatusOK, NewApiDataResp(gin.H{ + ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "total": len(ml), "movies": movies, })) @@ -105,52 +91,47 @@ func Movies(ctx *gin.Context) { type PushMovieReq = room.BaseMovieInfo func PushMovie(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } + user := ctx.Value("user").(*room.User) req := new(PushMovieReq) if err := json.NewDecoder(ctx.Request.Body).Decode(req); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } movie, err := user.NewMovieWithBaseMovie(*req) if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } switch { case movie.RtmpSource && movie.Proxy: - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorStringResp("rtmp source and proxy can not be true at the same time")) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("rtmp source and proxy can not be true at the same time")) return case movie.Live && movie.RtmpSource: if !conf.Conf.Rtmp.Enable { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorStringResp("rtmp source is not enabled")) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("rtmp source is not enabled")) return } else if movie.Type == "m3u8" && !conf.Conf.Rtmp.HlsPlayer { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorStringResp("hls player is not enabled")) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("hls player is not enabled")) return } movie.PullKey = uuid.New().String() c, err := user.Room().NewLiveChannel(movie.PullKey) if err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } movie.SetChannel(c) case movie.Live && movie.Proxy: if !conf.Conf.Proxy.LiveProxy { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorStringResp("live proxy is not enabled")) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("live proxy is not enabled")) return } u, err := url.Parse(movie.Url) if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } switch u.Scheme { @@ -158,7 +139,7 @@ func PushMovie(ctx *gin.Context) { PullKey := uuid.New().String() c, err := user.Room().NewLiveChannel(PullKey) if err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } movie.PullKey = PullKey @@ -184,7 +165,7 @@ func PushMovie(ctx *gin.Context) { PullKey := uuid.New().String() c, err := user.Room().NewLiveChannel(PullKey) if err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } movie.PullKey = PullKey @@ -209,15 +190,15 @@ func PushMovie(ctx *gin.Context) { } }() default: - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorStringResp("only support rtmp temporarily")) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("only support rtmp temporarily")) return } case !movie.Live && movie.RtmpSource: - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorStringResp("rtmp source must be live")) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("rtmp source must be live")) return case !movie.Live && movie.Proxy: if !conf.Conf.Proxy.MovieProxy { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorStringResp("movie proxy is not enabled")) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("movie proxy is not enabled")) return } movie.PullKey = uuid.New().String() @@ -225,15 +206,15 @@ func PushMovie(ctx *gin.Context) { case !movie.Live && !movie.Proxy, movie.Live && !movie.Proxy && !movie.RtmpSource: u, err := url.Parse(movie.Url) if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } if u.Scheme != "http" && u.Scheme != "https" { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorStringResp("only support http or https")) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("only support http or https")) return } default: - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorStringResp("unknown error")) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("unknown error")) return } @@ -247,7 +228,7 @@ func PushMovie(ctx *gin.Context) { err = FormatErrNotSupportPosition(s) } if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } @@ -257,32 +238,27 @@ func PushMovie(ctx *gin.Context) { Sender: user.Name(), }, }, room.WithSendToSelf()); err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } - ctx.JSON(http.StatusCreated, NewApiDataResp(gin.H{ + ctx.JSON(http.StatusCreated, model.NewApiDataResp(gin.H{ "id": movie.Id(), })) } func NewPublishKey(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatus(http.StatusUnauthorized) - return - } + user := ctx.Value("user").(*room.User) req := new(IdReq) if err := json.NewDecoder(ctx.Request.Body).Decode(req); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } movie, err := user.Room().GetMovie(req.Id) if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } @@ -292,18 +268,18 @@ func NewPublishKey(ctx *gin.Context) { } if !movie.RtmpSource { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorStringResp("only live movie can get publish key")) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("only live movie can get publish key")) return } if movie.PullKey == "" { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorStringResp("pull key is empty")) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("pull key is empty")) return } token, err := NewRtmpAuthorization(movie.PullKey) if err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } @@ -312,7 +288,7 @@ func NewPublishKey(ctx *gin.Context) { host = ctx.Request.Host } - ctx.JSON(http.StatusOK, NewApiDataResp(gin.H{ + ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "host": host, "app": user.Room().ID(), "token": token, @@ -325,21 +301,16 @@ type EditMovieReq struct { } func EditMovie(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } + user := ctx.Value("user").(*room.User) req := new(EditMovieReq) if err := json.NewDecoder(ctx.Request.Body).Decode(req); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } if err := user.EditMovie(req.Id, req.BaseMovieInfo); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } @@ -349,7 +320,7 @@ func EditMovie(ctx *gin.Context) { Sender: user.Name(), }, }, room.WithSendToSelf()); err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } @@ -361,21 +332,16 @@ type IdsReq struct { } func DelMovie(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } + user := ctx.Value("user").(*room.User) req := new(IdsReq) if err := json.NewDecoder(ctx.Request.Body).Decode(req); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } if err := user.Room().DelMovie(req.Ids...); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } @@ -385,7 +351,7 @@ func DelMovie(ctx *gin.Context) { Sender: user.Name(), }, }, room.WithSendToSelf()); err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } @@ -393,15 +359,10 @@ func DelMovie(ctx *gin.Context) { } func ClearMovies(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } + user := ctx.Value("user").(*room.User) if err := user.Room().ClearMovies(); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } @@ -411,7 +372,7 @@ func ClearMovies(ctx *gin.Context) { Sender: user.Name(), }, }, room.WithSendToSelf()); err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } @@ -424,21 +385,16 @@ type SwapMovieReq struct { } func SwapMovie(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } + user := ctx.Value("user").(*room.User) req := new(SwapMovieReq) if err := json.NewDecoder(ctx.Request.Body).Decode(req); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } if err := user.Room().SwapMovie(req.Id1, req.Id2); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } @@ -448,7 +404,7 @@ func SwapMovie(ctx *gin.Context) { Sender: user.Name(), }, }, room.WithSendToSelf()); err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } @@ -460,21 +416,16 @@ type IdReq struct { } func ChangeCurrentMovie(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } + user := ctx.Value("user").(*room.User) req := new(IdReq) if err := json.NewDecoder(ctx.Request.Body).Decode(req); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } if err := user.Room().ChangeCurrentMovie(req.Id); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } if err := user.Broadcast(&room.ElementMessage{ @@ -484,7 +435,7 @@ func ChangeCurrentMovie(ctx *gin.Context) { Current: user.Room().Current().Proto(), }, }, room.WithSendToSelf()); err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } @@ -532,23 +483,23 @@ func ProxyMovie(ctx *gin.Context) { rooms := ctx.Value("rooms").(*room.Rooms) roomId := ctx.Param("roomId") if roomId == "" { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorStringResp("roomId is empty")) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("roomId is empty")) return } room, err := rooms.GetRoom(roomId) if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } m, err := room.GetMovieWithPullKey(ctx.Param("pullKey")) if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } if !m.Proxy || m.Live || m.RtmpSource { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorStringResp("not support proxy")) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support proxy")) return } @@ -559,13 +510,13 @@ func ProxyMovie(ctx *gin.Context) { } resp, err := r.Head(m.Url) if err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } defer resp.RawBody().Close() if _, ok := allowedProxyMovieContentType[resp.Header().Get("Content-Type")]; !ok { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(fmt.Errorf("this movie type support proxy: %s", resp.Header().Get("Content-Type")))) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(fmt.Errorf("this movie type support proxy: %s", resp.Header().Get("Content-Type")))) return } ctx.Status(resp.StatusCode()) @@ -576,7 +527,7 @@ func ProxyMovie(ctx *gin.Context) { length, err := strconv.ParseInt(l, 10, 64) if err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } @@ -602,16 +553,11 @@ func (e FormatErrNotSupportFileType) Error() string { } func JoinLive(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) if !conf.Conf.Proxy.LiveProxy && !conf.Conf.Rtmp.Enable { - ctx.AbortWithStatusJSON(http.StatusForbidden, NewApiErrorStringResp("live proxy and rtmp source is not enabled")) - return - } - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("live proxy and rtmp source is not enabled")) return } + user := ctx.Value("user").(*room.User) pullKey := strings.Trim(ctx.Param("pullKey"), "/") pullKeySplitd := strings.Split(pullKey, "/") @@ -620,7 +566,7 @@ func JoinLive(ctx *gin.Context) { channelName := strings.TrimSuffix(fileName, fileExt) m, err := user.Room().GetMovieWithPullKey(channelName) if err != nil { - ctx.AbortWithStatusJSON(http.StatusNotFound, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err)) return } channel := m.Channel() @@ -635,20 +581,20 @@ func JoinLive(ctx *gin.Context) { ctx.Header("Cache-Control", "no-store") b, err := channel.GenM3U8PlayList(fmt.Sprintf("/api/movie/live/%s", channelName)) if err != nil { - ctx.AbortWithStatusJSON(http.StatusNotFound, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err)) return } ctx.Data(http.StatusOK, hls.M3U8ContentType, b.Bytes()) case ".ts": b, err := channel.GetTsFile(pullKeySplitd[1]) if err != nil { - ctx.AbortWithStatusJSON(http.StatusNotFound, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err)) return } ctx.Header("Cache-Control", "public, max-age=90") ctx.Data(http.StatusOK, hls.TSContentType, b) default: ctx.Header("Cache-Control", "no-store") - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(FormatErrNotSupportFileType(fileExt))) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(FormatErrNotSupportFileType(fileExt))) } } diff --git a/server/handlers/public.go b/server/handlers/public.go index c6e6382..1e71f8f 100644 --- a/server/handlers/public.go +++ b/server/handlers/public.go @@ -3,10 +3,11 @@ package handlers import ( "github.com/gin-gonic/gin" "github.com/synctv-org/synctv/internal/conf" + "github.com/synctv-org/synctv/server/model" ) func Settings(ctx *gin.Context) { - ctx.JSON(200, NewApiDataResp(gin.H{ + ctx.JSON(200, model.NewApiDataResp(gin.H{ "rtmp": gin.H{ "enable": conf.Conf.Rtmp.Enable, "rtmpPlayer": conf.Conf.Rtmp.RtmpPlayer, diff --git a/server/handlers/room.go b/server/handlers/room.go index 11facc5..50fe5ff 100644 --- a/server/handlers/room.go +++ b/server/handlers/room.go @@ -5,21 +5,19 @@ import ( "fmt" "net/http" "strconv" - "strings" "time" json "github.com/json-iterator/go" log "github.com/sirupsen/logrus" "github.com/gin-gonic/gin" - "github.com/golang-jwt/jwt/v5" "github.com/maruel/natural" - "github.com/synctv-org/synctv/internal/conf" pb "github.com/synctv-org/synctv/proto" "github.com/synctv-org/synctv/room" + "github.com/synctv-org/synctv/server/middlewares" + "github.com/synctv-org/synctv/server/model" "github.com/zijiren233/gencontainer/vec" rtmps "github.com/zijiren233/livelib/server" - "github.com/zijiren233/stream" ) var ( @@ -34,94 +32,6 @@ func (e FormatErrNotSupportPosition) Error() string { return fmt.Sprintf("not support position %s", string(e)) } -type AuthClaims struct { - RoomID string `json:"id"` - Version uint64 `json:"v"` - Username string `json:"un"` - UserVersion uint64 `json:"uv"` - jwt.RegisteredClaims -} - -func AuthRoom(Authorization string, rooms *room.Rooms) (*room.User, error) { - t, err := jwt.ParseWithClaims(strings.TrimPrefix(Authorization, `Bearer `), &AuthClaims{}, func(token *jwt.Token) (any, error) { - return stream.StringToBytes(conf.Conf.Jwt.Secret), nil - }) - if err != nil { - return nil, ErrAuthFailed - } - claims := t.Claims.(*AuthClaims) - - r, err := rooms.GetRoom(claims.RoomID) - if err != nil { - return nil, err - } - - if !r.CheckVersion(claims.Version) { - return nil, ErrAuthExpired - } - - user, err := r.GetUser(claims.Username) - if err != nil { - return nil, err - } - - if !user.CheckVersion(claims.UserVersion) { - return nil, ErrAuthExpired - } - - return user, nil -} - -func authWithPassword(rooms *room.Rooms, roomid, password, username, userPassword string) (*room.User, error) { - r, err := rooms.GetRoom(roomid) - if err != nil { - return nil, err - } - if !r.CheckPassword(password) { - return nil, ErrAuthFailed - } - user, err := r.GetUser(username) - if err != nil { - return nil, err - } - if !user.CheckPassword(userPassword) { - return nil, ErrAuthFailed - } - return user, nil -} - -func authOrNewWithPassword(rooms *room.Rooms, roomid, password, username, userPassword string, conf ...room.UserConf) (*room.User, error) { - r, err := rooms.GetRoom(roomid) - if err != nil { - return nil, err - } - if !r.CheckPassword(password) { - return nil, ErrAuthFailed - } - user, err := r.GetOrNewUser(username, userPassword, conf...) - if err != nil { - return nil, err - } - if !user.CheckPassword(userPassword) { - return nil, ErrAuthFailed - } - return user, nil -} - -func newAuthorization(user *room.User) (string, error) { - claims := &AuthClaims{ - RoomID: user.Room().ID(), - Version: user.Room().Version(), - Username: user.Name(), - UserVersion: user.Version(), - RegisteredClaims: jwt.RegisteredClaims{ - NotBefore: jwt.NewNumericDate(time.Now()), - ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * time.Duration(conf.Conf.Jwt.Expire))), - }, - } - return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(stream.StringToBytes(conf.Conf.Jwt.Secret)) -} - type CreateRoomReq struct { RoomID string `json:"roomId"` Password string `json:"password"` @@ -135,13 +45,13 @@ func NewCreateRoomHandler(s *rtmps.Server) gin.HandlerFunc { rooms := ctx.Value("rooms").(*room.Rooms) req := new(CreateRoomReq) if err := json.NewDecoder(ctx.Request.Body).Decode(req); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } user, err := room.NewUser(req.Username, req.UserPassword, nil, room.WithUserAdmin(true)) if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } @@ -150,13 +60,13 @@ func NewCreateRoomHandler(s *rtmps.Server) gin.HandlerFunc { room.WithRootUser(user), ) if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } - token, err := newAuthorization(user) + token, err := middlewares.NewAuthToken(user) if err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } @@ -193,7 +103,7 @@ func NewCreateRoomHandler(s *rtmps.Server) gin.HandlerFunc { } }() - ctx.JSON(http.StatusCreated, NewApiDataResp(gin.H{ + ctx.JSON(http.StatusCreated, model.NewApiDataResp(gin.H{ "token": token, })) } @@ -253,7 +163,7 @@ func RoomList(ctx *gin.Context) { return t1.NeedPassword == t2.NeedPassword }) default: - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorStringResp("sort must be peoplenum or roomid")) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("sort must be peoplenum or roomid")) return } @@ -263,17 +173,17 @@ func RoomList(ctx *gin.Context) { case "desc": resp.Reverse() default: - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorStringResp("order must be asc or desc")) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("order must be asc or desc")) return } list, err := GetPageItems(ctx, resp.Slice()) if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } - ctx.JSON(http.StatusOK, NewApiDataResp(gin.H{ + ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "total": resp.Len(), "list": list, })) @@ -283,11 +193,11 @@ func CheckRoom(ctx *gin.Context) { rooms := ctx.Value("rooms").(*room.Rooms) r, err := rooms.GetRoom(ctx.Query("roomId")) if err != nil { - ctx.AbortWithStatusJSON(http.StatusNotFound, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err)) return } - ctx.JSON(http.StatusOK, NewApiDataResp(gin.H{ + ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "peopleNum": r.ClientNum(), "needPassword": r.NeedPassword(), })) @@ -297,17 +207,17 @@ func CheckUser(ctx *gin.Context) { rooms := ctx.Value("rooms").(*room.Rooms) r, err := rooms.GetRoom(ctx.Query("roomId")) if err != nil { - ctx.AbortWithStatusJSON(http.StatusNotFound, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err)) return } u, err := r.GetUser(ctx.Query("username")) if err != nil { - ctx.AbortWithStatusJSON(http.StatusNotFound, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err)) return } - ctx.JSON(http.StatusOK, NewApiDataResp(gin.H{ + ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "idRoot": u.IsRoot(), "idAdmin": u.IsAdmin(), "lastAct": u.LastAct(), @@ -325,13 +235,13 @@ func LoginRoom(ctx *gin.Context) { rooms := ctx.Value("rooms").(*room.Rooms) req := new(LoginRoomReq) if err := json.NewDecoder(ctx.Request.Body).Decode(req); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } autoNew, err := strconv.ParseBool(ctx.DefaultQuery("autoNew", "false")) if err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorStringResp("autoNew must be bool")) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("autoNew must be bool")) return } @@ -339,46 +249,42 @@ func LoginRoom(ctx *gin.Context) { user *room.User ) if autoNew { - user, err = authOrNewWithPassword(rooms, req.RoomID, req.Password, req.Username, req.UserPassword) + user, err = middlewares.AuthOrNewWithPassword(req.RoomID, req.Password, req.Username, req.UserPassword, rooms) if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err)) return } } else { - user, err = authWithPassword(rooms, req.RoomID, req.Password, req.Username, req.UserPassword) + user, err = middlewares.AuthWithPassword(req.RoomID, req.Password, req.Username, req.UserPassword, rooms) if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err)) return } } - token, err := newAuthorization(user) + token, err := middlewares.NewAuthToken(user) if err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } - ctx.JSON(http.StatusOK, NewApiDataResp(gin.H{ + ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "token": token, })) } func DeleteRoom(ctx *gin.Context) { rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } + user := ctx.Value("user").(*room.User) if !user.IsRoot() { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorStringResp("only root can close room")) + ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorStringResp("only root can close room")) return } - err = rooms.DelRoom(user.Room().ID()) + err := rooms.DelRoom(user.Room().ID()) if err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } @@ -390,33 +296,28 @@ type SetPasswordReq struct { } func SetPassword(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } + user := ctx.Value("user").(*room.User) if !user.IsRoot() || !user.IsAdmin() { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorStringResp("only root or admin can set password")) + ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorStringResp("only root or admin can set password")) return } req := new(SetPasswordReq) if err := json.NewDecoder(ctx.Request.Body).Decode(req); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } user.Room().SetPassword(req.Password) - token, err := newAuthorization(user) + token, err := middlewares.NewAuthToken(user) if err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } - ctx.JSON(http.StatusOK, NewApiDataResp(gin.H{ + ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "token": token, })) } @@ -426,27 +327,22 @@ type UsernameReq struct { } func AddAdmin(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } + user := ctx.Value("user").(*room.User) if !user.IsRoot() && !user.IsAdmin() { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorStringResp("only root or admin can add admin")) + ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorStringResp("only root or admin can add admin")) return } req := new(UsernameReq) if err := json.NewDecoder(ctx.Request.Body).Decode(req); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } u, err := user.Room().GetUser(req.Username) if err != nil { - ctx.AbortWithStatusJSON(http.StatusNotFound, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err)) return } @@ -456,27 +352,22 @@ func AddAdmin(ctx *gin.Context) { } func DelAdmin(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } + user := ctx.Value("user").(*room.User) if !user.IsRoot() { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorStringResp("only root can del admin")) + ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorStringResp("only root can del admin")) return } req := new(UsernameReq) if err := json.NewDecoder(ctx.Request.Body).Decode(req); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } u, err := user.Room().GetUser(req.Username) if err != nil { - ctx.AbortWithStatusJSON(http.StatusNotFound, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err)) return } @@ -484,25 +375,3 @@ func DelAdmin(ctx *gin.Context) { ctx.Status(http.StatusNoContent) } - -func CloseRoom(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } - - if !user.IsRoot() { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorStringResp("only root can close room")) - return - } - - err = rooms.DelRoom(user.Room().ID()) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) - return - } - - ctx.Status(http.StatusNoContent) -} diff --git a/server/handlers/user.go b/server/handlers/user.go index 33ccbd6..15caa5f 100644 --- a/server/handlers/user.go +++ b/server/handlers/user.go @@ -6,17 +6,14 @@ import ( "github.com/gin-gonic/gin" json "github.com/json-iterator/go" "github.com/synctv-org/synctv/room" + "github.com/synctv-org/synctv/server/middlewares" + "github.com/synctv-org/synctv/server/model" ) func Me(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } + user := ctx.Value("user").(*room.User) - ctx.JSON(http.StatusOK, NewApiDataResp(gin.H{ + ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "isRoot": user.IsRoot(), "isAdmin": user.IsAdmin(), "username": user.Name(), @@ -25,33 +22,28 @@ func Me(ctx *gin.Context) { } func SetUserPassword(ctx *gin.Context) { - rooms := ctx.Value("rooms").(*room.Rooms) - user, err := AuthRoom(ctx.GetHeader("Authorization"), rooms) - if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) - return - } + user := ctx.Value("user").(*room.User) req := new(SetPasswordReq) if err := json.NewDecoder(ctx.Request.Body).Decode(req); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } if err := user.SetPassword(req.Password); err != nil { - ctx.AbortWithStatusJSON(http.StatusBadRequest, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } user.CloseHub() - token, err := newAuthorization(user) + token, err := middlewares.NewAuthToken(user) if err != nil { - ctx.AbortWithStatusJSON(http.StatusInternalServerError, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } - ctx.JSON(http.StatusOK, NewApiDataResp(gin.H{ + ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "token": token, })) } diff --git a/server/handlers/websocket.go b/server/handlers/websocket.go index a5ea694..c22fc31 100644 --- a/server/handlers/websocket.go +++ b/server/handlers/websocket.go @@ -11,6 +11,8 @@ import ( log "github.com/sirupsen/logrus" pb "github.com/synctv-org/synctv/proto" "github.com/synctv-org/synctv/room" + "github.com/synctv-org/synctv/server/middlewares" + "github.com/synctv-org/synctv/server/model" "github.com/synctv-org/synctv/utils" "google.golang.org/protobuf/proto" ) @@ -21,9 +23,9 @@ func NewWebSocketHandler(wss *utils.WebSocket) gin.HandlerFunc { return func(ctx *gin.Context) { rooms := ctx.Value("rooms").(*room.Rooms) token := ctx.GetHeader("Sec-WebSocket-Protocol") - user, err := AuthRoom(token, rooms) + user, err := middlewares.Auth(token, rooms) if err != nil { - ctx.AbortWithStatusJSON(http.StatusUnauthorized, NewApiErrorResp(err)) + ctx.AbortWithStatusJSON(http.StatusUnauthorized, model.NewApiErrorResp(err)) return } wss.Server(ctx.Writer, ctx.Request, []string{token}, NewWSMessageHandler(user)) diff --git a/server/middlewares/auth.go b/server/middlewares/auth.go new file mode 100644 index 0000000..8d40399 --- /dev/null +++ b/server/middlewares/auth.go @@ -0,0 +1,129 @@ +package middlewares + +import ( + "errors" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "github.com/synctv-org/synctv/internal/conf" + "github.com/synctv-org/synctv/room" + "github.com/synctv-org/synctv/server/model" + "github.com/zijiren233/stream" +) + +var ( + ErrAuthFailed = errors.New("auth failed") + ErrAuthExpired = errors.New("auth expired") +) + +type AuthClaims struct { + RoomID string `json:"id"` + Version uint64 `json:"v"` + Username string `json:"un"` + UserVersion uint64 `json:"uv"` + jwt.RegisteredClaims +} + +func auth(Authorization string) (*AuthClaims, error) { + t, err := jwt.ParseWithClaims(strings.TrimPrefix(Authorization, `Bearer `), &AuthClaims{}, func(token *jwt.Token) (any, error) { + return stream.StringToBytes(conf.Conf.Jwt.Secret), nil + }) + if err != nil { + return nil, ErrAuthFailed + } + claims, ok := t.Claims.(*AuthClaims) + if !ok || !t.Valid { + return nil, ErrAuthFailed + } + return claims, nil +} + +func Auth(Authorization string, rooms *room.Rooms) (*room.User, error) { + claims, err := auth(Authorization) + if err != nil { + return nil, err + } + r, err := rooms.GetRoom(claims.RoomID) + if err != nil { + return nil, err + } + + if !r.CheckVersion(claims.Version) { + return nil, ErrAuthExpired + } + + user, err := r.GetUser(claims.Username) + if err != nil { + return nil, err + } + + if !user.CheckVersion(claims.UserVersion) { + return nil, ErrAuthExpired + } + + return user, nil +} + +func AuthWithPassword(roomID, roomPassword, username, password string, rooms *room.Rooms) (*room.User, error) { + room, err := rooms.GetRoom(roomID) + if err != nil { + return nil, err + } + if !room.CheckPassword(roomPassword) { + return nil, ErrAuthFailed + } + user, err := room.GetUser(username) + if err != nil { + return nil, err + } + if !user.CheckPassword(password) { + return nil, ErrAuthFailed + } + return user, nil +} + +func AuthOrNewWithPassword(roomID, roomPassword, username, password string, rooms *room.Rooms) (*room.User, error) { + room, err := rooms.GetRoom(roomID) + if err != nil { + return nil, err + } + if !room.CheckPassword(roomPassword) { + return nil, ErrAuthFailed + } + user, err := room.GetOrNewUser(username, password) + if err != nil { + return nil, err + } + if !user.CheckPassword(password) { + return nil, ErrAuthFailed + } + return user, nil +} + +func AuthRoom(ctx *gin.Context) { + rooms := ctx.Value("rooms").(*room.Rooms) + user, err := Auth(ctx.GetHeader("Authorization"), rooms) + if err != nil { + ctx.AbortWithStatusJSON(401, model.NewApiErrorResp(err)) + return + } + + ctx.Set("user", user) + ctx.Next() +} + +func NewAuthToken(user *room.User) (string, error) { + claims := &AuthClaims{ + RoomID: user.Room().ID(), + Version: user.Room().Version(), + Username: user.Name(), + UserVersion: user.Version(), + RegisteredClaims: jwt.RegisteredClaims{ + NotBefore: jwt.NewNumericDate(time.Now()), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * time.Duration(conf.Conf.Jwt.Expire))), + }, + } + return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(stream.StringToBytes(conf.Conf.Jwt.Secret)) +} diff --git a/server/handlers/api.go b/server/model/api.go similarity index 97% rename from server/handlers/api.go rename to server/model/api.go index da80ccb..6b1e304 100644 --- a/server/handlers/api.go +++ b/server/model/api.go @@ -1,4 +1,4 @@ -package handlers +package model import ( "time"