admin: add manual "Send Password reset" to accounts tab

Only appears with Reset links enabled.
Pressing this sends a PWR link to the users selected.
if one user is selected, or if one of you selected users doesn't have a
method of contact, a link is given to the admin to send to them
manually.
This commit is contained in:
Harvey Tindall
2021-10-13 15:04:22 +01:00
parent 9ae16163bb
commit eeb9b07bce
41 changed files with 349 additions and 84 deletions
+114 -14
View File
@@ -918,6 +918,74 @@ func (app *appContext) DeleteAnnounceTemplate(gc *gin.Context) {
respondBool(200, false, gc)
}
// @Summary Generate password reset links for a list of users, sending the links to them if possible.
// @Produce json
// @Param AdminPasswordResetDTO body AdminPasswordResetDTO true "List of user IDs"
// @Success 204 {object} boolResponse
// @Success 200 {object} AdminPasswordResetRespDTO
// @Failure 400 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Router /users/password-reset [post]
// @Security Bearer
// @tags Users
func (app *appContext) AdminPasswordReset(gc *gin.Context) {
var req AdminPasswordResetDTO
gc.BindJSON(&req)
if req.Users == nil || len(req.Users) == 0 {
app.debug.Println("Ignoring empty request for PWR")
respondBool(400, false, gc)
return
}
linkCount := 0
var pwr InternalPWR
var err error
resp := AdminPasswordResetRespDTO{}
for _, id := range req.Users {
pwr, err = app.GenInternalReset(id)
if err != nil {
app.err.Printf("Failed to get user from Jellyfin: %v", err)
respondBool(500, false, gc)
return
}
if app.internalPWRs == nil {
app.internalPWRs = map[string]InternalPWR{}
}
app.internalPWRs[pwr.PIN] = pwr
sendAddress := app.getAddressOrName(id)
if sendAddress == "" || len(req.Users) == 1 {
resp.Link, err = app.GenResetLink(pwr.PIN)
linkCount++
if sendAddress == "" {
resp.Manual = true
}
}
if sendAddress != "" {
msg, err := app.email.constructReset(
PasswordReset{
Pin: pwr.PIN,
Username: pwr.Username,
Expiry: pwr.Expiry,
Internal: true,
}, app, false,
)
if err != nil {
app.err.Printf("Failed to construct password reset message for \"%s\": %v", pwr.Username, err)
respondBool(500, false, gc)
return
} else if err := app.sendByID(msg, id); err != nil {
app.err.Printf("Failed to send password reset message to \"%s\": %v", sendAddress, err)
} else {
app.info.Printf("Sent password reset message to \"%s\"", sendAddress)
}
}
}
if resp.Link != "" && linkCount == 1 {
gc.JSON(200, resp)
return
}
respondBool(204, true, gc)
}
// @Summary Create a new invite.
// @Produce json
// @Param generateInviteDTO body generateInviteDTO true "New invite request object"
@@ -1505,38 +1573,70 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
gc.JSON(400, validation)
return
}
resp, status, err := app.jf.ResetPassword(req.PIN)
if status != 200 || err != nil || !resp.Success {
app.err.Printf("Password Reset failed (%d): %v", status, err)
respondBool(status, false, gc)
return
isInternal := false
var userID, username string
if reset, ok := app.internalPWRs[req.PIN]; ok {
isInternal = true
if time.Now().After(reset.Expiry) {
app.info.Printf("Password reset failed: PIN \"%s\" has expired", reset.PIN)
respondBool(401, false, gc)
delete(app.internalPWRs, req.PIN)
return
}
userID = reset.ID
username = reset.Username
status, err := app.jf.ResetPasswordAdmin(userID)
if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Password Reset failed (%d): %v", status, err)
respondBool(status, false, gc)
return
}
} else {
resp, status, err := app.jf.ResetPassword(req.PIN)
if status != 200 || err != nil || !resp.Success {
app.err.Printf("Password Reset failed (%d): %v", status, err)
respondBool(status, false, gc)
return
}
if req.Password == "" || len(resp.UsersReset) == 0 {
respondBool(200, false, gc)
return
}
username = resp.UsersReset[0]
}
if req.Password == "" || len(resp.UsersReset) == 0 {
respondBool(200, false, gc)
return
var user mediabrowser.User
var status int
var err error
if isInternal {
user, status, err = app.jf.UserByID(userID, false)
} else {
user, status, err = app.jf.UserByName(username, false)
}
user, status, err := app.jf.UserByName(resp.UsersReset[0], false)
if status != 200 || err != nil {
app.err.Printf("Failed to get user \"%s\" (%d): %v", resp.UsersReset[0], status, err)
app.err.Printf("Failed to get user \"%s\" (%d): %v", username, status, err)
respondBool(500, false, gc)
return
}
status, err = app.jf.SetPassword(user.ID, req.PIN, req.Password)
prevPassword := req.PIN
if isInternal {
prevPassword = ""
}
status, err = app.jf.SetPassword(user.ID, prevPassword, req.Password)
if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to change password for \"%s\" (%d): %v", resp.UsersReset[0], status, err)
app.err.Printf("Failed to change password for \"%s\" (%d): %v", username, status, err)
respondBool(500, false, gc)
return
}
if app.config.Section("ombi").Key("enabled").MustBool(false) {
// Silently fail for changing ombi passwords
if status != 200 || err != nil {
app.err.Printf("Failed to get user \"%s\" from jellyfin/emby (%d): %v", resp.UsersReset[0], status, err)
app.err.Printf("Failed to get user \"%s\" from jellyfin/emby (%d): %v", username, status, err)
respondBool(200, true, gc)
return
}
ombiUser, status, err := app.getOmbiUser(user.ID)
if status != 200 || err != nil {
app.err.Printf("Failed to get user \"%s\" from ombi (%d): %v", resp.UsersReset[0], status, err)
app.err.Printf("Failed to get user \"%s\" from ombi (%d): %v", username, status, err)
respondBool(200, true, gc)
return
}