@@ -16,12 +16,14 @@ import ( | |||
"net/http" | |||
"runtime" | |||
"strconv" | |||
"strings" | |||
"time" | |||
"github.com/gorilla/mux" | |||
"github.com/writeas/impart" | |||
"github.com/writeas/web-core/auth" | |||
"github.com/writeas/web-core/log" | |||
"github.com/writeas/web-core/passgen" | |||
"github.com/writeas/writefreely/appstats" | |||
"github.com/writeas/writefreely/config" | |||
) | |||
@@ -170,11 +172,12 @@ func handleViewAdminUser(app *App, u *User, w http.ResponseWriter, r *http.Reque | |||
Config config.AppCfg | |||
Message string | |||
User *User | |||
Colls []inspectedCollection | |||
LastPost string | |||
TotalPosts int64 | |||
User *User | |||
Colls []inspectedCollection | |||
LastPost string | |||
NewPassword string | |||
TotalPosts int64 | |||
ClearEmail string | |||
}{ | |||
Config: app.cfg.App, | |||
Message: r.FormValue("m"), | |||
@@ -186,6 +189,14 @@ func handleViewAdminUser(app *App, u *User, w http.ResponseWriter, r *http.Reque | |||
if err != nil { | |||
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get user: %v", err)} | |||
} | |||
flashes, _ := getSessionFlashes(app, w, r, nil) | |||
for _, flash := range flashes { | |||
if strings.HasPrefix(flash, "SUCCESS: ") { | |||
p.NewPassword = strings.TrimPrefix(flash, "SUCCESS: ") | |||
p.ClearEmail = p.User.EmailClear(app.keys) | |||
} | |||
} | |||
p.UserPage = NewUserPage(app, r, u, p.User.Username, nil) | |||
p.TotalPosts = app.db.GetUserPostsCount(p.User.ID) | |||
lp, err := app.db.GetUserLastPostTime(p.User.ID) | |||
@@ -230,6 +241,37 @@ func handleViewAdminUser(app *App, u *User, w http.ResponseWriter, r *http.Reque | |||
return nil | |||
} | |||
func handleAdminResetUserPass(app *App, u *User, w http.ResponseWriter, r *http.Request) error { | |||
vars := mux.Vars(r) | |||
username := vars["username"] | |||
if username == "" { | |||
return impart.HTTPError{http.StatusFound, "/admin/users"} | |||
} | |||
// Generate new random password since none supplied | |||
pass := passgen.NewWordish() | |||
hashedPass, err := auth.HashPass([]byte(pass)) | |||
if err != nil { | |||
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not create password hash: %v", err)} | |||
} | |||
userIDVal := r.FormValue("user") | |||
log.Info("ADMIN: Changing user %s password", userIDVal) | |||
id, err := strconv.Atoi(userIDVal) | |||
if err != nil { | |||
return impart.HTTPError{http.StatusBadRequest, fmt.Sprintf("Invalid user ID: %v", err)} | |||
} | |||
err = app.db.ChangePassphrase(int64(id), true, "", hashedPass) | |||
if err != nil { | |||
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not update passphrase: %v", err)} | |||
} | |||
log.Info("ADMIN: Successfully changed.") | |||
addSessionFlash(app, w, r, fmt.Sprintf("SUCCESS: %s", pass), nil) | |||
return impart.HTTPError{http.StatusFound, fmt.Sprintf("/admin/user/%s", username)} | |||
} | |||
func handleViewAdminPages(app *App, u *User, w http.ResponseWriter, r *http.Request) error { | |||
p := struct { | |||
*UserPage | |||
@@ -45,7 +45,7 @@ require ( | |||
github.com/writeas/openssl-go v1.0.0 // indirect | |||
github.com/writeas/saturday v1.7.1 | |||
github.com/writeas/slug v1.2.0 | |||
github.com/writeas/web-core v1.0.0 | |||
github.com/writeas/web-core v1.2.0 | |||
github.com/writefreely/go-nodeinfo v1.2.0 | |||
golang.org/x/crypto v0.0.0-20190208162236-193df9c0f06f | |||
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1 // indirect | |||
@@ -135,6 +135,8 @@ github.com/writeas/slug v1.2.0 h1:EMQ+cwLiOcA6EtFwUgyw3Ge18x9uflUnOnR6bp/J+/g= | |||
github.com/writeas/slug v1.2.0/go.mod h1:RE8shOqQP3YhsfsQe0L3RnuejfQ4Mk+JjY5YJQFubfQ= | |||
github.com/writeas/web-core v1.0.0 h1:5VKkCakQgdKZcbfVKJXtRpc5VHrkflusCl/KRCPzpQ0= | |||
github.com/writeas/web-core v1.0.0/go.mod h1:Si3chV7VWgY8CsV+3gRolMXSO2Vx1ZFAQ/mkrpvmyEE= | |||
github.com/writeas/web-core v1.2.0 h1:CYqvBd+byi1cK4mCr1NZ6CjILuMOFmiFecv+OACcmG0= | |||
github.com/writeas/web-core v1.2.0/go.mod h1:vTYajviuNBAxjctPp2NUYdgjofywVkxUGpeaERF3SfI= | |||
github.com/writefreely/go-nodeinfo v1.2.0 h1:La+YbTCvmpTwFhBSlebWDDL81N88Qf/SCAvRLR7F8ss= | |||
github.com/writefreely/go-nodeinfo v1.2.0/go.mod h1:UTvE78KpcjYOlRHupZIiSEFcXHioTXuacCbHU+CAcPg= | |||
golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59 h1:hk3yo72LXLapY9EXVttc3Z1rLOxT9IuAPPX3GpY2+jo= | |||
@@ -144,6 +144,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { | |||
write.HandleFunc("/admin", handler.Admin(handleViewAdminDash)).Methods("GET") | |||
write.HandleFunc("/admin/users", handler.Admin(handleViewAdminUsers)).Methods("GET") | |||
write.HandleFunc("/admin/user/{username}", handler.Admin(handleViewAdminUser)).Methods("GET") | |||
write.HandleFunc("/admin/user/{username}/passphrase", handler.Admin(handleAdminResetUserPass)).Methods("POST") | |||
write.HandleFunc("/admin/pages", handler.Admin(handleViewAdminPages)).Methods("GET") | |||
write.HandleFunc("/admin/page/{slug}", handler.Admin(handleViewAdminPage)).Methods("GET") | |||
write.HandleFunc("/admin/update/config", handler.AdminApper(handleAdminUpdateConfig)).Methods("POST") | |||
@@ -7,12 +7,25 @@ table.classy th { | |||
h3 { | |||
font-weight: normal; | |||
} | |||
input.copy-text { | |||
text-align: center; | |||
font-size: 1.2em; | |||
color: #555; | |||
width: 100%; | |||
box-sizing: border-box; | |||
} | |||
</style> | |||
<div class="snug content-container"> | |||
{{template "admin-header" .}} | |||
<h2 id="posts-header">{{.User.Username}}</h2> | |||
{{if .NewPassword}}<div class="alert success"> | |||
<p>This user's password has been reset to:</p> | |||
<p><input type="text" class="copy-text" value="{{.NewPassword}}" onfocus="if (this.select) this.select(); else this.setSelectionRange(0, this.value.length);" readonly /></p> | |||
<p>They can use this new password to log in to their account. <strong>This will only be shown once</strong>, so be sure to copy it and send it to them now.</p> | |||
{{if .ClearEmail}}<p>Their email address is: <a href="mailto:{{.ClearEmail}}">{{.ClearEmail}}</a></p>{{end}} | |||
</div> | |||
{{end}} | |||
<table class="classy export"> | |||
<tr> | |||
<th>No.</th> | |||
@@ -38,6 +51,19 @@ h3 { | |||
<th>Last Post</th> | |||
<td>{{if .LastPost}}{{.LastPost}}{{else}}Never{{end}}</td> | |||
</tr> | |||
<tr> | |||
<th>Password</th> | |||
<td> | |||
{{if ne .Username .User.Username}} | |||
<form id="reset-form" action="/admin/user/{{.User.Username}}/passphrase" method="post" autocomplete="false"> | |||
<input type="hidden" name="user" value="{{.User.ID}}"/> | |||
<button type="submit">Reset</button> | |||
</form> | |||
{{else}} | |||
<a href="/me/settings" title="Go to reset password page">Change your password</a> | |||
{{end}} | |||
</td> | |||
</tr> | |||
</table> | |||
<h2>Blogs</h2> | |||
@@ -83,5 +109,15 @@ h3 { | |||
{{end}} | |||
</div> | |||
<script type="text/javascript"> | |||
form = document.getElementById("reset-form"); | |||
form.addEventListener('submit', function(e) { | |||
e.preventDefault(); | |||
agreed = confirm("Reset this user's password? This will generate a new temporary password that you'll need to share with them, and invalidate their old one."); | |||
if (agreed === true) { | |||
form.submit(); | |||
} | |||
}); | |||
</script> | |||
{{template "footer" .}} | |||
{{end}} |