From 345285d7a3b2e27f2660631838893086ada91e1b Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sat, 24 Dec 2022 23:00:59 -0800 Subject: [PATCH] Add Admin Guidelines to dashboard * A reason must be entered to impersonate a user, and it triggers a Report and email notification to the admin. * User gallery pages will show at the top whether the user had granted you access to their private photos. --- pkg/controller/admin/feedback.go | 2 +- pkg/controller/admin/user_actions.go | 3 +- pkg/controller/photo/user_gallery.go | 1 + pkg/session/session.go | 37 +++++- web/templates/admin/dashboard.html | 126 ++++++++++++++++++--- web/templates/admin/user_actions.html | 29 +++++ web/templates/email/admin_impersonate.html | 32 ++++++ web/templates/photo/gallery.html | 20 +++- 8 files changed, 225 insertions(+), 25 deletions(-) create mode 100644 web/templates/email/admin_impersonate.html diff --git a/pkg/controller/admin/feedback.go b/pkg/controller/admin/feedback.go index a429ca4..b8e7a06 100644 --- a/pkg/controller/admin/feedback.go +++ b/pkg/controller/admin/feedback.go @@ -80,7 +80,7 @@ func Feedback() http.HandlerFunc { if err != nil { session.FlashError(w, r, "Couldn't get reporting user ID %d: %s", fb.UserID, err) } else { - if err := session.ImpersonateUser(w, r, user, currentUser); err != nil { + if err := session.ImpersonateUser(w, r, user, currentUser, "Clicked from user reported Message via admin dashboard"); err != nil { session.FlashError(w, r, "Couldn't impersonate user: %s", err) } else { // Redirect to the thread. diff --git a/pkg/controller/admin/user_actions.go b/pkg/controller/admin/user_actions.go index fa456b6..d8166df 100644 --- a/pkg/controller/admin/user_actions.go +++ b/pkg/controller/admin/user_actions.go @@ -17,6 +17,7 @@ func UserActions() http.HandlerFunc { var ( intent = r.FormValue("intent") confirm = r.Method == http.MethodPost + reason = r.FormValue("reason") // for impersonation userId uint64 ) @@ -47,7 +48,7 @@ func UserActions() http.HandlerFunc { switch intent { case "impersonate": if confirm { - if err := session.ImpersonateUser(w, r, user, currentUser); err != nil { + 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) diff --git a/pkg/controller/photo/user_gallery.go b/pkg/controller/photo/user_gallery.go index 0ad749e..ae0157d 100644 --- a/pkg/controller/photo/user_gallery.go +++ b/pkg/controller/photo/user_gallery.go @@ -108,6 +108,7 @@ func UserPhotos() http.HandlerFunc { var vars = map[string]interface{}{ "IsOwnPhotos": currentUser.ID == user.ID, "IsMyPrivateUnlockedFor": isGranted, // have WE granted THIS USER to see our private pics? + "AreWeGrantedPrivate": isGrantee, // have THEY granted US private photo access. "User": user, "Photos": photos, "PhotoCount": models.CountPhotos(user.ID), diff --git a/pkg/session/session.go b/pkg/session/session.go index 0458261..515dd35 100644 --- a/pkg/session/session.go +++ b/pkg/session/session.go @@ -11,6 +11,7 @@ import ( "code.nonshy.com/nonshy/website/pkg/config" "code.nonshy.com/nonshy/website/pkg/log" + "code.nonshy.com/nonshy/website/pkg/mail" "code.nonshy.com/nonshy/website/pkg/models" "code.nonshy.com/nonshy/website/pkg/redis" "github.com/google/uuid" @@ -175,7 +176,7 @@ func LoginUser(w http.ResponseWriter, r *http.Request, u *models.User) error { } // ImpersonateUser assumes the role of the user impersonated by an admin uid. -func ImpersonateUser(w http.ResponseWriter, r *http.Request, u *models.User, impersonator *models.User) error { +func ImpersonateUser(w http.ResponseWriter, r *http.Request, u *models.User, impersonator *models.User, reason string) error { if u == nil || u.ID == 0 { return errors.New("not a valid user account") } @@ -189,6 +190,40 @@ func ImpersonateUser(w http.ResponseWriter, r *http.Request, u *models.User, imp sess.Impersonator = impersonator.ID sess.Save(w) + // Issue an admin notification that this has happened. + // NOTE: not DRY compared to contact.go + fb := &models.Feedback{ + Intent: "report", + Subject: "'Impersonate user' has been used", + TableName: "users", + TableID: impersonator.ID, + Message: fmt.Sprintf( + "The admin user **%s** (id:%d) has impersonated user **%s** (id:%d)\n\n"+ + "The reason they have given:\n\n%s", + impersonator.Username, impersonator.ID, + u.Username, u.ID, reason, + ), + } + + if err := models.CreateFeedback(fb); err != nil { + FlashError(w, r, "Couldn't create admin notification: %s", err) + } + + // Email the admins. + if err := mail.Send(mail.Message{ + To: config.Current.AdminEmail, + Subject: "Admin 'user impersonate' has been used", + Template: "email/admin_impersonate.html", + Data: map[string]interface{}{ + "Impersonator": impersonator, + "User": u, + "Reason": reason, + "AdminURL": config.Current.BaseURL + "/admin/feedback", + }, + }); err != nil { + log.Error("/contact page: couldn't send email: %s", err) + } + return u.Save() } diff --git a/web/templates/admin/dashboard.html b/web/templates/admin/dashboard.html index ed28c30..cb272a6 100644 --- a/web/templates/admin/dashboard.html +++ b/web/templates/admin/dashboard.html @@ -11,12 +11,112 @@
+ +
+
+
+

+ + Admin Guidelines NEW! +

+
+ +
+

Respect the privacy of our users

+ +
    +
  • + We do not snoop on our users' Direct Messages unless they report a conversation + for us to check out. The only way to access DMs is to impersonate a + user, which can't be done in secret. +
  • +
  • + We treat the Certification Photos as sensitive information. Go there only when a certification + photo is pending approval (red notification badges will guide the way). Do not download or leak + these images; be respectful. +
  • +
+ +

What we moderate

+ + Admin users are only expected to help moderate the following areas of the site: + +

1. User profile photos

+ +

+ Every picture uploaded to a user's profile page can be seen by admin users. The + admin gallery view can find all + user photos, whether private or friends-only, whether opted-in for the Site Gallery or not. +

+ +

+ Be careful not to "Like" or comment on a picture if the user marked it + "Friends only" or "Private" and they wouldn't expect you to have been able to see it. "Like" + and "Comment" buttons are hidden in the admin gallery view to reduce accidents but they are + functional on the user's own gallery page. +

+ +

2. The Forums

+ +

+ Keep up with the newest forum posts and generally make sure + people aren't fighting or uploading inappropriate photos to one of the few photo boards. +

+ +

3. Reported DMs only

+ +

+ If a user reports a Direct Message conversation + they're having, a link to view that chat thread will be available from the report. + This will impersonate the reporter and will be logged + - see "Impersonating users," below. +

+ +

+ DMs are text-based only, so users won't be sending any image attachments that need + moderating and their privacy is to be respected. A user may report a problematic + conversation for us to take a look at. +

+ +
+ +

Impersonating users

+ +

+ From a user's profile page you can "impersonate," or log in as them. You should almost + never need to do this, and only to diagnose a reported issue from their account or + something like that. +

+ +

+ You will need to write a reason for impersonating a user. The event is + logged and will be e-mailed to the admin team along with your reason. The admin team is + alerted any time an Impersonate action is performed. +

+ +

+ Note: when you impersonate, their "Last logged in at" time is not updated by your actions. + So if a user were last seen a month ago, they will still appear last seen a month ago. + But other interactions you make as their account (e.g. marking notifications read, reading + their unread DMs) will work and may be noticed. +

+ +
+ +

+ +

+ +
+
+
+
- -
-
-
-

Notifications

-
- -
- TBD. -
-
-
diff --git a/web/templates/admin/user_actions.html b/web/templates/admin/user_actions.html index dbcdb5b..8c40018 100644 --- a/web/templates/admin/user_actions.html +++ b/web/templates/admin/user_actions.html @@ -65,8 +65,37 @@ Please respect user privacy and only impersonate an account as needed to diagnose a customer support issue or similar.

+

+ + This event is logged and will be noticed. + + Write an explanation below why you are impersonating this user. It will + be e-mailed to the admin mailing list and trigger an admin notification + and be logged as a Report to + the admin dashboard. Reports can be acknowledged, but not deleted. +

+

+ Good reasons may include: +

+

+ +
- {{else if and (not .IsSiteGallery) (not .IsMyPrivateUnlockedFor)}} + {{else if not .IsSiteGallery}}
+ {{if not .IsMyPrivateUnlockedFor}} Grant {{.User.Username}} access to see my private photos + {{else}} + + You had granted {{.User.Username}} access to see your private photos. + Manage that here. + {{end}}
- {{else if and (not .IsSiteGallery) .IsMyPrivateUnlockedFor}} -
- - You had granted {{.User.Username}} access to see your private photos. - Manage that here. + {{end}} + + {{if .AreWeGrantedPrivate}} +
+ + {{.User.Username}} has granted you + access to see their private photos.
{{end}}