diff --git a/pkg/controller/account/settings.go b/pkg/controller/account/settings.go
index 575d9e2..b454f49 100644
--- a/pkg/controller/account/settings.go
+++ b/pkg/controller/account/settings.go
@@ -18,6 +18,7 @@ import (
"code.nonshy.com/nonshy/website/pkg/session"
"code.nonshy.com/nonshy/website/pkg/templates"
"code.nonshy.com/nonshy/website/pkg/utility"
+ "code.nonshy.com/nonshy/website/pkg/worker"
"github.com/google/uuid"
)
@@ -50,6 +51,10 @@ func Settings() http.HandlerFunc {
return
}
+ // Is the user currently in the chat room? Gate username changes when so.
+ var isOnChat = worker.GetChatStatistics().IsOnline(user.Username)
+ vars["OnChat"] = isOnChat
+
// URL hashtag to redirect to
var hashtag string
@@ -293,32 +298,79 @@ func Settings() http.HandlerFunc {
case "settings":
hashtag = "#account"
var (
- oldPassword = r.PostFormValue("old_password")
- changeEmail = strings.TrimSpace(strings.ToLower(r.PostFormValue("change_email")))
- password1 = strings.TrimSpace(r.PostFormValue("new_password"))
- password2 = strings.TrimSpace(r.PostFormValue("new_password2"))
+ oldPassword = r.PostFormValue("old_password")
+ changeEmail = strings.TrimSpace(strings.ToLower(r.PostFormValue("change_email")))
+ changeUsername = strings.TrimSpace(strings.ToLower(r.PostFormValue("change_username")))
+ password1 = strings.TrimSpace(r.PostFormValue("new_password"))
+ password2 = strings.TrimSpace(r.PostFormValue("new_password2"))
)
// Their old password is needed to make any changes to their account.
if err := user.CheckPassword(oldPassword); err != nil {
session.FlashError(w, r, "Could not make changes to your account settings as the 'current password' you entered was incorrect.")
- templates.Redirect(w, r.URL.Path)
+ templates.Redirect(w, r.URL.Path+hashtag)
return
}
+ // Changing their username?
+ if changeUsername != user.Username {
+ // Not if they are in the chat room!
+ if isOnChat {
+ session.FlashError(w, r, "Your username could not be changed right now because you are logged into the chat room. Please exit the chat room, wait a minute, and try your request again.")
+ templates.Redirect(w, r.URL.Path+hashtag)
+ return
+ }
+
+ // Check if the new name is OK.
+ if err := models.IsValidUsername(changeUsername); err != nil {
+ session.FlashError(w, r, "Could not change your username: %s", err.Error())
+ templates.Redirect(w, r.URL.Path+hashtag)
+ return
+ }
+
+ // Set their name.
+ origUsername := user.Username
+ user.Username = changeUsername
+ if err := user.Save(); err != nil {
+ session.FlashError(w, r, "Error saving your new username: %s", err)
+ } else {
+ session.Flash(w, r, "Your username has been updated to: %s", user.Username)
+
+ // Notify the admin about this to keep tabs if someone is acting strangely
+ // with too-frequent username changes.
+ fb := &models.Feedback{
+ Intent: "report",
+ Subject: "Change of username",
+ UserID: user.ID,
+ TableName: "users",
+ TableID: user.ID,
+ Message: fmt.Sprintf(
+ "A user has modified their username on their profile page!\n\n"+
+ "* Original: %s\n* Updated: %s",
+ origUsername, changeUsername,
+ ),
+ }
+
+ // Save the feedback.
+ if err := models.CreateFeedback(fb); err != nil {
+ log.Error("Couldn't save feedback from user updating their DOB: %s", err)
+ }
+ }
+ }
+
// Changing their email?
if changeEmail != user.Email {
// Validate the email.
if _, err := nm.ParseAddress(changeEmail); err != nil {
session.FlashError(w, r, "The email address you entered is not valid: %s", err)
- templates.Redirect(w, r.URL.Path)
+ templates.Redirect(w, r.URL.Path+hashtag)
return
}
// Email must not already exist.
if _, err := models.FindUser(changeEmail); err == nil {
session.FlashError(w, r, "That email address is already in use.")
- templates.Redirect(w, r.URL.Path)
+ templates.Redirect(w, r.URL.Path+hashtag)
return
}
@@ -330,7 +382,7 @@ func Settings() http.HandlerFunc {
}
if err := redis.Set(fmt.Sprintf(config.ChangeEmailRedisKey, token.Token), token, config.SignupTokenExpires); err != nil {
session.FlashError(w, r, "Failed to create change email token: %s", err)
- templates.Redirect(w, r.URL.Path)
+ templates.Redirect(w, r.URL.Path+hashtag)
return
}
diff --git a/pkg/controller/account/signup.go b/pkg/controller/account/signup.go
index 2cc4e18..3d0df19 100644
--- a/pkg/controller/account/signup.go
+++ b/pkg/controller/account/signup.go
@@ -86,6 +86,9 @@ func Signup() http.HandlerFunc {
password = strings.TrimSpace(r.PostFormValue("password"))
password2 = strings.TrimSpace(r.PostFormValue("password2"))
dob = r.PostFormValue("dob")
+
+ // Validation errors but still show the form again.
+ hasError bool
)
// Don't let them sneakily change their verified email address on us.
@@ -95,21 +98,12 @@ func Signup() http.HandlerFunc {
return
}
- // Reserved username check.
- for _, cmp := range config.ReservedUsernames {
- if username == cmp {
- session.FlashError(w, r, "That username is reserved, please choose a different username.")
- templates.Redirect(w, r.URL.Path+"?token="+tokenStr)
- return
- }
- }
-
// Cache username in case of passwd validation errors.
vars["Email"] = email
vars["Username"] = username
// Is the app not configured to send email?
- if !config.Current.Mail.Enabled {
+ if !config.Current.Mail.Enabled && !config.SkipEmailVerification {
session.FlashError(w, r, "This app is not configured to send email so you can not sign up at this time. "+
"Please contact the website administrator about this issue!")
templates.Redirect(w, r.URL.Path)
@@ -209,7 +203,6 @@ func Signup() http.HandlerFunc {
}
// Full sign-up step (w/ email verification token), validate more things.
- var hasError bool
if len(password) < 3 {
session.FlashError(w, r, "Please enter a password longer than 3 characters.")
hasError = true
@@ -218,8 +211,9 @@ func Signup() http.HandlerFunc {
hasError = true
}
- if !config.UsernameRegexp.MatchString(username) {
- session.FlashError(w, r, "Your username must consist of only numbers, letters, - . and be 3-32 characters.")
+ // Validate the username is OK: well formatted, not reserved, not existing.
+ if err := models.IsValidUsername(username); err != nil {
+ session.FlashError(w, r, err.Error())
hasError = true
}
diff --git a/pkg/controller/chat/chat.go b/pkg/controller/chat/chat.go
index 9a3a178..ca5f5c5 100644
--- a/pkg/controller/chat/chat.go
+++ b/pkg/controller/chat/chat.go
@@ -153,6 +153,10 @@ func Landing() http.HandlerFunc {
log.Error("SendBlocklist: %s", err)
}
+ // Mark them as online immediately: so e.g. on the Change Username screen we leave no window
+ // of time where they can exist in chat but change their name on the site.
+ worker.GetChatStatistics().SetOnlineNow(currentUser.Username)
+
// Redirect them to the chat room.
templates.Redirect(w, strings.TrimSuffix(chatURL, "/")+"/?jwt="+ss)
return
diff --git a/pkg/models/user.go b/pkg/models/user.go
index 4e04af0..561030d 100644
--- a/pkg/models/user.go
+++ b/pkg/models/user.go
@@ -186,6 +186,28 @@ func FindUser(username string) (*User, error) {
return u, result.Error
}
+// IsValidUsername checks if a username is available and not reserved.
+func IsValidUsername(username string) error {
+ // Check the formatting of the name.
+ if !config.UsernameRegexp.MatchString(username) {
+ return errors.New("Your username must consist of only numbers, letters, - . and be 3-32 characters.")
+ }
+
+ // Reserved username check.
+ for _, cmp := range config.ReservedUsernames {
+ if username == cmp {
+ return errors.New("That username is reserved, please choose a different username.")
+ }
+ }
+
+ // Does the username already exist?
+ if _, err := FindUser(username); err == nil {
+ return errors.New("That username already exists. Please try a different username.")
+ }
+
+ return nil
+}
+
// IsShyFrom tells whether the user is shy from the perspective of the other user.
//
// That is, depending on our profile visibility and friendship status.
diff --git a/pkg/worker/barertc.go b/pkg/worker/barertc.go
index b165502..e3b97a1 100644
--- a/pkg/worker/barertc.go
+++ b/pkg/worker/barertc.go
@@ -2,7 +2,7 @@ package worker
import (
"encoding/json"
- "io/ioutil"
+ "io"
"net/http"
"sync"
"time"
@@ -22,16 +22,10 @@ type ChatStatistics struct {
}
// GetChatStatistics returns the latest (cached) chat statistics.
-func GetChatStatistics() ChatStatistics {
+func GetChatStatistics() *ChatStatistics {
chatStatisticsMu.RLock()
defer chatStatisticsMu.RUnlock()
-
- if cachedChatStatistics != nil {
- return *cachedChatStatistics
- }
- return ChatStatistics{
- Usernames: []string{},
- }
+ return cachedChatStatistics
}
// SetChatStatistics updates the cached chat statistics, holding a write lock briefly.
@@ -42,7 +36,7 @@ func SetChatStatistics(stats *ChatStatistics) {
}
// IsOnline returns whether the username is currently logged-in to chat.
-func (cs ChatStatistics) IsOnline(username string) bool {
+func (cs *ChatStatistics) IsOnline(username string) bool {
for _, user := range cs.Usernames {
if user == username {
return true
@@ -51,10 +45,20 @@ func (cs ChatStatistics) IsOnline(username string) bool {
return false
}
+// SetOnlineNow patches the current ChatStatistics to mark a user as online immediately, e.g.
+// because the main site has just sent them to the chat with a JWT token.
+func (cs *ChatStatistics) SetOnlineNow(username string) {
+ if !cs.IsOnline(username) {
+ chatStatisticsMu.Lock()
+ defer chatStatisticsMu.Unlock()
+ cs.Usernames = append(cs.Usernames, username)
+ }
+}
+
type UserOnChatMap map[string]bool
// MapUsersOnline returns a hashmap of usernames to online status.
-func (cs ChatStatistics) MapUsersOnline(usernames []string) UserOnChatMap {
+func (cs *ChatStatistics) MapUsersOnline(usernames []string) UserOnChatMap {
var result = UserOnChatMap{}
for _, user := range cs.Usernames {
result[user] = true
@@ -68,7 +72,7 @@ func (m UserOnChatMap) Get(username string) bool {
}
var (
- cachedChatStatistics *ChatStatistics
+ cachedChatStatistics = &ChatStatistics{}
chatStatisticsMu sync.RWMutex
)
@@ -117,7 +121,7 @@ func DoCheckBareRTC() {
if res.StatusCode == http.StatusOK {
var cs ChatStatistics
- body, _ := ioutil.ReadAll(res.Body)
+ body, _ := io.ReadAll(res.Body)
res.Body.Close()
if err = json.Unmarshal(body, &cs); err != nil {
log.Error("WatchBareRTC: json decode error: %s", err)
diff --git a/web/templates/account/settings.html b/web/templates/account/settings.html
index 2b177db..96987af 100644
--- a/web/templates/account/settings.html
+++ b/web/templates/account/settings.html
@@ -1120,7 +1120,7 @@
- Update E-mail or Password
+ Update E-mail, Username or Password
+ {{if .OnChat}} + + + You are currently logged into the chat room, so your username may not be + updated at this time. To change your username, please log out of the chat + room and wait a minute before reloading this page. + + {{else}} + Usernames are 3 to 32 characters a-z 0-9 . - + {{end}} +
+