allow admin to reset user passwords

this adds a new button when viewing a user as an admin, that will
generate and store a new password for the user
This commit is contained in:
Rob Loranger 2019-10-03 13:41:50 -07:00
parent 3759f16ed3
commit aa9efc7b37
No known key found for this signature in database
GPG key ID: D6F1633A4F0903B8
3 changed files with 94 additions and 8 deletions

View file

@ -16,12 +16,14 @@ import (
"net/http" "net/http"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"time" "time"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/writeas/impart" "github.com/writeas/impart"
"github.com/writeas/web-core/auth" "github.com/writeas/web-core/auth"
"github.com/writeas/web-core/log" "github.com/writeas/web-core/log"
"github.com/writeas/web-core/passgen"
"github.com/writeas/writefreely/appstats" "github.com/writeas/writefreely/appstats"
"github.com/writeas/writefreely/config" "github.com/writeas/writefreely/config"
) )
@ -169,11 +171,12 @@ func handleViewAdminUser(app *App, u *User, w http.ResponseWriter, r *http.Reque
*UserPage *UserPage
Config config.AppCfg Config config.AppCfg
Message string Message string
OwnUserPage bool
User *User User *User
Colls []inspectedCollection Colls []inspectedCollection
LastPost string LastPost string
NewPassword string
TotalPosts int64 TotalPosts int64
}{ }{
Config: app.cfg.App, Config: app.cfg.App,
@ -181,11 +184,19 @@ func handleViewAdminUser(app *App, u *User, w http.ResponseWriter, r *http.Reque
Colls: []inspectedCollection{}, Colls: []inspectedCollection{},
} }
flashes, _ := getSessionFlashes(app, w, r, nil)
for _, flash := range flashes {
if strings.HasPrefix(flash, "SUCCESS: ") {
p.NewPassword = strings.TrimPrefix(flash, "SUCCESS: ")
}
}
var err error var err error
p.User, err = app.db.GetUserForAuth(username) p.User, err = app.db.GetUserForAuth(username)
if err != nil { if err != nil {
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get user: %v", err)} return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get user: %v", err)}
} }
p.OwnUserPage = u.ID == p.User.ID
p.UserPage = NewUserPage(app, r, u, p.User.Username, nil) p.UserPage = NewUserPage(app, r, u, p.User.Username, nil)
p.TotalPosts = app.db.GetUserPostsCount(p.User.ID) p.TotalPosts = app.db.GetUserPostsCount(p.User.ID)
lp, err := app.db.GetUserLastPostTime(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 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.New()
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 { func handleViewAdminPages(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
p := struct { p := struct {
*UserPage *UserPage

View file

@ -144,6 +144,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
write.HandleFunc("/admin", handler.Admin(handleViewAdminDash)).Methods("GET") write.HandleFunc("/admin", handler.Admin(handleViewAdminDash)).Methods("GET")
write.HandleFunc("/admin/users", handler.Admin(handleViewAdminUsers)).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}", handler.Admin(handleViewAdminUser)).Methods("GET")
write.HandleFunc("/admin/user/{username}", handler.Admin(handleAdminResetUserPass)).Methods("POST")
write.HandleFunc("/admin/pages", handler.Admin(handleViewAdminPages)).Methods("GET") write.HandleFunc("/admin/pages", handler.Admin(handleViewAdminPages)).Methods("GET")
write.HandleFunc("/admin/page/{slug}", handler.Admin(handleViewAdminPage)).Methods("GET") write.HandleFunc("/admin/page/{slug}", handler.Admin(handleViewAdminPage)).Methods("GET")
write.HandleFunc("/admin/update/config", handler.AdminApper(handleAdminUpdateConfig)).Methods("POST") write.HandleFunc("/admin/update/config", handler.AdminApper(handleAdminUpdateConfig)).Methods("POST")

View file

@ -7,12 +7,32 @@ table.classy th {
h3 { h3 {
font-weight: normal; font-weight: normal;
} }
input.copy-text {
text-align: center;
font-size: 1.2em;
color: #555;
margin-left: 1rem;
}
button[type="submit"].danger {
padding-left: 2rem;
padding-right: 2rem;
}
</style> </style>
<div class="snug content-container"> <div class="snug content-container">
{{template "admin-header" .}} {{template "admin-header" .}}
<h2 id="posts-header">{{.User.Username}}</h2> <h2 id="posts-header">{{.User.Username}}</h2>
{{if .NewPassword}}<p class="alert success">New password for user <strong>{{.User.Username}}</strong> is
<input
type="text"
class="copy-text"
value="{{.NewPassword}}"
onfocus="if (this.select) this.select(); else this.setSelectionRange(0, this.value.length);"
readonly />
<br/><br/>
You must share this new password with the user, this is the only time it will be displayed.
</p>
{{end}}
<table class="classy export"> <table class="classy export">
<tr> <tr>
<th>No.</th> <th>No.</th>
@ -38,6 +58,21 @@ h3 {
<th>Last Post</th> <th>Last Post</th>
<td>{{if .LastPost}}{{.LastPost}}{{else}}Never{{end}}</td> <td>{{if .LastPost}}{{.LastPost}}{{else}}Never{{end}}</td>
</tr> </tr>
<tr>
<th>Password</th>
<td>
{{if not .OwnUserPage}}
<form id="reset-form" action="/admin/user/{{.User.Username}}" method="post" autocomplete="false">
<input type="hidden" name="user" value="{{.User.ID}}"/>
<button
class="danger"
type="submit">Reset</button>
</form>
{{else}}
<a href="/me/settings" title="Go to reset password page">Change your password</a>
{{end}}
</td>
</tr>
</table> </table>
<h2>Blogs</h2> <h2>Blogs</h2>
@ -83,5 +118,13 @@ h3 {
{{end}} {{end}}
</div> </div>
<script type="text/javascript">
form = document.getElementById("reset-form");
form.addEventListener('submit', function(e) {
e.preventDefault();
agreed = confirm("Really reset password for {{.User.Username}}?\nYou will have to record and share the new password with them.");
if (agreed === true) { form.submit();}
});
</script>
{{template "footer" .}} {{template "footer" .}}
{{end}} {{end}}