Auto-Disconnect Users from Chat
Users whose accounts are no longer eligible to be in the chat room will be disconnected immediately from chat when their account status changes. The places in nonshy where these disconnects may happen include: * When the user deactivates or deletes their account. * When they modify their settings to mark their profile as 'private,' making them become a Shy Account. * When they edit or delete their photos in case they have moved their final public photo to be private, making them become a Shy Account. * When the user deletes their certification photo, or uploads a new cert photo to be reviewed (in both cases, losing account certified status). * When an admin user rejects their certification photo, even retroactively. * On admin actions against a user, including: banning them, deleting their user account. Other changes made include: * When signing up an account and e-mail sending is not enabled (e.g. local dev environment), the SignupToken is still created and logged to the console so you can continue the signup manually. * On the new account DOB prompt, add a link to manually input their birthdate as text similar to on the Age Gate page.
This commit is contained in:
parent
be9276f4c0
commit
742a5fa1af
163
pkg/chat/chat_api.go
Normal file
163
pkg/chat/chat_api.go
Normal file
|
@ -0,0 +1,163 @@
|
|||
package chat
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/config"
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
"code.nonshy.com/nonshy/website/pkg/models"
|
||||
)
|
||||
|
||||
// MaybeDisconnectUser may send a DisconnectUserNow to BareRTC if the user should not be allowed in the chat room.
|
||||
//
|
||||
// For example, they have set their profile to private and become a shy account, or they deactivated or got banned.
|
||||
//
|
||||
// If the user is presently in the chat room, they will be removed and given an appropriate ChatServer message.
|
||||
//
|
||||
// Returns a boolean OK (they were online in chat, and were removed) with the error only returning in case of a
|
||||
// communication or JSON encode error with BareRTC. If they were online and removed, an admin feedback notice is
|
||||
// also generated for visibility and confirmation of success.
|
||||
func MaybeDisconnectUser(user *models.User) (bool, error) {
|
||||
// What reason to remove them? If a message is provided, the DisconnectUserNow API will be called.
|
||||
var because = "You have been signed out of chat because "
|
||||
var reasons = []struct {
|
||||
If bool
|
||||
Message string
|
||||
}{
|
||||
{
|
||||
If: !user.Certified,
|
||||
Message: because + "your nonshy account is not Certified, or its Certified status has been revoked.",
|
||||
},
|
||||
{
|
||||
If: user.IsShy(),
|
||||
Message: because + "you had updated your nonshy profile to become too private. " +
|
||||
"'Shy Accounts' are not permitted to remain in the chat room.",
|
||||
},
|
||||
{
|
||||
If: user.Status == models.UserStatusDisabled,
|
||||
Message: because + "you have deactivated your nonshy account.",
|
||||
},
|
||||
{
|
||||
If: user.Status == models.UserStatusBanned,
|
||||
Message: because + "your nonshy account has been banned.",
|
||||
},
|
||||
{
|
||||
// Catch-all for any non-active user status.
|
||||
If: user.Status != models.UserStatusActive,
|
||||
Message: because + "your nonshy account is no longer eligible to remain in the chat room.",
|
||||
},
|
||||
}
|
||||
|
||||
for _, reason := range reasons {
|
||||
if reason.If {
|
||||
i, err := DisconnectUserNow(user, reason.Message)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Were they online and were removed? Notify the admin for visibility.
|
||||
if i > 0 {
|
||||
fb := &models.Feedback{
|
||||
Intent: "report",
|
||||
Subject: "Auto-Disconnect from Chat",
|
||||
UserID: user.ID,
|
||||
TableName: "users",
|
||||
TableID: user.ID,
|
||||
Message: fmt.Sprintf(
|
||||
"A user was automatically disconnected from the chat room!\n\n"+
|
||||
"* Username: %s\n"+
|
||||
"* Number of users removed: %d\n"+
|
||||
"* Message sent to them: %s\n\n"+
|
||||
"Note: this is an informative message only. Users are expected to be removed from "+
|
||||
"chat when they do things such as deactivate their account, or private their profile "+
|
||||
"or pictures, and thus become ineligible to remain in the chat room.",
|
||||
user.Username,
|
||||
i,
|
||||
reason.Message,
|
||||
),
|
||||
}
|
||||
|
||||
// Save the feedback.
|
||||
if err := models.CreateFeedback(fb); err != nil {
|
||||
log.Error("Couldn't save feedback from user updating their DOB: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// First removal reason wins.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// DisconnectUserNow tells the chat room to remove the user now if they are presently online.
|
||||
func DisconnectUserNow(user *models.User, message string) (int, error) {
|
||||
// API request struct for BareRTC /api/block/now endpoint.
|
||||
var request = struct {
|
||||
APIKey string
|
||||
Usernames []string
|
||||
Message string
|
||||
Kick bool
|
||||
}{
|
||||
APIKey: config.Current.CronAPIKey,
|
||||
Usernames: []string{
|
||||
user.Username,
|
||||
},
|
||||
Message: message,
|
||||
Kick: false,
|
||||
}
|
||||
|
||||
type response struct {
|
||||
OK bool
|
||||
Removed int
|
||||
Error string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// JSON request body.
|
||||
jsonStr, err := json.Marshal(request)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Make the API request to BareRTC.
|
||||
var url = strings.TrimSuffix(config.Current.BareRTC.URL, "/") + "/api/disconnect/now"
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonStr))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Ingest the JSON response to see the count and error.
|
||||
var (
|
||||
result response
|
||||
body, _ = io.ReadAll(resp.Body)
|
||||
)
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK || !result.OK {
|
||||
log.Error("DisconnectUserNow: error from BareRTC: status %d body %s", resp.StatusCode, body)
|
||||
return result.Removed, errors.New(result.Error)
|
||||
}
|
||||
|
||||
return result.Removed, nil
|
||||
}
|
|
@ -4,6 +4,8 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/chat"
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
"code.nonshy.com/nonshy/website/pkg/models"
|
||||
"code.nonshy.com/nonshy/website/pkg/session"
|
||||
"code.nonshy.com/nonshy/website/pkg/templates"
|
||||
|
@ -42,6 +44,11 @@ func Deactivate() http.HandlerFunc {
|
|||
session.Flash(w, r, "Your account has been deactivated and you are now logged out. If you wish to re-activate your account, sign in again with your username and password.")
|
||||
templates.Redirect(w, "/")
|
||||
|
||||
// Maybe kick them from chat if this deletion makes them into a Shy Account.
|
||||
if _, err := chat.MaybeDisconnectUser(currentUser); err != nil {
|
||||
log.Error("chat.MaybeDisconnectUser(%s#%d): %s", currentUser.Username, currentUser.ID, err)
|
||||
}
|
||||
|
||||
// Log the change.
|
||||
models.LogEvent(currentUser, nil, models.ChangeLogLifecycle, "users", currentUser.ID, "Deactivated their account.")
|
||||
return
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/chat"
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
"code.nonshy.com/nonshy/website/pkg/models"
|
||||
"code.nonshy.com/nonshy/website/pkg/models/deletion"
|
||||
"code.nonshy.com/nonshy/website/pkg/session"
|
||||
|
@ -43,6 +45,11 @@ func Delete() http.HandlerFunc {
|
|||
session.Flash(w, r, "Your account has been deleted.")
|
||||
templates.Redirect(w, "/")
|
||||
|
||||
// Kick them from the chat room if they are online.
|
||||
if _, err := chat.DisconnectUserNow(currentUser, "You have been signed out of chat because you had deleted your account."); err != nil {
|
||||
log.Error("chat.MaybeDisconnectUser(%s#%d): %s", currentUser.Username, currentUser.ID, err)
|
||||
}
|
||||
|
||||
// Log the change.
|
||||
models.LogDeleted(nil, nil, "users", currentUser.ID, fmt.Sprintf("Username %s has deleted their account.", currentUser.Username), nil)
|
||||
return
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/chat"
|
||||
"code.nonshy.com/nonshy/website/pkg/config"
|
||||
"code.nonshy.com/nonshy/website/pkg/geoip"
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
|
@ -426,6 +427,11 @@ func Settings() http.HandlerFunc {
|
|||
session.FlashError(w, r, "Unknown POST intent value. Please try again.")
|
||||
}
|
||||
|
||||
// Maybe kick them from the chat room if they had become a Shy Account.
|
||||
if _, err := chat.MaybeDisconnectUser(user); err != nil {
|
||||
log.Error("chat.MaybeDisconnectUser(%s#%d): %s", user.Username, user.ID, err)
|
||||
}
|
||||
|
||||
templates.Redirect(w, r.URL.Path+hashtag+".")
|
||||
return
|
||||
}
|
||||
|
|
|
@ -102,14 +102,6 @@ func Signup() http.HandlerFunc {
|
|||
vars["Email"] = email
|
||||
vars["Username"] = username
|
||||
|
||||
// Is the app not configured to send email?
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
// Validate the email.
|
||||
if _, err := nm.ParseAddress(email); err != nil {
|
||||
session.FlashError(w, r, "The email address you entered is not valid: %s", err)
|
||||
|
@ -157,6 +149,16 @@ func Signup() http.HandlerFunc {
|
|||
session.FlashError(w, r, "Error creating a link to send you: %s", err)
|
||||
}
|
||||
|
||||
// Is the app not configured to send email?
|
||||
if !config.Current.Mail.Enabled && !config.SkipEmailVerification {
|
||||
// Log the signup token for local dev.
|
||||
log.Error("Signup: the app is not configured to send email. To continue, visit the URL: /signup?token=%s", token.Token)
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
err := mail.Send(mail.Message{
|
||||
To: email,
|
||||
Subject: "Verify your e-mail address",
|
||||
|
|
|
@ -6,7 +6,9 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/chat"
|
||||
"code.nonshy.com/nonshy/website/pkg/config"
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
"code.nonshy.com/nonshy/website/pkg/models"
|
||||
"code.nonshy.com/nonshy/website/pkg/models/deletion"
|
||||
"code.nonshy.com/nonshy/website/pkg/session"
|
||||
|
@ -157,6 +159,11 @@ func UserActions() http.HandlerFunc {
|
|||
session.Flash(w, r, "User ban status updated!")
|
||||
templates.Redirect(w, "/u/"+user.Username)
|
||||
|
||||
// Maybe kick them from chat room now.
|
||||
if _, err := chat.MaybeDisconnectUser(user); err != nil {
|
||||
log.Error("chat.MaybeDisconnectUser(%s#%d): %s", user.Username, user.ID, err)
|
||||
}
|
||||
|
||||
// Log the change.
|
||||
models.LogEvent(user, currentUser, models.ChangeLogBanned, "users", currentUser.ID, fmt.Sprintf("User ban status updated to: %s", status))
|
||||
return
|
||||
|
@ -196,6 +203,11 @@ func UserActions() http.HandlerFunc {
|
|||
}
|
||||
templates.Redirect(w, "/admin")
|
||||
|
||||
// Kick them from the chat room if they are online.
|
||||
if _, err := chat.DisconnectUserNow(user, "You have been signed out of chat because your account has been deleted."); err != nil {
|
||||
log.Error("chat.MaybeDisconnectUser(%s#%d): %s", user.Username, user.ID, err)
|
||||
}
|
||||
|
||||
// Log the change.
|
||||
models.LogDeleted(nil, currentUser, "users", user.ID, fmt.Sprintf("Username %s has been deleted by an admin.", user.Username), nil)
|
||||
return
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/chat"
|
||||
"code.nonshy.com/nonshy/website/pkg/config"
|
||||
"code.nonshy.com/nonshy/website/pkg/geoip"
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
|
@ -90,6 +91,11 @@ func Certification() http.HandlerFunc {
|
|||
// Log the change.
|
||||
models.LogDeleted(currentUser, nil, "certification_photos", currentUser.ID, "Removed their certification photo.", cert)
|
||||
|
||||
// Kick them from the chat room if they are online.
|
||||
if _, err := chat.MaybeDisconnectUser(currentUser); err != nil {
|
||||
log.Error("chat.MaybeDisconnectUser(%s#%d): %s", currentUser.Username, currentUser.ID, err)
|
||||
}
|
||||
|
||||
session.Flash(w, r, "Your certification photo has been deleted.")
|
||||
templates.Redirect(w, r.URL.Path)
|
||||
return
|
||||
|
@ -141,6 +147,11 @@ func Certification() http.HandlerFunc {
|
|||
session.FlashError(w, r, "Error saving your User data: %s", err)
|
||||
}
|
||||
|
||||
// Kick them from the chat room if they are online.
|
||||
if _, err := chat.MaybeDisconnectUser(currentUser); err != nil {
|
||||
log.Error("chat.MaybeDisconnectUser(%s#%d): %s", currentUser.Username, currentUser.ID, err)
|
||||
}
|
||||
|
||||
// Notify the admin email to check out this photo.
|
||||
if err := mail.Send(mail.Message{
|
||||
To: config.Current.AdminEmail,
|
||||
|
@ -306,6 +317,11 @@ func AdminCertification() http.HandlerFunc {
|
|||
// Log the change.
|
||||
models.LogEvent(user, currentUser, models.ChangeLogRejected, "certification_photos", user.ID, "Rejected the certification photo with comment: "+comment)
|
||||
|
||||
// Kick them from the chat room if they are online.
|
||||
if _, err := chat.MaybeDisconnectUser(user); err != nil {
|
||||
log.Error("chat.MaybeDisconnectUser(%s#%d): %s", user.Username, user.ID, err)
|
||||
}
|
||||
|
||||
// Did we silently ignore it?
|
||||
if comment == "(ignore)" {
|
||||
session.FlashError(w, r, "The certification photo was ignored with no comment, and will not notify the sender.")
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"code.nonshy.com/nonshy/website/pkg/chat"
|
||||
"code.nonshy.com/nonshy/website/pkg/config"
|
||||
"code.nonshy.com/nonshy/website/pkg/log"
|
||||
"code.nonshy.com/nonshy/website/pkg/models"
|
||||
|
@ -128,8 +129,6 @@ func Edit() http.HandlerFunc {
|
|||
setProfilePic = false
|
||||
}
|
||||
|
||||
log.Error("SAVING PHOTO: %+v", photo)
|
||||
|
||||
if err := photo.Save(); err != nil {
|
||||
session.FlashError(w, r, "Couldn't save photo: %s", err)
|
||||
}
|
||||
|
@ -149,6 +148,11 @@ func Edit() http.HandlerFunc {
|
|||
// Log the change.
|
||||
models.LogUpdated(currentUser, requestUser, "photos", photo.ID, "Updated the photo's settings.", diffs)
|
||||
|
||||
// Maybe kick them from the chat if this photo save makes them a Shy Account.
|
||||
if _, err := chat.MaybeDisconnectUser(currentUser); err != nil {
|
||||
log.Error("chat.MaybeDisconnectUser(%s#%d): %s", currentUser.Username, currentUser.ID, err)
|
||||
}
|
||||
|
||||
// If this picture has moved to Private, revoke any notification we gave about it before.
|
||||
if goingPrivate || goingCircle {
|
||||
log.Info("The picture is GOING PRIVATE (to %s), revoke any notifications about it", photo.Visibility)
|
||||
|
@ -272,6 +276,11 @@ func Delete() http.HandlerFunc {
|
|||
|
||||
session.Flash(w, r, "Photo deleted!")
|
||||
|
||||
// Maybe kick them from chat if this deletion makes them into a Shy Account.
|
||||
if _, err := chat.MaybeDisconnectUser(currentUser); err != nil {
|
||||
log.Error("chat.MaybeDisconnectUser(%s#%d): %s", currentUser.Username, currentUser.ID, err)
|
||||
}
|
||||
|
||||
// Return the user to their gallery.
|
||||
templates.Redirect(w, "/u/"+currentUser.Username+"/photos")
|
||||
return
|
||||
|
|
|
@ -94,6 +94,7 @@
|
|||
window.alert(`NOTE: Your input was interpreted to be in MM/DD/YYYY order and has been read as: ${answer}`);
|
||||
} else if (!answer.match(/^\d{4}-\d{2}-\d{2}/)) {
|
||||
window.alert(`Please enter the date in YYYY-MM-DD format.`);
|
||||
return;
|
||||
}
|
||||
|
||||
$dob.value = answer;
|
||||
|
|
|
@ -154,6 +154,9 @@
|
|||
<p class="help">
|
||||
Your birthdate won't be shown to other members and is used to show
|
||||
your current age on your profile. Please enter your correct birthdate.
|
||||
<br>
|
||||
On mobile and scrolling for your year is tedious?
|
||||
<a href="#" id="manualEntry">Click to type your birthdate instead.</a>
|
||||
</p>
|
||||
</div>
|
||||
{{end}}
|
||||
|
@ -225,8 +228,32 @@ window.addEventListener("DOMContentLoaded", (event) => {
|
|||
})
|
||||
};
|
||||
|
||||
if ($username != undefined) {
|
||||
$username.addEventListener("change", onChange);
|
||||
$username.addEventListener("blur", onChange);
|
||||
}
|
||||
|
||||
// DOB manual entry script, on signup completion page.
|
||||
let $manualEntry = document.querySelector("#manualEntry"),
|
||||
$dob = document.querySelector("#dob");
|
||||
|
||||
if ($manualEntry != undefined) {
|
||||
$manualEntry.addEventListener("click", function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
let answer = window.prompt("Enter your birthdate in 'YYYY-MM-DD' format").trim().replace(/\//g, '-');
|
||||
if (answer.match(/^(\d{2})-(\d{2})-(\d{4})/)) {
|
||||
let group = answer.match(/^(\d{2})-(\d{2})-(\d{4})/);
|
||||
answer = `${group[3]}-${group[1]}-${group[2]}`;
|
||||
window.alert(`NOTE: Your input was interpreted to be in MM/DD/YYYY order and has been read as: ${answer}`);
|
||||
} else if (!answer.match(/^\d{4}-\d{2}-\d{2}/)) {
|
||||
window.alert(`Please enter the date in YYYY-MM-DD format.`);
|
||||
return;
|
||||
}
|
||||
|
||||
$dob.value = answer;
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
||||
|
|
Loading…
Reference in New Issue
Block a user