742a5fa1af
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.
247 lines
7.0 KiB
Go
247 lines
7.0 KiB
Go
package admin
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"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"
|
|
"code.nonshy.com/nonshy/website/pkg/templates"
|
|
)
|
|
|
|
// Mark a user photo as Explicit for them.
|
|
func MarkPhotoExplicit() http.HandlerFunc {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
photoID uint64
|
|
next = r.FormValue("next")
|
|
)
|
|
|
|
if !strings.HasPrefix(next, "/") {
|
|
next = "/"
|
|
}
|
|
|
|
// Get current user.
|
|
currentUser, err := session.CurrentUser(r)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Failed to get current user: %s", err)
|
|
templates.Redirect(w, "/")
|
|
return
|
|
}
|
|
|
|
if idInt, err := strconv.Atoi(r.FormValue("photo_id")); err == nil {
|
|
photoID = uint64(idInt)
|
|
} else {
|
|
session.FlashError(w, r, "Invalid or missing photo_id parameter: %s", err)
|
|
templates.Redirect(w, next)
|
|
return
|
|
}
|
|
|
|
// Get this photo.
|
|
photo, err := models.GetPhoto(photoID)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Didn't find photo ID in database: %s", err)
|
|
templates.Redirect(w, next)
|
|
return
|
|
}
|
|
|
|
photo.Explicit = true
|
|
if err := photo.Save(); err != nil {
|
|
session.FlashError(w, r, "Couldn't save photo: %s", err)
|
|
} else {
|
|
session.Flash(w, r, "Marked photo as Explicit!")
|
|
}
|
|
|
|
// Log the change.
|
|
models.LogUpdated(&models.User{ID: photo.UserID}, currentUser, "photos", photo.ID, "Marked explicit by admin action.", []models.FieldDiff{
|
|
models.NewFieldDiff("Explicit", false, true),
|
|
})
|
|
|
|
templates.Redirect(w, next)
|
|
})
|
|
}
|
|
|
|
// Admin actions against a user account.
|
|
func UserActions() http.HandlerFunc {
|
|
tmpl := templates.Must("admin/user_actions.html")
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var (
|
|
intent = r.FormValue("intent")
|
|
confirm = r.Method == http.MethodPost
|
|
reason = r.FormValue("reason") // for impersonation
|
|
userId uint64
|
|
)
|
|
|
|
// Get current user.
|
|
currentUser, err := session.CurrentUser(r)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Failed to get current user: %s", err)
|
|
templates.Redirect(w, "/")
|
|
return
|
|
}
|
|
|
|
if idInt, err := strconv.Atoi(r.FormValue("user_id")); err == nil {
|
|
userId = uint64(idInt)
|
|
} else {
|
|
session.FlashError(w, r, "Invalid or missing user_id parameter: %s", err)
|
|
templates.Redirect(w, "/admin")
|
|
return
|
|
}
|
|
|
|
// Get this user.
|
|
user, err := models.GetUser(userId)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Didn't find user ID in database: %s", err)
|
|
templates.Redirect(w, "/admin")
|
|
return
|
|
}
|
|
|
|
// Template variables.
|
|
var vars = map[string]interface{}{
|
|
"Intent": intent,
|
|
"User": user,
|
|
}
|
|
|
|
switch intent {
|
|
case "insights":
|
|
// Admin insights (peek at block lists, etc.)
|
|
if !currentUser.HasAdminScope(config.ScopeUserInsight) {
|
|
session.FlashError(w, r, "Missing admin scope: %s", config.ScopeUserInsight)
|
|
templates.Redirect(w, "/admin")
|
|
return
|
|
}
|
|
|
|
insights, err := models.GetBlocklistInsights(user)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Error getting blocklist insights: %s", err)
|
|
}
|
|
vars["BlocklistInsights"] = insights
|
|
case "impersonate":
|
|
// Scope check.
|
|
if !currentUser.HasAdminScope(config.ScopeUserImpersonate) {
|
|
session.FlashError(w, r, "Missing admin scope: %s", config.ScopeUserImpersonate)
|
|
templates.Redirect(w, "/admin")
|
|
return
|
|
}
|
|
|
|
if confirm {
|
|
if err := session.ImpersonateUser(w, r, user, currentUser, reason); err != nil {
|
|
session.FlashError(w, r, "Failed to impersonate user: %s", err)
|
|
} else {
|
|
session.Flash(w, r, "You are now impersonating %s", user.Username)
|
|
templates.Redirect(w, "/me")
|
|
return
|
|
}
|
|
}
|
|
case "ban":
|
|
// Scope check.
|
|
if !currentUser.HasAdminScope(config.ScopeUserBan) {
|
|
session.FlashError(w, r, "Missing admin scope: %s", config.ScopeUserBan)
|
|
templates.Redirect(w, "/admin")
|
|
return
|
|
}
|
|
|
|
if confirm {
|
|
status := r.PostFormValue("status")
|
|
if status == "active" {
|
|
user.Status = models.UserStatusActive
|
|
} else if status == "banned" {
|
|
user.Status = models.UserStatusBanned
|
|
}
|
|
|
|
user.Save()
|
|
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
|
|
}
|
|
case "promote":
|
|
// Scope check.
|
|
if !currentUser.HasAdminScope(config.ScopeUserPromote) {
|
|
session.FlashError(w, r, "Missing admin scope: %s", config.ScopeUserPromote)
|
|
templates.Redirect(w, "/admin")
|
|
return
|
|
}
|
|
|
|
if confirm {
|
|
action := r.PostFormValue("action")
|
|
user.IsAdmin = action == "promote"
|
|
user.Save()
|
|
session.Flash(w, r, "User admin status updated!")
|
|
templates.Redirect(w, "/u/"+user.Username)
|
|
|
|
// Log the change.
|
|
models.LogEvent(user, currentUser, models.ChangeLogAdmin, "users", currentUser.ID, fmt.Sprintf("User admin status updated to: %s", action))
|
|
return
|
|
}
|
|
case "delete":
|
|
// Scope check.
|
|
if !currentUser.HasAdminScope(config.ScopeUserDelete) {
|
|
session.FlashError(w, r, "Missing admin scope: %s", config.ScopeUserDelete)
|
|
templates.Redirect(w, "/admin")
|
|
return
|
|
}
|
|
|
|
if confirm {
|
|
if err := deletion.DeleteUser(user); err != nil {
|
|
session.FlashError(w, r, "Failed when deleting the user: %s", err)
|
|
} else {
|
|
session.Flash(w, r, "User has been deleted!")
|
|
}
|
|
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
|
|
}
|
|
default:
|
|
session.FlashError(w, r, "Unsupported admin user intent: %s", intent)
|
|
templates.Redirect(w, "/admin")
|
|
return
|
|
}
|
|
|
|
if err := tmpl.Execute(w, r, vars); err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
})
|
|
}
|
|
|
|
// Un-impersonate a user account.
|
|
func Unimpersonate() http.HandlerFunc {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
sess := session.Get(r)
|
|
if sess.Impersonator > 0 {
|
|
user, err := models.GetUser(sess.Impersonator)
|
|
if err != nil {
|
|
session.FlashError(w, r, "Couldn't unimpersonate: impersonator (%d) is not an admin!", user.ID)
|
|
templates.Redirect(w, "/")
|
|
return
|
|
}
|
|
|
|
session.LoginUser(w, r, user)
|
|
session.Flash(w, r, "No longer impersonating.")
|
|
templates.Redirect(w, "/")
|
|
}
|
|
templates.Redirect(w, "/")
|
|
})
|
|
}
|