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.
This commit is contained in:
parent
76963ca514
commit
345285d7a3
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -11,12 +11,112 @@
|
|||
|
||||
<div class="block p-4">
|
||||
<div class="columns">
|
||||
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<header class="card-header has-background-warning">
|
||||
<p class="card-header-title has-text-dark-dark">
|
||||
<i class="fa fa-book mr-2"></i>
|
||||
Admin Guidelines <span class="tag is-success ml-3">NEW!</span>
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="card-content content">
|
||||
<h2>Respect the privacy of our users</h2>
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
We do not snoop on our users' Direct Messages unless they <strong>report</strong> a conversation
|
||||
for us to check out. The only way to access DMs is to <a href="#impersonate">impersonate</a> a
|
||||
user, which can't be done in secret.
|
||||
</li>
|
||||
<li>
|
||||
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.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>What we moderate</h2>
|
||||
|
||||
Admin users are only expected to help moderate the following areas of the site:
|
||||
|
||||
<h3>1. User profile photos</h3>
|
||||
|
||||
<p>
|
||||
Every picture uploaded to a user's profile page can be seen by admin users. The
|
||||
<a href="/photo/gallery?admin_view=true">admin gallery view</a> can find <strong>all</strong>
|
||||
user photos, whether private or friends-only, whether opted-in for the Site Gallery or not.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<strong>Be careful</strong> 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.
|
||||
</p>
|
||||
|
||||
<h3>2. The Forums</h3>
|
||||
|
||||
<p>
|
||||
Keep up with the <a href="/forum/newest">newest</a> forum posts and generally make sure
|
||||
people aren't fighting or uploading inappropriate photos to one of the few photo boards.
|
||||
</p>
|
||||
|
||||
<h3>3. Reported DMs only</h3>
|
||||
|
||||
<p>
|
||||
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 <a href="#impersonate">impersonate</a> the reporter and will be logged
|
||||
- see "Impersonating users," below.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Impersonating users</h2>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You will need to write a <strong>reason</strong> 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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<p>
|
||||
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="card block">
|
||||
<header class="card-header has-background-link">
|
||||
<p class="card-header-title has-text-light">
|
||||
<span class="icon"><i class="fa fa-gavel"></i></span>
|
||||
Admin Dashboard
|
||||
<i class="fa fa-gavel mr-2"></i>
|
||||
Admin Menu
|
||||
</p>
|
||||
</header>
|
||||
|
||||
|
@ -24,34 +124,28 @@
|
|||
<ul class="menu-list">
|
||||
<li>
|
||||
<a href="/admin/photo/certification">
|
||||
<span class="icon"><i class="fa fa-certificate"></i></span>
|
||||
<i class="fa fa-certificate mr-2"></i>
|
||||
Certification Photos
|
||||
{{if .NavCertificationPhotos}}<span class="tag is-danger ml-1">{{.NavCertificationPhotos}}</span>{{end}}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/feedback">
|
||||
<span class="icon"><i class="fa fa-message"></i></span>
|
||||
<i class="fa fa-message mr-2"></i>
|
||||
Feedback & User Reports
|
||||
{{if .NavAdminFeedback}}<span class="tag is-danger ml-1">{{.NavAdminFeedback}}</span>{{end}}
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/photo/gallery?admin_view=true">
|
||||
<i class="fa fa-image mr-2"></i>
|
||||
Gallery: Admin View
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<div class="card">
|
||||
<header class="card-header has-background-warning">
|
||||
<p class="card-header-title">Notifications</p>
|
||||
</header>
|
||||
|
||||
<div class="card-content">
|
||||
TBD.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -65,8 +65,37 @@
|
|||
Please respect user privacy and only impersonate an account as needed to diagnose
|
||||
a customer support issue or similar.
|
||||
</p>
|
||||
<p>
|
||||
<strong class="has-text-danger">
|
||||
This event is logged and will be noticed.
|
||||
</strong>
|
||||
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 <a href="/admin/feedback?intent=report">Report</a> to
|
||||
the admin dashboard. Reports can be acknowledged, but not deleted.
|
||||
</p>
|
||||
<p>
|
||||
Good reasons may include:
|
||||
<ul>
|
||||
<li>
|
||||
I need to diagnose a bug report given by one of our users
|
||||
(briefly describe what the bug is; e.g. user saw a database error
|
||||
at the top of a page).
|
||||
</li>
|
||||
<li>
|
||||
A user has reported a Direct Message conversation and I need to
|
||||
take a look at the context. (There is no other way to read user DMs)
|
||||
</li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<textarea class="textarea mb-4"
|
||||
cols="80" rows="4"
|
||||
name="reason"
|
||||
placeholder="Reason"
|
||||
required></textarea>
|
||||
|
||||
<div class="field has-text-centered">
|
||||
<button type="submit" class="button is-success">
|
||||
Log in as {{.User.Username}}
|
||||
|
|
32
web/templates/email/admin_impersonate.html
Normal file
32
web/templates/email/admin_impersonate.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
{{define "content"}}
|
||||
<html>
|
||||
<body bakground="#ffffff" color="#000000" link="#0000FF" vlink="#990099" alink="#FF0000">
|
||||
<basefont face="Arial,Helvetica,sans-serif" size="3" color="#000000"></basefont>
|
||||
|
||||
<h1>User impersonate</h1>
|
||||
|
||||
<p>
|
||||
Dear website administrators,
|
||||
</p>
|
||||
|
||||
<p>
|
||||
An admin user <strong>{{.Data.Impersonator.Username}}</strong> has used the "Impersonate user"
|
||||
feature to become user <strong>{{.Data.User.Username}}</strong>. The reason they have listed
|
||||
for doing so is as follows:
|
||||
</p>
|
||||
|
||||
{{.Data.Reason}}
|
||||
|
||||
<hr>
|
||||
|
||||
<p>
|
||||
To view this message on the admin dashboard, please visit:
|
||||
<a href="{{.Data.AdminURL}}">{{.Data.AdminURL}}</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This is an automated e-mail; do not reply to this message.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
|
@ -285,18 +285,26 @@
|
|||
<span>Manage who can see <strong>my</strong> private photos</span>
|
||||
</a>
|
||||
</div>
|
||||
{{else if and (not .IsSiteGallery) (not .IsMyPrivateUnlockedFor)}}
|
||||
{{else if not .IsSiteGallery}}
|
||||
<div class="block">
|
||||
{{if not .IsMyPrivateUnlockedFor}}
|
||||
<a href="/photo/private/share?to={{.User.Username}}" class="has-text-private">
|
||||
<span class="icon"><i class="fa fa-unlock"></i></span>
|
||||
<span>Grant <strong>{{.User.Username}}</strong> access to see <strong>my</strong> private photos</span>
|
||||
</a>
|
||||
{{else}}
|
||||
<span class="icon"><i class="fa fa-unlock has-text-private"></i></span>
|
||||
<span>You had granted <strong>{{.User.Username}}</strong> access to see <strong>your</strong> private photos.</span>
|
||||
<a href="/photo/private">Manage that here.</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else if and (not .IsSiteGallery) .IsMyPrivateUnlockedFor}}
|
||||
<div class="block">
|
||||
<span class="icon"><i class="fa fa-unlock has-text-private"></i></span>
|
||||
<span>You had granted <strong>{{.User.Username}}</strong> access to see <strong>your</strong> private photos.</span>
|
||||
<a href="/photo/private">Manage that here.</a>
|
||||
{{end}}
|
||||
|
||||
{{if .AreWeGrantedPrivate}}
|
||||
<div class="block mt-0">
|
||||
<span class="icon"><i class="fa fa-eye has-text-private"></i></span>
|
||||
<strong>{{.User.Username}}</strong> has <span class="has-text-private">granted</span> you
|
||||
access to see their private photos.
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user