diff --git a/controllers/admin/config.go b/controllers/admin/config.go index 7f4dcae3e..e2fd85a2a 100644 --- a/controllers/admin/config.go +++ b/controllers/admin/config.go @@ -603,6 +603,27 @@ func SetForbiddenUsernameList(w http.ResponseWriter, r *http.Request) { controllers.WriteSimpleResponse(w, true, "forbidden username list updated") } +// SetSuggestedUsernameList will set the list of suggested usernames that newly registered users are assigned if it isn't inferred otherwise (i.e. through a proxy). +func SetSuggestedUsernameList(w http.ResponseWriter, r *http.Request) { + type suggestedUsernameListRequest struct { + Value []string `json:"value"` + } + + decoder := json.NewDecoder(r.Body) + var request suggestedUsernameListRequest + + if err := decoder.Decode(&request); err != nil { + controllers.WriteSimpleResponse(w, false, "unable to update suggested usernames with provided values") + return + } + + if err := data.SetSuggestedUsernamesList(request.Value); err != nil { + controllers.WriteSimpleResponse(w, false, err.Error()) + } + + controllers.WriteSimpleResponse(w, true, "suggested username list updated") +} + func requirePOST(w http.ResponseWriter, r *http.Request) bool { if r.Method != controllers.POST { controllers.WriteSimpleResponse(w, false, r.Method+" not supported") diff --git a/controllers/admin/serverConfig.go b/controllers/admin/serverConfig.go index 328edf6e5..8270e9622 100644 --- a/controllers/admin/serverConfig.go +++ b/controllers/admin/serverConfig.go @@ -17,6 +17,7 @@ import ( func GetServerConfig(w http.ResponseWriter, r *http.Request) { ffmpeg := utils.ValidatedFfmpegPath(data.GetFfMpegPath()) usernameBlocklist := data.GetForbiddenUsernameList() + usernameSuggestions := data.GetSuggestedUsernamesList() videoQualityVariants := make([]models.StreamOutputVariant, 0) for _, variant := range data.GetStreamOutputVariants() { @@ -64,6 +65,7 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) { SupportedCodecs: transcoder.GetCodecs(ffmpeg), VideoCodec: data.GetVideoCodec(), ForbiddenUsernames: usernameBlocklist, + SuggestedUsernames: usernameSuggestions, } w.Header().Set("Content-Type", "application/json") @@ -89,6 +91,7 @@ type serverConfigAdminResponse struct { SupportedCodecs []string `json:"supportedCodecs"` VideoCodec string `json:"videoCodec"` ForbiddenUsernames []string `json:"forbiddenUsernames"` + SuggestedUsernames []string `json:"suggestedUsernames"` } type videoSettings struct { diff --git a/core/data/config.go b/core/data/config.go index c8beb0152..0eaa0f57d 100644 --- a/core/data/config.go +++ b/core/data/config.go @@ -42,6 +42,7 @@ const externalActionsKey = "external_actions" const customStylesKey = "custom_styles" const videoCodecKey = "video_codec" const blockedUsernamesKey = "blocked_usernames" +const suggestedUsernamesKey = "suggested_usernames" // GetExtraPageBodyContent will return the user-supplied body content. func GetExtraPageBodyContent() string { @@ -601,3 +602,23 @@ func SetForbiddenUsernameList(usernames []string) error { usernameListString := strings.Join(usernames, ",") return _datastore.SetString(blockedUsernamesKey, usernameListString) } + +// GetSuggestedUsernamesList will return the suggested usernames as a comma separated string. +// If the number of suggested usernames is smaller than 10, the number pool is not used (see code in the CreateAnonymousUser function). +func GetSuggestedUsernamesList() []string { + usernameString, err := _datastore.GetString(suggestedUsernamesKey) + + if err != nil || usernameString == "" { + return []string{} + } + + suggestionList := strings.Split(usernameString, ",") + + return suggestionList +} + +// SetSuggestedUsernamesList sets the username suggestion list as a comma separated string. +func SetSuggestedUsernamesList(usernames []string) error { + usernameListString := strings.Join(usernames, ",") + return _datastore.SetString(suggestedUsernamesKey, usernameListString) +} diff --git a/core/user/user.go b/core/user/user.go index 3216828ce..218cb3d18 100644 --- a/core/user/user.go +++ b/core/user/user.go @@ -17,6 +17,7 @@ import ( var _datastore *data.Datastore const moderatorScopeKey = "MODERATOR" +const minSuggestedUsernamePoolLength = 10 // User represents a single chat user. type User struct { @@ -48,7 +49,7 @@ func SetupUsers() { } // CreateAnonymousUser will create a new anonymous user with the provided display name. -func CreateAnonymousUser(username string) (*User, error) { +func CreateAnonymousUser(displayName string) (*User, error) { id := shortid.MustGenerate() accessToken, err := utils.GenerateAccessToken() if err != nil { @@ -56,9 +57,15 @@ func CreateAnonymousUser(username string) (*User, error) { return nil, err } - displayName := username if displayName == "" { - displayName = utils.GeneratePhrase() + suggestedUsernamesList := data.GetSuggestedUsernamesList() + + if len(suggestedUsernamesList) >= minSuggestedUsernamePoolLength { + index := utils.RandomIndex(len(suggestedUsernamesList)) + displayName = suggestedUsernamesList[index] + } else { + displayName = utils.GeneratePhrase() + } } displayColor := utils.GenerateRandomDisplayColor() @@ -287,8 +294,8 @@ func GetModeratorUsers() []*User { substr(rest, instr(rest, ',')+1) FROM split WHERE rest <> '') - SELECT id, display_name, scopes, display_color, created_at, disabled_at, previous_names, namechanged_at, scope - FROM split + SELECT id, display_name, scopes, display_color, created_at, disabled_at, previous_names, namechanged_at, scope + FROM split WHERE scope <> '' ORDER BY created_at ) AS token WHERE token.scope = ?` diff --git a/router/router.go b/router/router.go index 686104c42..23e46b7d8 100644 --- a/router/router.go +++ b/router/router.go @@ -142,6 +142,9 @@ func Start() error { // Set chat usernames that are not allowed http.HandleFunc("/api/admin/config/chat/forbiddenusernames", middleware.RequireAdminAuth(admin.SetForbiddenUsernameList)) + // Set the suggested chat usernames that will be assigned automatically + http.HandleFunc("/api/admin/config/chat/suggestedusernames", middleware.RequireAdminAuth(admin.SetSuggestedUsernameList)) + // Set video codec http.HandleFunc("/api/admin/config/video/codec", middleware.RequireAdminAuth(admin.SetVideoCodec)) diff --git a/utils/phraseGenerator.go b/utils/phraseGenerator.go index 7776b169a..104f86c88 100644 --- a/utils/phraseGenerator.go +++ b/utils/phraseGenerator.go @@ -10,7 +10,7 @@ import ( var ( // taken from https://github.com/docker/docker/blob/master/pkg/namesgenerator/names-generator.go - left = [...]string{ + left = []string{ "admiring", "adoring", "affectionate", @@ -115,7 +115,7 @@ var ( // Docker, starting from 0.7.x, generates names from notable scientists and hackers. // Please, for any amazing man that you add to the list, consider adding an equally amazing woman to it, and vice versa. - right = [...]string{ + right = []string{ // Muhammad ibn Jābir al-Ḥarrānī al-Battānī was a founding father of astronomy. https://en.wikipedia.org/wiki/Mu%E1%B8%A5ammad_ibn_J%C4%81bir_al-%E1%B8%A4arr%C4%81n%C4%AB_al-Batt%C4%81n%C4%AB "albattani", @@ -628,10 +628,15 @@ var ( // GeneratePhrase will generate and return a random string consisting of our word list. func GeneratePhrase() string { - r := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint - - leftIndex := int(r.Float32() * float32(len(left))) - rightIndex := int(r.Float32() * float32(len(right))) + leftIndex := RandomIndex(len(left)) + rightIndex := RandomIndex(len(right)) return fmt.Sprintf("%s-%s", left[leftIndex], right[rightIndex]) } + +// RandomIndex returns a random integer that is at most the `max` parameter. +func RandomIndex(max int) int { + r := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint + + return int(r.Float32() * float32(max)) +}