diff --git a/internal/db/db.go b/internal/db/db.go index 445a94e..4cf061c 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -25,6 +25,10 @@ func Init(d *gorm.DB, t conf.DatabaseType) error { if err != nil { return err } + err = initGuestUser() + if err != nil { + return err + } return initRootUser() } @@ -39,6 +43,22 @@ func initRootUser() error { return err } +const ( + GuestUsername = "guest" + GuestUserID = "00000000000000000000000000000001" +) + +func initGuestUser() error { + user := model.User{} + err := db.Where("id = ?", GuestUserID).First(&user).Error + if err == nil || !errors.Is(err, gorm.ErrRecordNotFound) { + return err + } + u, err := CreateUser("guest", utils.RandString(32), WithRole(model.RoleUser), WithID(GuestUserID)) + log.Infof("init guest user:\nid: %s\nusername: %s", u.ID, u.Username) + return err +} + func DB() *gorm.DB { return db } diff --git a/internal/db/member.go b/internal/db/member.go index 143f6b8..2bff0cc 100644 --- a/internal/db/member.go +++ b/internal/db/member.go @@ -50,7 +50,7 @@ func FirstOrCreateRoomMemberRelation(roomID, userID string, conf ...CreateRoomMe return roomMemberRelation, err } -func GetRoomMemberRelation(roomID, userID string) (*model.RoomMember, error) { +func GetRoomMember(roomID, userID string) (*model.RoomMember, error) { roomMemberRelation := &model.RoomMember{} err := db.Where("room_id = ? AND user_id = ?", roomID, userID).First(roomMemberRelation).Error return roomMemberRelation, HandleNotFound(err, "room or user") diff --git a/internal/db/user.go b/internal/db/user.go index 8c4b58c..da16bf7 100644 --- a/internal/db/user.go +++ b/internal/db/user.go @@ -15,6 +15,12 @@ import ( type CreateUserConfig func(u *model.User) +func WithID(id string) CreateUserConfig { + return func(u *model.User) { + u.ID = id + } +} + func WithRole(role model.Role) CreateUserConfig { return func(u *model.User) { u.Role = role diff --git a/internal/model/room.go b/internal/model/room.go index 7ffbc49..401bb35 100644 --- a/internal/model/room.go +++ b/internal/model/room.go @@ -77,6 +77,7 @@ type RoomSettings struct { DisableJoinNewUser bool `gorm:"default:false" json:"disable_join_new_user"` JoinNeedReview bool `gorm:"default:false" json:"join_need_review"` UserDefaultPermissions RoomMemberPermission `json:"user_default_permissions"` + DisableGuest bool `gorm:"default:false" json:"disable_guest"` CanGetMovieList bool `gorm:"default:true" json:"can_get_movie_list"` CanAddMovie bool `gorm:"default:true" json:"can_add_movie"` @@ -93,6 +94,7 @@ func DefaultRoomSettings() *RoomSettings { DisableJoinNewUser: false, JoinNeedReview: false, UserDefaultPermissions: DefaultPermissions, + DisableGuest: false, CanGetMovieList: true, CanAddMovie: true, diff --git a/internal/op/room.go b/internal/op/room.go index 963283a..fef7bf4 100644 --- a/internal/op/room.go +++ b/internal/op/room.go @@ -7,9 +7,9 @@ import ( "sync/atomic" "github.com/gorilla/websocket" - log "github.com/sirupsen/logrus" "github.com/synctv-org/synctv/internal/db" "github.com/synctv-org/synctv/internal/model" + "github.com/synctv-org/synctv/internal/settings" "github.com/synctv-org/synctv/utils" "github.com/zijiren233/gencontainer/rwmap" rtmps "github.com/zijiren233/livelib/server" @@ -93,10 +93,10 @@ func (r *Room) AddMovies(movies []*model.Movie) error { } func (r *Room) UserRole(userID string) (model.RoomMemberRole, error) { - if r.CreatorID == userID { + if r.IsCreator(userID) { return model.RoomMemberRoleCreator, nil } - rur, err := r.LoadOrCreateRoomMember(userID) + rur, err := r.LoadRoomMember(userID) if err != nil { return model.RoomMemberRoleUnknown, err } @@ -105,12 +105,8 @@ func (r *Room) UserRole(userID string) (model.RoomMemberRole, error) { // do not use this value for permission determination func (r *Room) IsAdmin(userID string) bool { - if r.IsCreator(userID) { - return true - } role, err := r.UserRole(userID) if err != nil { - log.Errorf("get user role failed: %s", err.Error()) return false } return role.IsAdmin() @@ -120,6 +116,10 @@ func (r *Room) IsCreator(userID string) bool { return r.CreatorID == userID } +func (r *Room) IsGuest(userID string) bool { + return userID == db.GuestUserID +} + func (r *Room) HasPermission(userID string, permission model.RoomMemberPermission) bool { if r.IsCreator(userID) { return true @@ -162,7 +162,7 @@ func (r *Room) HasAdminPermission(userID string, permission model.RoomAdminPermi } func (r *Room) LoadOrCreateMemberStatus(userID string) (model.RoomMemberStatus, error) { - if r.CreatorID == userID { + if r.IsCreator(userID) { return model.RoomMemberStatusActive, nil } rur, err := r.LoadOrCreateRoomMember(userID) @@ -173,7 +173,7 @@ func (r *Room) LoadOrCreateMemberStatus(userID string) (model.RoomMemberStatus, } func (r *Room) LoadMemberStatus(userID string) (model.RoomMemberStatus, error) { - if r.CreatorID == userID { + if r.IsCreator(userID) { return model.RoomMemberStatusActive, nil } rur, err := r.LoadRoomMember(userID) @@ -187,12 +187,15 @@ func (r *Room) LoadOrCreateRoomMember(userID string) (*model.RoomMember, error) if r.Settings.DisableJoinNewUser { return r.LoadRoomMember(userID) } + if r.IsGuest(userID) && (r.Settings.DisableGuest || !settings.EnableGuest.Get()) { + return nil, errors.New("guest is disabled") + } member, ok := r.members.Load(userID) if ok { return member, nil } var conf []db.CreateRoomMemberRelationConfig - if r.CreatorID == userID { + if r.IsCreator(userID) { conf = append( conf, db.WithRoomMemberStatus(model.RoomMemberStatusActive), @@ -200,6 +203,14 @@ func (r *Room) LoadOrCreateRoomMember(userID string) (*model.RoomMember, error) db.WithRoomMemberRole(model.RoomMemberRoleCreator), db.WithRoomMemberAdminPermissions(model.AllAdminPermissions), ) + } else if r.IsGuest(userID) { + conf = append( + conf, + db.WithRoomMemberStatus(model.RoomMemberStatusActive), + db.WithRoomMemberRelationPermissions(model.NoPermission), + db.WithRoomMemberRole(model.RoomMemberRoleMember), + db.WithRoomMemberAdminPermissions(model.NoAdminPermission), + ) } else { conf = append( conf, @@ -220,6 +231,11 @@ func (r *Room) LoadOrCreateRoomMember(userID string) (*model.RoomMember, error) member.Permissions = model.AllPermissions member.AdminPermissions = model.AllAdminPermissions member.Status = model.RoomMemberStatusActive + } else if r.IsGuest(userID) { + member.Role = model.RoomMemberRoleMember + member.Permissions = model.NoPermission + member.AdminPermissions = model.NoAdminPermission + member.Status = model.RoomMemberStatusActive } else if member.Role.IsAdmin() { member.Permissions = model.AllPermissions } @@ -228,11 +244,14 @@ func (r *Room) LoadOrCreateRoomMember(userID string) (*model.RoomMember, error) } func (r *Room) LoadRoomMember(userID string) (*model.RoomMember, error) { + if r.IsGuest(userID) && (r.Settings.DisableGuest || !settings.EnableGuest.Get()) { + return nil, errors.New("guest is disabled") + } member, ok := r.members.Load(userID) if ok { return member, nil } - member, err := db.GetRoomMemberRelation(r.ID, userID) + member, err := db.GetRoomMember(r.ID, userID) if err != nil { return nil, fmt.Errorf("get room member failed: %w", err) } @@ -241,6 +260,11 @@ func (r *Room) LoadRoomMember(userID string) (*model.RoomMember, error) { member.Permissions = model.AllPermissions member.AdminPermissions = model.AllAdminPermissions member.Status = model.RoomMemberStatusActive + } else if r.IsGuest(userID) { + member.Role = model.RoomMemberRoleMember + member.Permissions = model.NoPermission + member.AdminPermissions = model.NoAdminPermission + member.Status = model.RoomMemberStatusActive } else if member.Role.IsAdmin() { member.Permissions = model.AllPermissions } diff --git a/internal/op/user.go b/internal/op/user.go index 6ee3c93..f6a7470 100644 --- a/internal/op/user.go +++ b/internal/op/user.go @@ -193,6 +193,10 @@ func (u *User) IsPending() bool { return u.Role == model.RolePending } +func (u *User) IsGuest() bool { + return u.ID == db.GuestUserID +} + func (u *User) HasRoomPermission(room *Room, permission model.RoomMemberPermission) bool { if u.IsAdmin() { return true diff --git a/internal/settings/var.go b/internal/settings/var.go index 1d59dbc..46d2aef 100644 --- a/internal/settings/var.go +++ b/internal/settings/var.go @@ -19,6 +19,7 @@ var ( DisableUserSignup = NewBoolSetting("disable_user_signup", false, model.SettingGroupUser) SignupNeedReview = NewBoolSetting("signup_need_review", false, model.SettingGroupUser) UserMaxRoomCount = NewInt64Setting("user_max_room_count", 3, model.SettingGroupUser) + EnableGuest = NewBoolSetting("enable_guest", true, model.SettingGroupUser) ) var ( diff --git a/server/handlers/init.go b/server/handlers/init.go index d6c792f..ef8f258 100644 --- a/server/handlers/init.go +++ b/server/handlers/init.go @@ -167,6 +167,8 @@ func initRoom(room *gin.RouterGroup, needAuthUser *gin.RouterGroup, needAuthRoom room.GET("/list", RoomList) + room.POST("/guest", GuestJoinRoom) + needAuthUser.POST("/create", CreateRoom) needAuthUser.POST("/login", LoginRoom) diff --git a/server/handlers/room.go b/server/handlers/room.go index 54e4e71..f590caf 100644 --- a/server/handlers/room.go +++ b/server/handlers/room.go @@ -276,20 +276,27 @@ func CheckRoom(ctx *gin.Context) { })) } -func LoginRoom(ctx *gin.Context) { - user := ctx.MustGet("user").(*op.UserEntry).Value() +func GuestJoinRoom(ctx *gin.Context) { log := ctx.MustGet("log").(*logrus.Entry) req := model.LoginRoomReq{} if err := model.Decode(ctx, &req); err != nil { - log.Errorf("login room failed: %v", err) + log.Errorf("guest join room failed: %v", err) ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } + userE, err := op.LoadOrInitUserByID(db.GuestUserID) + if err != nil { + log.Errorf("guest join room failed: %v", err) + ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) + return + } + user := userE.Value() + roomE, err := op.LoadOrInitRoomByID(req.RoomId) if err != nil { - log.Errorf("login room failed: %v", err) + log.Errorf("guest join room failed: %v", err) if err == op.ErrRoomBanned || err == op.ErrRoomPending { ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err)) return @@ -299,20 +306,49 @@ func LoginRoom(ctx *gin.Context) { } room := roomE.Value() - member, err := room.LoadOrCreateRoomMember(user.ID) + if !room.CheckPassword(req.Password) { + log.Warn("guest join room failed: password error") + ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("password error")) + return + } + + token, err := middlewares.NewAuthRoomToken(user, room) if err != nil { - log.Errorf("login room failed: %v", err) + log.Errorf("guest join room failed: %v", err) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } - if member.Status.IsNotActive() { - log.Warn("login room failed: member status not active") - ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("member status not active")) + ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ + "roomId": room.ID, + "token": token, + })) +} + +func LoginRoom(ctx *gin.Context) { + user := ctx.MustGet("user").(*op.UserEntry).Value() + log := ctx.MustGet("log").(*logrus.Entry) + + req := model.LoginRoomReq{} + if err := model.Decode(ctx, &req); err != nil { + log.Errorf("login room failed: %v", err) + ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) + return + } + + roomE, err := op.LoadOrInitRoomByID(req.RoomId) + if err != nil { + log.Errorf("login room failed: %v", err) + if err == op.ErrRoomBanned || err == op.ErrRoomPending { + ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err)) + return + } + ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err)) return } + room := roomE.Value() - if !member.Role.IsAdmin() && !room.CheckPassword(req.Password) { + if !user.IsAdmin() && !user.IsRoomAdmin(room) && !room.CheckPassword(req.Password) { log.Warn("login room failed: password error") ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("password error")) return diff --git a/server/middlewares/auth.go b/server/middlewares/auth.go index bebabbf..018adf1 100644 --- a/server/middlewares/auth.go +++ b/server/middlewares/auth.go @@ -121,16 +121,21 @@ func AuthUser(Authorization string) (*op.UserEntry, error) { return nil, ErrAuthFailed } - u, err := op.LoadOrInitUserByID(claims.UserId) + userE, err := op.LoadOrInitUserByID(claims.UserId) if err != nil { return nil, err } + user := userE.Value() + + if user.IsGuest() { + return nil, errors.New("user is guest, can not login") + } - if !u.Value().CheckVersion(claims.UserVersion) { + if !user.CheckVersion(claims.UserVersion) { return nil, ErrAuthExpired } - return u, nil + return userE, nil } func NewAuthUserToken(user *op.User) (string, error) { @@ -140,6 +145,9 @@ func NewAuthUserToken(user *op.User) (string, error) { if user.IsPending() { return "", errors.New("user is pending, need admin to approve") } + if user.IsGuest() { + return "", errors.New("user is guest, can not login") + } t, err := time.ParseDuration(conf.Conf.Jwt.Expire) if err != nil { return "", err