func (db *dbClient) newRedisClient() (rdisClient, error) { if db.config.DatabaseAddress == "" { return nil, errMissingDatabaseHost } if db.config.DatabasePass == "" { logger.Info("Insecure redis database is not recommended") } r := &rClient{} r.Client = redis.NewClient(&redis.Options{ //r.Client = redis.NewClient(&redis.Options{ Addr: db.config.DatabaseAddress, Password: db.config.DatabasePass, DB: db.config.DatabaseUser, }) // Confirm we can communicate with the redis instance. _, err := r.Ping().Result() if err != nil { return nil, errors.Wrap(err, "r.Pint().Result") } return r, nil }
func main() { http.HandleFunc("/", http.NotFound) http.HandleFunc("/ws", newConnection) logger.Info("Starting Tiberious on", config.Port) if err := http.ListenAndServe(config.Port, nil); err != nil { logger.Error(err) } }
// WriteGroupData writes a given group object to the current database. func (db *dbClient) WriteGroupData(group *types.Group) error { switch { case db.config.UserDatabase == 0: return db.rdis.writeGroupData(group) default: // TODO determine if this should be a fatal error, it should never happen logger.Info("Invalid config, no group data written") break } return nil }
// NewDB returns a new database client func NewDB(c settings.Config) (Client, error) { client := &dbClient{ config: c, } if client.config.UserDatabase == 0 { // Load Redis DB var err error client.rdis, err = client.newRedisClient() if err != nil { return client, errors.Wrap(err, "newRedisClient") } logger.Info("User database started on redis db", client.config.DatabaseUser) } return client, nil }
package db import ( "strings" "tiberious/logger" "tiberious/settings" "tiberious/types" "github.com/pkg/errors" ) type ( // Client provides access to the database Client interface { GetKeySet(search string) ([]string, error) WriteUserData(user *types.User) error WriteRoomData(room *types.Room) error WriteGroupData(group *types.Group) error UserExists(id string) (bool, error) RoomExists(gname, rname string) (bool, error) GroupExists(gname string) (bool, error) GetUserData(id string) (*types.User, error) GetRoomData(gname, rname string) (*types.Room, error) GetGroupData(gname string) (*types.Group, error) DeleteUser(user *types.User) error } dbClient struct { config settings.Config rdis rdisClient } ) // NewDB returns a new database client func NewDB(c settings.Config) (Client, error) { client := &dbClient{ config: c, } if client.config.UserDatabase == 0 { // Load Redis DB var err error client.rdis, err = client.newRedisClient() if err != nil { return client, errors.Wrap(err, "newRedisClient") } logger.Info("User database started on redis db", client.config.DatabaseUser) } return client, nil } // GetKeySet returns all the keys that match a given search pattern. func (db *dbClient) GetKeySet(search string) ([]string, error) { switch { case db.config.UserDatabase == 0: return db.rdis.getKeySet(search) default: break } return nil, types.NotInDB } // WriteUserData writes a given user object to the current database. func (db *dbClient) WriteUserData(user *types.User) error { switch { case db.config.UserDatabase == 0: return db.rdis.writeUserData(user) default: // TODO determine if this should be a fatal error, it should never happen logger.Info("Invalid config, no user data written") break } return nil } // WriteRoomData writes a given room object to the current database. func (db *dbClient) WriteRoomData(room *types.Room) error { switch { case db.config.UserDatabase == 0: return db.rdis.writeRoomData(room) default: // TODO determine if this should be a fatal error, it should never happen logger.Info("Invalid config, no room data written") break } return nil } // WriteGroupData writes a given group object to the current database. func (db *dbClient) WriteGroupData(group *types.Group) error { switch { case db.config.UserDatabase == 0: return db.rdis.writeGroupData(group) default: // TODO determine if this should be a fatal error, it should never happen logger.Info("Invalid config, no group data written") break } return nil } // UserExists returns whether a user exists in the database. func (db *dbClient) UserExists(id string) (bool, error) { res, err := db.GetKeySet("user-*-*-" + id) if err != nil { return false, errors.Wrap(err, "GetKeySet") } if len(res) == 0 { return false, nil } return true, nil } // RoomExists returns whether a room exists in the database. func (db *dbClient) RoomExists(gname, rname string) (bool, error) { res, err := db.GetKeySet("room-" + gname + "-" + rname + "*") if err != nil { return false, errors.Wrap(err, "GetKeySet") } if len(res) == 0 { return false, nil } return true, nil } // GroupExists returns whether a group exists in the database. func (db *dbClient) GroupExists(gname string) (bool, error) { res, err := db.GetKeySet("group-" + gname + "-*") if err != nil { return false, errors.Wrap(err, "GetKeySet") } if len(res) == 0 { return false, nil } return true, nil } // GetUserData gets all the data for a given user ID from the database func (db *dbClient) GetUserData(id string) (*types.User, error) { switch { case db.config.UserDatabase == 0: return db.rdis.getUserData(id) default: break } return nil, types.NotInDB } // GetRoomData gets all the data for a given room (group required) from the database func (db *dbClient) GetRoomData(gname, rname string) (*types.Room, error) { switch { case db.config.UserDatabase == 0: return db.rdis.getRoomData(gname, rname) default: break } return nil, types.NotInDB } // GetGroupData gets all the data for a given group from the database func (db *dbClient) GetGroupData(gname string) (*types.Group, error) { switch { case db.config.UserDatabase == 0: return db.rdis.getGroupData(gname) default: break } return nil, types.NotInDB } // DeleteUser removes a user from all rooms and groups and deletes it from the // database (use sparingly). */ func (db *dbClient) DeleteUser(user *types.User) error { for _, gname := range user.Groups { group, err := db.GetGroupData(gname) if err != nil { return errors.Wrap(err, "GetGroupData") } delete(group.Users, user.ID.String()) if err := db.WriteGroupData(group); err != nil { return errors.Wrap(err, "WriteGroupData") } } for _, rname := range user.Rooms { slice := strings.Split(rname, "/") room, err := db.GetRoomData(slice[0], slice[1]) if err != nil { return errors.Wrap(err, "GetRoomData") } delete(room.Users, user.ID.String()) if err := db.WriteRoomData(room); err != nil { return errors.Wrap(err, "WriteRoomData") } } switch { case db.config.UserDatabase == 0: return db.rdis.deleteUser(user) default: // TODO determine if this should be a fatal error, it should never happen logger.Info("Invalid config, user not deleted") break } return nil }
// ClientHandler handles all client interactions func ClientHandler(conn *websocket.Conn) { var err error client := types.NewClient() client.Conn = conn client.User = new(types.User) // Set the UUID and initialize a username of "guest" client.User.ID, err = getUniqueID() if err != nil { logger.Error(err) } guests, err := dbClient.GetKeySet("user-guest-*-*") if err != nil { logger.Error(err) } // TODO give guests a numeric suffix, allow disabling guest connections. client.User.Username = "******" + strconv.Itoa(len(guests)+1) client.User.LoginName = client.User.Username client.User.Type = "guest" client.User.Connected = true clients[client.User.ID.String()] = client if config.AllowGuests { defgroup, err := GetGroup("#default") if err != nil { logger.Error(err) } defgroup.Users[client.User.ID.String()] = client.User client.User.Groups = append(client.User.Groups, "#default") room, err := GetRoom("#default", "#general") if err != nil { logger.Error(err) } client.User.Rooms = append(client.User.Rooms, "#default/#general") room.Users[client.User.ID.String()] = client.User dbClient.WriteUserData(client.User) dbClient.WriteGroupData(defgroup) dbClient.WriteRoomData(room) logger.Info("guest", client.User.ID.String(), "connected") } else { logger.Info("new client connected") } if err := client.Alert(types.OK, ""); err != nil { logger.Error(err) } /* TODO we may want to remove this later it's just for easy testing. * to allow a client to get their UUID back from the server after * connecting. */ if config.AllowGuests { if err := client.Alert(types.GeneralNotice, string("Connected as guest with ID "+client.User.ID.String())); err != nil { logger.Error(err) } } else { if err := client.Alert(types.ImportantNotice, "No Guests Allowed : send authentication token to continue"); err != nil { logger.Error(err) } } /* Never return from this loop! * Never break from this loop unless intending to disconnect the client. */ for { _, rawmsg, err := client.Conn.ReadMessage() if err != nil { switch { case websocket.IsCloseError(err, websocket.CloseNormalClosure): if client.User != nil { logger.Info(client.User.Type, client.User.ID.String(), "disconnected") } else { logger.Info("client disconnected") } break // TODO handle these different cases appropriately. case websocket.IsCloseError(err, websocket.CloseGoingAway): case websocket.IsCloseError(err, websocket.CloseProtocolError, websocket.CloseUnsupportedData): // This should utilize the ban-score to combat possible spammers case websocket.IsCloseError(err, websocket.ClosePolicyViolation, websocket.CloseMessageTooBig): // These should also utilize the ban-score but with a higher ban default: logger.Info(err) } break } ban, err := ParseMessage(client, rawmsg) if err != nil { logger.Info(err) } if ban > 0 { // TODO handle ban-score break } } // We broke out of the loop so disconnect the client. client.Conn.Close() if client.User != nil { if client.User.Type == "guest" { if err := dbClient.DeleteUser(client.User); err != nil { logger.Error(err) } } else { client.User.Connected = false dbClient.WriteUserData(client.User) } } delete(clients, client.User.ID.String()) }