mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 09:00:10 +00:00
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:
parent
9ae16163bb
commit
eeb9b07bce
128
api.go
128
api.go
@ -918,6 +918,74 @@ func (app *appContext) DeleteAnnounceTemplate(gc *gin.Context) {
|
|||||||
respondBool(200, false, gc)
|
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.
|
// @Summary Create a new invite.
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param generateInviteDTO body generateInviteDTO true "New invite request object"
|
// @Param generateInviteDTO body generateInviteDTO true "New invite request object"
|
||||||
@ -1505,38 +1573,70 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
|
|||||||
gc.JSON(400, validation)
|
gc.JSON(400, validation)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
resp, status, err := app.jf.ResetPassword(req.PIN)
|
isInternal := false
|
||||||
if status != 200 || err != nil || !resp.Success {
|
var userID, username string
|
||||||
app.err.Printf("Password Reset failed (%d): %v", status, err)
|
if reset, ok := app.internalPWRs[req.PIN]; ok {
|
||||||
respondBool(status, false, gc)
|
isInternal = true
|
||||||
return
|
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 {
|
var user mediabrowser.User
|
||||||
respondBool(200, false, gc)
|
var status int
|
||||||
return
|
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 {
|
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)
|
respondBool(500, false, gc)
|
||||||
return
|
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 {
|
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)
|
respondBool(500, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||||
// Silently fail for changing ombi passwords
|
// Silently fail for changing ombi passwords
|
||||||
if status != 200 || err != nil {
|
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)
|
respondBool(200, true, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ombiUser, status, err := app.getOmbiUser(user.ID)
|
ombiUser, status, err := app.getOmbiUser(user.ID)
|
||||||
if status != 200 || err != nil {
|
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)
|
respondBool(200, true, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
19
email.go
19
email.go
@ -514,6 +514,18 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite
|
|||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GenResetLink generates and returns a password reset link.
|
||||||
|
func (app *appContext) GenResetLink(pin string) (string, error) {
|
||||||
|
url := app.config.Section("password_resets").Key("url_base").String()
|
||||||
|
var pinLink string
|
||||||
|
if url == "" {
|
||||||
|
return pinLink, fmt.Errorf("disabled as no URL Base provided. Set in Settings > Password Resets.")
|
||||||
|
}
|
||||||
|
// Strip /invite from end of this URL, ik it's ugly.
|
||||||
|
pinLink = fmt.Sprintf("%s/reset?pin=%s", url, pin)
|
||||||
|
return pinLink, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) resetValues(pwr PasswordReset, app *appContext, noSub bool) map[string]interface{} {
|
func (emailer *Emailer) resetValues(pwr PasswordReset, app *appContext, noSub bool) map[string]interface{} {
|
||||||
d, t, expiresIn := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern)
|
d, t, expiresIn := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern)
|
||||||
message := app.config.Section("messages").Key("message").String()
|
message := app.config.Section("messages").Key("message").String()
|
||||||
@ -544,17 +556,16 @@ func (emailer *Emailer) resetValues(pwr PasswordReset, app *appContext, noSub bo
|
|||||||
} else {
|
} else {
|
||||||
template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": pwr.Username})
|
template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": pwr.Username})
|
||||||
template["codeExpiry"] = emailer.lang.PasswordReset.template("codeExpiry", tmpl{"date": d, "time": t, "expiresInMinutes": expiresIn})
|
template["codeExpiry"] = emailer.lang.PasswordReset.template("codeExpiry", tmpl{"date": d, "time": t, "expiresInMinutes": expiresIn})
|
||||||
url := app.config.Section("password_resets").Key("url_base").String()
|
|
||||||
if linkResetEnabled {
|
if linkResetEnabled {
|
||||||
if url != "" {
|
pinLink, err := app.GenResetLink(pwr.Pin)
|
||||||
|
if err == nil {
|
||||||
// Strip /invite form end of this URL, ik its ugly.
|
// Strip /invite form end of this URL, ik its ugly.
|
||||||
template["link_reset"] = true
|
template["link_reset"] = true
|
||||||
pinLink := fmt.Sprintf("%s/reset?pin=%s", url, pwr.Pin)
|
|
||||||
template["pin"] = pinLink
|
template["pin"] = pinLink
|
||||||
// Only used in html email.
|
// Only used in html email.
|
||||||
template["pin_code"] = pwr.Pin
|
template["pin_code"] = pwr.Pin
|
||||||
} else {
|
} else {
|
||||||
app.info.Println("Password Reset link disabled as no URL Base provided. Set in Settings > Password Resets.")
|
app.info.Println("Couldn't generate PWR link: %v", err)
|
||||||
template["pin"] = pwr.Pin
|
template["pin"] = pwr.Pin
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
4
go.mod
4
go.mod
@ -12,6 +12,8 @@ replace github.com/hrfee/jfa-go/logger => ./logger
|
|||||||
|
|
||||||
replace github.com/hrfee/jfa-go/linecache => ./linecache
|
replace github.com/hrfee/jfa-go/linecache => ./linecache
|
||||||
|
|
||||||
|
replace github.com/hrfee/mediabrowser => ../mediabrowser
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bwmarrin/discordgo v0.23.2
|
github.com/bwmarrin/discordgo v0.23.2
|
||||||
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||||
@ -38,7 +40,7 @@ require (
|
|||||||
github.com/hrfee/jfa-go/linecache v0.0.0-20211003145958-a220ba8dfb58
|
github.com/hrfee/jfa-go/linecache v0.0.0-20211003145958-a220ba8dfb58
|
||||||
github.com/hrfee/jfa-go/logger v0.0.0-20211003145958-a220ba8dfb58
|
github.com/hrfee/jfa-go/logger v0.0.0-20211003145958-a220ba8dfb58
|
||||||
github.com/hrfee/jfa-go/ombi v0.0.0-20211003145958-a220ba8dfb58
|
github.com/hrfee/jfa-go/ombi v0.0.0-20211003145958-a220ba8dfb58
|
||||||
github.com/hrfee/mediabrowser v0.3.5
|
github.com/hrfee/mediabrowser v0.0.0-00010101000000-000000000000
|
||||||
github.com/itchyny/timefmt-go v0.1.3
|
github.com/itchyny/timefmt-go v0.1.3
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/lib/pq v1.10.3 // indirect
|
github.com/lib/pq v1.10.3 // indirect
|
||||||
|
7
go.sum
7
go.sum
@ -20,6 +20,7 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE
|
|||||||
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
|
||||||
github.com/bwmarrin/discordgo v0.23.2 h1:BzrtTktixGHIu9Tt7dEE6diysEF9HWnXeHuoJEt2fH4=
|
github.com/bwmarrin/discordgo v0.23.2 h1:BzrtTktixGHIu9Tt7dEE6diysEF9HWnXeHuoJEt2fH4=
|
||||||
github.com/bwmarrin/discordgo v0.23.2/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M=
|
github.com/bwmarrin/discordgo v0.23.2/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -58,6 +59,7 @@ github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6 h1:QthAQCekS1YOeYWS
|
|||||||
github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||||
github.com/getlantern/systray v1.1.0 h1:U0wCEqseLi2ok1fE6b88gJklzriavPJixZysZPkZd/Y=
|
github.com/getlantern/systray v1.1.0 h1:U0wCEqseLi2ok1fE6b88gJklzriavPJixZysZPkZd/Y=
|
||||||
github.com/getlantern/systray v1.1.0/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
|
github.com/getlantern/systray v1.1.0/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
|
||||||
|
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc=
|
github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc=
|
||||||
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
|
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
|
||||||
@ -138,8 +140,6 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
|
|||||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/hrfee/mediabrowser v0.3.5 h1:bOJlI2HLvw7v0c7mcRw5XDRMUHReQzk5z0EJYRyYjpo=
|
|
||||||
github.com/hrfee/mediabrowser v0.3.5/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
|
||||||
github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU=
|
github.com/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU=
|
||||||
github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
|
github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
|
||||||
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
@ -216,6 +216,7 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
|
|||||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
@ -263,8 +264,10 @@ github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2t
|
|||||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
|
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
|
||||||
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
|
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
|
||||||
|
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||||
|
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw=
|
github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw=
|
||||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE=
|
github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE=
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
window.ombiEnabled = {{ .ombiEnabled }};
|
window.ombiEnabled = {{ .ombiEnabled }};
|
||||||
window.usernameEnabled = {{ .username }};
|
window.usernameEnabled = {{ .username }};
|
||||||
window.langFile = JSON.parse({{ .language }});
|
window.langFile = JSON.parse({{ .language }});
|
||||||
|
window.linkResetEnabled = {{ .linkResetEnabled }};
|
||||||
window.language = "{{ .langName }}";
|
window.language = "{{ .langName }}";
|
||||||
</script>
|
</script>
|
||||||
{{ template "header.html" . }}
|
{{ template "header.html" . }}
|
||||||
@ -253,6 +254,13 @@
|
|||||||
<p class="content">{{ .strings.settingsRefreshPage }}</p>
|
<p class="content">{{ .strings.settingsRefreshPage }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="modal-send-pwr" class="modal">
|
||||||
|
<div class="modal-content card ~neutral !normal">
|
||||||
|
<span class="heading">{{ .strings.sendPWR }}</span>
|
||||||
|
<p class="content" id="send-pwr-note"></p>
|
||||||
|
<span class="button ~urge !normal mt-half" id="send-pwr-link">{{ .strings.copy }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div id="modal-ombi-defaults" class="modal">
|
<div id="modal-ombi-defaults" class="modal">
|
||||||
<form class="modal-content card" id="form-ombi-defaults" href="">
|
<form class="modal-content card" id="form-ombi-defaults" href="">
|
||||||
<span class="heading">{{ .strings.ombiUserDefaults }} <span class="modal-close">×</span></span>
|
<span class="heading">{{ .strings.ombiUserDefaults }} <span class="modal-close">×</span></span>
|
||||||
@ -567,6 +575,7 @@
|
|||||||
<span class="col sm button ~urge !normal center mb-half" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
|
<span class="col sm button ~urge !normal center mb-half" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
|
||||||
<span class="col sm button ~warning !normal center mb-half" id="accounts-extend-expiry">{{ .strings.extendExpiry }}</span>
|
<span class="col sm button ~warning !normal center mb-half" id="accounts-extend-expiry">{{ .strings.extendExpiry }}</span>
|
||||||
<span class="col sm button ~positive !normal center mb-half" id="accounts-disable-enable">{{ .strings.disable }}</span>
|
<span class="col sm button ~positive !normal center mb-half" id="accounts-disable-enable">{{ .strings.disable }}</span>
|
||||||
|
<span class="col sm button ~info !normal center mb-half" id="accounts-send-pwr">{{ .strings.sendPWR }}</span>
|
||||||
<span class="col sm button ~critical !normal center mb-half" id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
|
<span class="col sm button ~critical !normal center mb-half" id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
<body class="max-w-full overflow-x-hidden section">
|
<body class="max-w-full overflow-x-hidden section">
|
||||||
<div id="modal-success" class="modal">
|
<div id="modal-success" class="modal">
|
||||||
<div class="modal-content card">
|
<div class="modal-content card">
|
||||||
<span class="heading mb-1">{{ .strings.successHeader }}</span>
|
<span class="heading mb-1">{{ if .passwordReset }}{{ .strings.passwordReset }}{{ else }}{{ .strings.successHeader }}{{ end }}</span>
|
||||||
<p class="content mb-1">{{ .successMessage }}</p>
|
<p class="content mb-1">{{ if .passwordReset }}{{ .strings.youCanLoginPassword }}{{ else }}{{ .successMessage }}{{ end }}</p>
|
||||||
<a class="button ~urge !normal full-width center supra submit" href="{{ .jfLink }}" id="create-success-button">{{ .strings.successContinueButton }}</a>
|
<a class="button ~urge !normal full-width center supra submit" href="{{ .jfLink }}" id="create-success-button">{{ .strings.continue }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="modal-confirmation" class="modal">
|
<div id="modal-confirmation" class="modal">
|
||||||
|
@ -57,8 +57,13 @@
|
|||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
"donate": "Donate",
|
"donate": "Donate",
|
||||||
|
"sendPWR": "Send Password Reset",
|
||||||
"contactThrough": "Contact through:",
|
"contactThrough": "Contact through:",
|
||||||
"extendExpiry": "Extend expiry",
|
"extendExpiry": "Extend expiry",
|
||||||
|
"sendPWRManual": "User {n} has no method of contact, press copy to get a link to send to them.",
|
||||||
|
"sendPWRSuccess": "Password reset link sent.",
|
||||||
|
"sendPWRSuccessManual": "If the user hasn't received it, press copy to get a link to manually send to them.",
|
||||||
|
"sendPWRValidFor": "The link is valid for 30m.",
|
||||||
"customizeMessages": "Customize Messages",
|
"customizeMessages": "Customize Messages",
|
||||||
"customizeMessagesDescription": "If you don't want to use jfa-go's message templates, you can create your own using Markdown.",
|
"customizeMessagesDescription": "If you don't want to use jfa-go's message templates, you can create your own using Markdown.",
|
||||||
"markdownSupported": "Markdown is supported.",
|
"markdownSupported": "Markdown is supported.",
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"submit": "Indsend",
|
"submit": "Indsend",
|
||||||
"send": "Send",
|
"send": "Send",
|
||||||
"success": "Succes",
|
"success": "Succes",
|
||||||
|
"continue": "Fortsæt",
|
||||||
"error": "Fejl",
|
"error": "Fejl",
|
||||||
"copy": "Kopiér",
|
"copy": "Kopiér",
|
||||||
"copied": "Kopiret",
|
"copied": "Kopiret",
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"emailAddress": "E-Mail-Adresse",
|
"emailAddress": "E-Mail-Adresse",
|
||||||
"submit": "Absenden",
|
"submit": "Absenden",
|
||||||
"success": "Erfolgreich",
|
"success": "Erfolgreich",
|
||||||
|
"continue": "Weiter",
|
||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"copy": "Kopieren",
|
"copy": "Kopieren",
|
||||||
"theme": "Thema",
|
"theme": "Thema",
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"name": "Όνομα",
|
"name": "Όνομα",
|
||||||
"submit": "Καταχώρηση",
|
"submit": "Καταχώρηση",
|
||||||
"success": "Επιτυχία",
|
"success": "Επιτυχία",
|
||||||
|
"continue": "Συνέχεια",
|
||||||
"error": "Σφάλμα",
|
"error": "Σφάλμα",
|
||||||
"copy": "Αντιγραφή",
|
"copy": "Αντιγραφή",
|
||||||
"theme": "Θέμα",
|
"theme": "Θέμα",
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"send": "Send",
|
"send": "Send",
|
||||||
"success": "Success",
|
"success": "Success",
|
||||||
|
"continue": "Continue",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"copy": "Copy",
|
"copy": "Copy",
|
||||||
"copied": "Copied",
|
"copied": "Copied",
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"name": "Nombre",
|
"name": "Nombre",
|
||||||
"submit": "Enviar",
|
"submit": "Enviar",
|
||||||
"success": "Éxito",
|
"success": "Éxito",
|
||||||
|
"continue": "Continuar",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"copied": "Copiado",
|
"copied": "Copiado",
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"submit": "تایید",
|
"submit": "تایید",
|
||||||
"send": "ارسال",
|
"send": "ارسال",
|
||||||
"success": "موفقیت",
|
"success": "موفقیت",
|
||||||
|
"continue": "ادامه دادن",
|
||||||
"error": "خطا",
|
"error": "خطا",
|
||||||
"copy": "کپی",
|
"copy": "کپی",
|
||||||
"copied": "کپی شد",
|
"copied": "کپی شد",
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"emailAddress": "Addresse Email",
|
"emailAddress": "Addresse Email",
|
||||||
"submit": "Soumettre",
|
"submit": "Soumettre",
|
||||||
"success": "Succès",
|
"success": "Succès",
|
||||||
|
"continue": "Continuer",
|
||||||
"error": "Erreur",
|
"error": "Erreur",
|
||||||
"copy": "Copier",
|
"copy": "Copier",
|
||||||
"time24h": "Temps 24h",
|
"time24h": "Temps 24h",
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"name": "Nama",
|
"name": "Nama",
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"success": "Sukses",
|
"success": "Sukses",
|
||||||
|
"continue": "Lanjut",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"copy": "Salin",
|
"copy": "Salin",
|
||||||
"time24h": "Waktu 24 jam",
|
"time24h": "Waktu 24 jam",
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"emailAddress": "E-mailadres",
|
"emailAddress": "E-mailadres",
|
||||||
"submit": "Verstuur",
|
"submit": "Verstuur",
|
||||||
"success": "Succes",
|
"success": "Succes",
|
||||||
|
"continue": "Doorgaan",
|
||||||
"error": "Fout",
|
"error": "Fout",
|
||||||
"copy": "Kopiëer",
|
"copy": "Kopiëer",
|
||||||
"theme": "Thema",
|
"theme": "Thema",
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"emailAddress": "Endereço de Email",
|
"emailAddress": "Endereço de Email",
|
||||||
"submit": "Enviar",
|
"submit": "Enviar",
|
||||||
"success": "Sucesso",
|
"success": "Sucesso",
|
||||||
|
"continue": "Continuar",
|
||||||
"error": "Erro",
|
"error": "Erro",
|
||||||
"copy": "Copiar",
|
"copy": "Copiar",
|
||||||
"theme": "Tema",
|
"theme": "Tema",
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"name": "Namn",
|
"name": "Namn",
|
||||||
"submit": "Skicka",
|
"submit": "Skicka",
|
||||||
"success": "Lyckades",
|
"success": "Lyckades",
|
||||||
|
"continue": "Fortsätt",
|
||||||
"error": "Fel",
|
"error": "Fel",
|
||||||
"copy": "Kopiera",
|
"copy": "Kopiera",
|
||||||
"time24h": "24 timmarsklocka",
|
"time24h": "24 timmarsklocka",
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"submit": "提交",
|
"submit": "提交",
|
||||||
"send": "发送",
|
"send": "发送",
|
||||||
"success": "成功",
|
"success": "成功",
|
||||||
|
"continue": "继续",
|
||||||
"error": "错误",
|
"error": "错误",
|
||||||
"copy": "复制",
|
"copy": "复制",
|
||||||
"copied": "已复制",
|
"copied": "已复制",
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
"createAccountButton": "Opret Konto",
|
"createAccountButton": "Opret Konto",
|
||||||
"passwordRequirementsHeader": "Adgangskodekrav",
|
"passwordRequirementsHeader": "Adgangskodekrav",
|
||||||
"successHeader": "Succes!",
|
"successHeader": "Succes!",
|
||||||
"successContinueButton": "Fortsæt",
|
|
||||||
"confirmationRequired": "E-mail bekræftelse er påkrævet",
|
"confirmationRequired": "E-mail bekræftelse er påkrævet",
|
||||||
"confirmationRequiredMessage": "Tjek venligst din e-mail indbakke for at verificere din adresse.",
|
"confirmationRequiredMessage": "Tjek venligst din e-mail indbakke for at verificere din adresse.",
|
||||||
"yourAccountIsValidUntil": "Din konto er gyldig indtil {date}.",
|
"yourAccountIsValidUntil": "Din konto er gyldig indtil {date}.",
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
"createAccountButton": "Konto erstellen",
|
"createAccountButton": "Konto erstellen",
|
||||||
"passwordRequirementsHeader": "Passwortanforderungen",
|
"passwordRequirementsHeader": "Passwortanforderungen",
|
||||||
"successHeader": "Erfolgreich!",
|
"successHeader": "Erfolgreich!",
|
||||||
"successContinueButton": "Weiter",
|
|
||||||
"confirmationRequired": "E-Mail-Bestätigung erforderlich",
|
"confirmationRequired": "E-Mail-Bestätigung erforderlich",
|
||||||
"confirmationRequiredMessage": "Bitte überprüfe dein Posteingang und bestätige deine E-Mail-Adresse.",
|
"confirmationRequiredMessage": "Bitte überprüfe dein Posteingang und bestätige deine E-Mail-Adresse.",
|
||||||
"yourAccountIsValidUntil": "Dein Konto wird bis zum {date} gültig sein.",
|
"yourAccountIsValidUntil": "Dein Konto wird bis zum {date} gültig sein.",
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
"createAccountButton": "Δημιουργία Λογαρισμού",
|
"createAccountButton": "Δημιουργία Λογαρισμού",
|
||||||
"passwordRequirementsHeader": "Απαιτήσεις Κωδικού",
|
"passwordRequirementsHeader": "Απαιτήσεις Κωδικού",
|
||||||
"successHeader": "Επιτυχία!",
|
"successHeader": "Επιτυχία!",
|
||||||
"successContinueButton": "Συνέχεια",
|
|
||||||
"confirmationRequired": "Απαιτείται επιβεβαίωση Email",
|
"confirmationRequired": "Απαιτείται επιβεβαίωση Email",
|
||||||
"confirmationRequiredMessage": "Παρακαλώ ελέγξτε το email σας για να επιβεβαιώσετε την διεύθυνση σας .",
|
"confirmationRequiredMessage": "Παρακαλώ ελέγξτε το email σας για να επιβεβαιώσετε την διεύθυνση σας .",
|
||||||
"yourAccountIsValidUntil": "Ο λογαριασμός σου θα ισχύει μέχρι {date}."
|
"yourAccountIsValidUntil": "Ο λογαριασμός σου θα ισχύει μέχρι {date}."
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
"createAccountButton": "Create Account",
|
"createAccountButton": "Create Account",
|
||||||
"passwordRequirementsHeader": "Password Requirements",
|
"passwordRequirementsHeader": "Password Requirements",
|
||||||
"successHeader": "Success!",
|
"successHeader": "Success!",
|
||||||
"successContinueButton": "Continue",
|
|
||||||
"confirmationRequired": "Email confirmation required",
|
"confirmationRequired": "Email confirmation required",
|
||||||
"confirmationRequiredMessage": "Please check your email inbox to verify your address.",
|
"confirmationRequiredMessage": "Please check your email inbox to verify your address.",
|
||||||
"yourAccountIsValidUntil": "Your account will be valid until {date}.",
|
"yourAccountIsValidUntil": "Your account will be valid until {date}.",
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
"createAccountButton": "Crear una cuenta",
|
"createAccountButton": "Crear una cuenta",
|
||||||
"passwordRequirementsHeader": "Requisitos de contraseña",
|
"passwordRequirementsHeader": "Requisitos de contraseña",
|
||||||
"successHeader": "¡Éxito!",
|
"successHeader": "¡Éxito!",
|
||||||
"successContinueButton": "Continuar",
|
|
||||||
"confirmationRequired": "Se requiere confirmación por correo electrónico",
|
"confirmationRequired": "Se requiere confirmación por correo electrónico",
|
||||||
"confirmationRequiredMessage": "Revise la bandeja de entrada de su correo electrónico para verificar su dirección.",
|
"confirmationRequiredMessage": "Revise la bandeja de entrada de su correo electrónico para verificar su dirección.",
|
||||||
"yourAccountIsValidUntil": "Su cuenta será válida hasta el {date}."
|
"yourAccountIsValidUntil": "Su cuenta será válida hasta el {date}."
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
"createAccountButton": "ساخت حساب کاربری",
|
"createAccountButton": "ساخت حساب کاربری",
|
||||||
"passwordRequirementsHeader": "کلمه عبور لازم است",
|
"passwordRequirementsHeader": "کلمه عبور لازم است",
|
||||||
"successHeader": "موفقیت!",
|
"successHeader": "موفقیت!",
|
||||||
"successContinueButton": "ادامه دادن",
|
|
||||||
"confirmationRequired": "تایید ایمیل لازم است",
|
"confirmationRequired": "تایید ایمیل لازم است",
|
||||||
"confirmationRequiredMessage": "لطفاً برای تأیید آدرس خود ، صندوق پستی ایمیل خود را بررسی کنید.",
|
"confirmationRequiredMessage": "لطفاً برای تأیید آدرس خود ، صندوق پستی ایمیل خود را بررسی کنید.",
|
||||||
"yourAccountIsValidUntil": "حساب شما تا {date} معتبر خواهد بود.",
|
"yourAccountIsValidUntil": "حساب شما تا {date} معتبر خواهد بود.",
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
"createAccountButton": "Créer le compte",
|
"createAccountButton": "Créer le compte",
|
||||||
"passwordRequirementsHeader": "Mot de passe requis",
|
"passwordRequirementsHeader": "Mot de passe requis",
|
||||||
"successHeader": "Succès !",
|
"successHeader": "Succès !",
|
||||||
"successContinueButton": "Continuer",
|
|
||||||
"confirmationRequired": "Confirmation de l'adresse e-mail requise",
|
"confirmationRequired": "Confirmation de l'adresse e-mail requise",
|
||||||
"confirmationRequiredMessage": "Veuillez vérifier votre boite de réception pour confirmer votre adresse e-mail.",
|
"confirmationRequiredMessage": "Veuillez vérifier votre boite de réception pour confirmer votre adresse e-mail.",
|
||||||
"yourAccountIsValidUntil": "Votre compte sera valide jusqu'au {date}.",
|
"yourAccountIsValidUntil": "Votre compte sera valide jusqu'au {date}.",
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
"createAccountButton": "Buat Akun",
|
"createAccountButton": "Buat Akun",
|
||||||
"passwordRequirementsHeader": "Persyaratan Kata Sandi",
|
"passwordRequirementsHeader": "Persyaratan Kata Sandi",
|
||||||
"successHeader": "Sukses!",
|
"successHeader": "Sukses!",
|
||||||
"successContinueButton": "Lanjut",
|
|
||||||
"confirmationRequired": "Konfirmasi email diperlukan",
|
"confirmationRequired": "Konfirmasi email diperlukan",
|
||||||
"confirmationRequiredMessage": "Silakan periksa kotak masuk email Anda untuk memverifikasi alamat Anda."
|
"confirmationRequiredMessage": "Silakan periksa kotak masuk email Anda untuk memverifikasi alamat Anda."
|
||||||
},
|
},
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
"createAccountButton": "Crea Un Account",
|
"createAccountButton": "Crea Un Account",
|
||||||
"passwordRequirementsHeader": "Requisiti Password",
|
"passwordRequirementsHeader": "Requisiti Password",
|
||||||
"successHeader": "Successo!",
|
"successHeader": "Successo!",
|
||||||
"successContinueButton": "Continua",
|
|
||||||
"confirmationRequired": "Richiesta la conferma Email",
|
"confirmationRequired": "Richiesta la conferma Email",
|
||||||
"confirmationRequiredMessage": "Controlla la tua casella email per verificare il tuo indirizzo."
|
"confirmationRequiredMessage": "Controlla la tua casella email per verificare il tuo indirizzo."
|
||||||
},
|
},
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
"createAccountButton": "Maak account aan",
|
"createAccountButton": "Maak account aan",
|
||||||
"passwordRequirementsHeader": "Wachtwoordvereisten",
|
"passwordRequirementsHeader": "Wachtwoordvereisten",
|
||||||
"successHeader": "Succes!",
|
"successHeader": "Succes!",
|
||||||
"successContinueButton": "Doorgaan",
|
|
||||||
"confirmationRequired": "Bevestiging van e-mailadres verplicht",
|
"confirmationRequired": "Bevestiging van e-mailadres verplicht",
|
||||||
"confirmationRequiredMessage": "Controleer je e-mail inbox om je adres te bevestigen.",
|
"confirmationRequiredMessage": "Controleer je e-mail inbox om je adres te bevestigen.",
|
||||||
"yourAccountIsValidUntil": "Je account zal geldig zijn tot {date}.",
|
"yourAccountIsValidUntil": "Je account zal geldig zijn tot {date}.",
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
"createAccountButton": "Criar Conta",
|
"createAccountButton": "Criar Conta",
|
||||||
"passwordRequirementsHeader": "Requisitos da Senha",
|
"passwordRequirementsHeader": "Requisitos da Senha",
|
||||||
"successHeader": "Concluído!",
|
"successHeader": "Concluído!",
|
||||||
"successContinueButton": "Continuar",
|
|
||||||
"confirmationRequired": "Confirmação por e-mail",
|
"confirmationRequired": "Confirmação por e-mail",
|
||||||
"confirmationRequiredMessage": "Verifique sua caixa de email para finalizar o cadastro.",
|
"confirmationRequiredMessage": "Verifique sua caixa de email para finalizar o cadastro.",
|
||||||
"yourAccountIsValidUntil": "Sua conta é válida até {date}.",
|
"yourAccountIsValidUntil": "Sua conta é válida até {date}.",
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
"createAccountButton": "Skapa konto",
|
"createAccountButton": "Skapa konto",
|
||||||
"passwordRequirementsHeader": "Lösenordskrav",
|
"passwordRequirementsHeader": "Lösenordskrav",
|
||||||
"successHeader": "Lyckades!",
|
"successHeader": "Lyckades!",
|
||||||
"successContinueButton": "Fortsätt",
|
|
||||||
"confirmationRequired": "E-postbekräftelse krävs",
|
"confirmationRequired": "E-postbekräftelse krävs",
|
||||||
"confirmationRequiredMessage": "Kontrollera din e-postkorg för att verifiera din adress.",
|
"confirmationRequiredMessage": "Kontrollera din e-postkorg för att verifiera din adress.",
|
||||||
"yourAccountIsValidUntil": "Ditt konto är giltigt fram tills {date}.",
|
"yourAccountIsValidUntil": "Ditt konto är giltigt fram tills {date}.",
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
"createAccountButton": "创建账户",
|
"createAccountButton": "创建账户",
|
||||||
"passwordRequirementsHeader": "密码格式要求",
|
"passwordRequirementsHeader": "密码格式要求",
|
||||||
"successHeader": "成功!",
|
"successHeader": "成功!",
|
||||||
"successContinueButton": "继续",
|
|
||||||
"confirmationRequired": "需要邮件确认",
|
"confirmationRequired": "需要邮件确认",
|
||||||
"confirmationRequiredMessage": "请登录您的邮箱收件箱来验证您的地址。",
|
"confirmationRequiredMessage": "请登录您的邮箱收件箱来验证您的地址。",
|
||||||
"yourAccountIsValidUntil": "您的账户将在 {date} 之前有效。",
|
"yourAccountIsValidUntil": "您的账户将在 {date} 之前有效。",
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"tryAgain": "Please try again.",
|
"tryAgain": "Please try again.",
|
||||||
"youCanLogin": "You can now log in with the below code as your password.",
|
"youCanLogin": "You can now log in with the below code as your password.",
|
||||||
"youCanLoginOmbi": "You can now log in to Jellyfin & Ombi with the below code as your password.",
|
"youCanLoginOmbi": "You can now log in to Jellyfin & Ombi with the below code as your password.",
|
||||||
|
"youCanLoginPassword": "You can now login with your new password. Press below to continue to Jellyfin.",
|
||||||
"changeYourPassword": "Make sure to change your password after you log in.",
|
"changeYourPassword": "Make sure to change your password after you log in.",
|
||||||
"enterYourPassword": "Enter your new password below."
|
"enterYourPassword": "Enter your new password below."
|
||||||
}
|
}
|
||||||
|
1
main.go
1
main.go
@ -108,6 +108,7 @@ type appContext struct {
|
|||||||
newUpdate bool // Whether whatever's in update is new.
|
newUpdate bool // Whether whatever's in update is new.
|
||||||
tag Tag
|
tag Tag
|
||||||
update Update
|
update Update
|
||||||
|
internalPWRs map[string]InternalPWR
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateSecret(length int) (string, error) {
|
func generateSecret(length int) (string, error) {
|
||||||
|
19
models.go
19
models.go
@ -1,5 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
type stringResponse struct {
|
type stringResponse struct {
|
||||||
Response string `json:"response" example:"message"`
|
Response string `json:"response" example:"message"`
|
||||||
Error string `json:"error" example:"errorDescription"`
|
Error string `json:"error" example:"errorDescription"`
|
||||||
@ -320,3 +322,20 @@ type ResetPasswordDTO struct {
|
|||||||
PIN string `json:"pin"`
|
PIN string `json:"pin"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AdminPasswordResetDTO struct {
|
||||||
|
Users []string `json:"users"` // List of Jellyfin user IDs
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminPasswordResetRespDTO struct {
|
||||||
|
Link string `json:"link"` // Only returned if one of the given users doesn't have a contact method set, or only one user was requested.
|
||||||
|
Manual bool `json:"manual"` // Whether or not the admin has to send the link manually or not.
|
||||||
|
}
|
||||||
|
|
||||||
|
// InternalPWR stores a local version of a password reset PIN used for resets triggered by the admin when reset links are enabled.
|
||||||
|
type InternalPWR struct {
|
||||||
|
PIN string `json:"pin"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
ID string `json:"id"`
|
||||||
|
Expiry time.Time `json:"expiry"`
|
||||||
|
}
|
||||||
|
19
pwreset.go
19
pwreset.go
@ -9,6 +9,22 @@ import (
|
|||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// GenInternalReset generates a local password reset PIN, for use with the PWR option on the Admin page.
|
||||||
|
func (app *appContext) GenInternalReset(userID string) (InternalPWR, error) {
|
||||||
|
pin := genAuthToken()
|
||||||
|
user, status, err := app.jf.UserByID(userID, false)
|
||||||
|
if err != nil || status != 200 {
|
||||||
|
return InternalPWR{}, err
|
||||||
|
}
|
||||||
|
pwr := InternalPWR{
|
||||||
|
PIN: pin,
|
||||||
|
Username: user.Name,
|
||||||
|
ID: userID,
|
||||||
|
Expiry: time.Now().Add(30 * time.Minute),
|
||||||
|
}
|
||||||
|
return pwr, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (app *appContext) StartPWR() {
|
func (app *appContext) StartPWR() {
|
||||||
app.info.Println("Starting password reset daemon")
|
app.info.Println("Starting password reset daemon")
|
||||||
path := app.config.Section("password_resets").Key("watch_directory").String()
|
path := app.config.Section("password_resets").Key("watch_directory").String()
|
||||||
@ -38,6 +54,7 @@ type PasswordReset struct {
|
|||||||
Pin string `json:"Pin"`
|
Pin string `json:"Pin"`
|
||||||
Username string `json:"UserName"`
|
Username string `json:"UserName"`
|
||||||
Expiry time.Time `json:"ExpirationDate"`
|
Expiry time.Time `json:"ExpirationDate"`
|
||||||
|
Internal bool `json:"Internal,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
|
func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
|
||||||
@ -81,7 +98,7 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
|
|||||||
msg, err := app.email.constructReset(pwr, app, false)
|
msg, err := app.email.constructReset(pwr, app, false)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Printf("Failed to construct password reset message for %s", pwr.Username)
|
app.err.Printf("Failed to construct password reset message for \"%s\"", pwr.Username)
|
||||||
app.debug.Printf("%s: Error: %s", pwr.Username, err)
|
app.debug.Printf("%s: Error: %s", pwr.Username, err)
|
||||||
} else if err := app.sendByID(msg, uid); err != nil {
|
} else if err := app.sendByID(msg, uid); err != nil {
|
||||||
app.err.Printf("Failed to send password reset message to \"%s\"", name)
|
app.err.Printf("Failed to send password reset message to \"%s\"", name)
|
||||||
|
@ -169,6 +169,8 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
api.GET(p+"/users/announce/:name", app.GetAnnounceTemplate)
|
api.GET(p+"/users/announce/:name", app.GetAnnounceTemplate)
|
||||||
api.DELETE(p+"/users/announce/:name", app.DeleteAnnounceTemplate)
|
api.DELETE(p+"/users/announce/:name", app.DeleteAnnounceTemplate)
|
||||||
|
|
||||||
|
api.POST(p+"/users/password-reset", app.AdminPasswordReset)
|
||||||
|
|
||||||
api.GET(p+"/config/update", app.CheckUpdate)
|
api.GET(p+"/config/update", app.CheckUpdate)
|
||||||
api.POST(p+"/config/update", app.ApplyUpdate)
|
api.POST(p+"/config/update", app.ApplyUpdate)
|
||||||
api.GET(p+"/config/emails", app.GetCustomEmails)
|
api.GET(p+"/config/emails", app.GetCustomEmails)
|
||||||
|
@ -72,6 +72,10 @@ window.availableProfiles = window.availableProfiles || [];
|
|||||||
if (window.discordEnabled) {
|
if (window.discordEnabled) {
|
||||||
window.modals.discord = new Modal(document.getElementById("modal-discord"));
|
window.modals.discord = new Modal(document.getElementById("modal-discord"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (window.linkResetEnabled) {
|
||||||
|
window.modals.sendPWR = new Modal(document.getElementById("modal-send-pwr"));
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
var inviteCreator = new createInvite();
|
var inviteCreator = new createInvite();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { _get, _post, _delete, toggleLoader, addLoader, removeLoader, toDateString, insertText } from "../modules/common.js";
|
import { _get, _post, _delete, toggleLoader, addLoader, removeLoader, toDateString, insertText, toClipboard } from "../modules/common.js";
|
||||||
import { templateEmail } from "../modules/settings.js";
|
import { templateEmail } from "../modules/settings.js";
|
||||||
import { Marked } from "@ts-stack/markdown";
|
import { Marked } from "@ts-stack/markdown";
|
||||||
import { stripMarkdown } from "../modules/stripmd.js";
|
import { stripMarkdown } from "../modules/stripmd.js";
|
||||||
@ -63,7 +63,7 @@ class user implements User {
|
|||||||
id = "";
|
id = "";
|
||||||
private _selected: boolean;
|
private _selected: boolean;
|
||||||
|
|
||||||
private _lastNotifyMethod = (): string => {
|
lastNotifyMethod = (): string => {
|
||||||
// Telegram, Matrix, Discord
|
// Telegram, Matrix, Discord
|
||||||
const telegram = window.telegramEnabled && this._telegramUsername && this._telegramUsername != "";
|
const telegram = window.telegramEnabled && this._telegramUsername && this._telegramUsername != "";
|
||||||
const discord = window.discordEnabled && this._discordUsername && this._discordUsername != "";
|
const discord = window.discordEnabled && this._discordUsername && this._discordUsername != "";
|
||||||
@ -188,7 +188,7 @@ class user implements User {
|
|||||||
this._notifyDropdown.querySelector(".accounts-area-matrix").classList.add("unfocused");
|
this._notifyDropdown.querySelector(".accounts-area-matrix").classList.add("unfocused");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const lastNotifyMethod = this._lastNotifyMethod() == "matrix";
|
const lastNotifyMethod = this.lastNotifyMethod() == "matrix";
|
||||||
this._matrixID = u;
|
this._matrixID = u;
|
||||||
if (!u) {
|
if (!u) {
|
||||||
this._notifyDropdown.querySelector(".accounts-area-matrix").classList.add("unfocused");
|
this._notifyDropdown.querySelector(".accounts-area-matrix").classList.add("unfocused");
|
||||||
@ -253,7 +253,7 @@ class user implements User {
|
|||||||
this._notifyDropdown.querySelector(".accounts-area-telegram").classList.add("unfocused");
|
this._notifyDropdown.querySelector(".accounts-area-telegram").classList.add("unfocused");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const lastNotifyMethod = this._lastNotifyMethod() == "telegram";
|
const lastNotifyMethod = this.lastNotifyMethod() == "telegram";
|
||||||
this._telegramUsername = u;
|
this._telegramUsername = u;
|
||||||
if (!u) {
|
if (!u) {
|
||||||
this._notifyDropdown.querySelector(".accounts-area-telegram").classList.add("unfocused");
|
this._notifyDropdown.querySelector(".accounts-area-telegram").classList.add("unfocused");
|
||||||
@ -319,7 +319,7 @@ class user implements User {
|
|||||||
this._notifyDropdown.querySelector(".accounts-area-discord").classList.add("unfocused");
|
this._notifyDropdown.querySelector(".accounts-area-discord").classList.add("unfocused");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const lastNotifyMethod = this._lastNotifyMethod() == "discord";
|
const lastNotifyMethod = this.lastNotifyMethod() == "discord";
|
||||||
this._discordUsername = u;
|
this._discordUsername = u;
|
||||||
if (!u) {
|
if (!u) {
|
||||||
this._discord.innerHTML = `<span class="chip btn !low">Add</span>`;
|
this._discord.innerHTML = `<span class="chip btn !low">Add</span>`;
|
||||||
@ -566,6 +566,7 @@ export class accountsList {
|
|||||||
private _modifySettings = document.getElementById("accounts-modify-user") as HTMLSpanElement;
|
private _modifySettings = document.getElementById("accounts-modify-user") as HTMLSpanElement;
|
||||||
private _modifySettingsProfile = document.getElementById("radio-use-profile") as HTMLInputElement;
|
private _modifySettingsProfile = document.getElementById("radio-use-profile") as HTMLInputElement;
|
||||||
private _modifySettingsUser = document.getElementById("radio-use-user") as HTMLInputElement;
|
private _modifySettingsUser = document.getElementById("radio-use-user") as HTMLInputElement;
|
||||||
|
private _sendPWR = document.getElementById("accounts-send-pwr") as HTMLSpanElement;
|
||||||
private _profileSelect = document.getElementById("modify-user-profiles") as HTMLSelectElement;
|
private _profileSelect = document.getElementById("modify-user-profiles") as HTMLSelectElement;
|
||||||
private _userSelect = document.getElementById("modify-user-users") as HTMLSelectElement;
|
private _userSelect = document.getElementById("modify-user-users") as HTMLSelectElement;
|
||||||
private _search = document.getElementById("accounts-search") as HTMLInputElement;
|
private _search = document.getElementById("accounts-search") as HTMLInputElement;
|
||||||
@ -698,6 +699,7 @@ export class accountsList {
|
|||||||
}
|
}
|
||||||
this._extendExpiry.classList.add("unfocused");
|
this._extendExpiry.classList.add("unfocused");
|
||||||
this._disableEnable.classList.add("unfocused");
|
this._disableEnable.classList.add("unfocused");
|
||||||
|
this._sendPWR.classList.add("unfocused");
|
||||||
} else {
|
} else {
|
||||||
let visibleCount = 0;
|
let visibleCount = 0;
|
||||||
for (let id in this._users) {
|
for (let id in this._users) {
|
||||||
@ -719,6 +721,7 @@ export class accountsList {
|
|||||||
this._announceButton.classList.remove("unfocused");
|
this._announceButton.classList.remove("unfocused");
|
||||||
}
|
}
|
||||||
let anyNonExpiries = list.length == 0 ? true : false;
|
let anyNonExpiries = list.length == 0 ? true : false;
|
||||||
|
let noContactCount = 0;
|
||||||
// Only show enable/disable button if all selected have the same state.
|
// Only show enable/disable button if all selected have the same state.
|
||||||
this._shouldEnable = this._users[list[0]].disabled
|
this._shouldEnable = this._users[list[0]].disabled
|
||||||
let showDisableEnable = true;
|
let showDisableEnable = true;
|
||||||
@ -732,10 +735,19 @@ export class accountsList {
|
|||||||
this._disableEnable.classList.add("unfocused");
|
this._disableEnable.classList.add("unfocused");
|
||||||
}
|
}
|
||||||
if (!showDisableEnable && anyNonExpiries) { break; }
|
if (!showDisableEnable && anyNonExpiries) { break; }
|
||||||
|
if (!this._users[id].lastNotifyMethod() && !this._users[id].email) {
|
||||||
|
noContactCount++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!anyNonExpiries) {
|
if (!anyNonExpiries) {
|
||||||
this._extendExpiry.classList.remove("unfocused");
|
this._extendExpiry.classList.remove("unfocused");
|
||||||
}
|
}
|
||||||
|
// Only show "Send PWR" if a maximum of 1 user selected doesn't have a contact method
|
||||||
|
if (noContactCount > 1) {
|
||||||
|
this._sendPWR.classList.add("unfocused");
|
||||||
|
} else {
|
||||||
|
this._sendPWR.classList.remove("unfocused");
|
||||||
|
}
|
||||||
if (showDisableEnable) {
|
if (showDisableEnable) {
|
||||||
let message: string;
|
let message: string;
|
||||||
if (this._shouldEnable) {
|
if (this._shouldEnable) {
|
||||||
@ -1042,6 +1054,58 @@ export class accountsList {
|
|||||||
};
|
};
|
||||||
window.modals.deleteUser.show();
|
window.modals.deleteUser.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendPWR = () => {
|
||||||
|
addLoader(this._sendPWR);
|
||||||
|
let list = this._collectUsers();
|
||||||
|
let manualUser: user;
|
||||||
|
for (let id of list) {
|
||||||
|
let user = this._users[id];
|
||||||
|
console.log(user, user.notify_email, user.notify_matrix, user.notify_discord, user.notify_telegram);
|
||||||
|
if (!user.lastNotifyMethod() && !user.email) {
|
||||||
|
manualUser = user;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const messageBox = document.getElementById("send-pwr-note") as HTMLParagraphElement;
|
||||||
|
let message: string;
|
||||||
|
let send = {
|
||||||
|
users: list
|
||||||
|
};
|
||||||
|
_post("/users/password-reset", send, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState != 4) return;
|
||||||
|
removeLoader(this._sendPWR);
|
||||||
|
let link: string;
|
||||||
|
if (req.status == 200) {
|
||||||
|
link = req.response["link"];
|
||||||
|
if (req.response["manual"] as boolean) {
|
||||||
|
message = window.lang.var("strings", "sendPWRManual", manualUser.name);
|
||||||
|
} else {
|
||||||
|
message = window.lang.strings("sendPWRSuccess") + " " + window.lang.strings("sendPWRSuccessManual");
|
||||||
|
}
|
||||||
|
} else if (req.status == 204) {
|
||||||
|
message = window.lang.strings("sendPWRSuccess");
|
||||||
|
} else {
|
||||||
|
window.notifications.customError("errorSendPWR", window.lang.strings("errorFailureCheckLogs"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message += " " + window.lang.strings("sendPWRValidFor");
|
||||||
|
messageBox.textContent = message;
|
||||||
|
let linkButton = document.getElementById("send-pwr-link") as HTMLSpanElement;
|
||||||
|
linkButton.onclick = () => {
|
||||||
|
toClipboard(link);
|
||||||
|
linkButton.textContent = window.lang.strings("copied");
|
||||||
|
linkButton.classList.add("~positive");
|
||||||
|
linkButton.classList.remove("~urge");
|
||||||
|
setTimeout(() => {
|
||||||
|
linkButton.textContent = window.lang.strings("copy");
|
||||||
|
linkButton.classList.add("~urge");
|
||||||
|
linkButton.classList.remove("~positive");
|
||||||
|
}, 800);
|
||||||
|
};
|
||||||
|
window.modals.sendPWR.show();
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
modifyUsers = () => {
|
modifyUsers = () => {
|
||||||
const modalHeader = document.getElementById("header-modify-user");
|
const modalHeader = document.getElementById("header-modify-user");
|
||||||
@ -1203,6 +1267,12 @@ export class accountsList {
|
|||||||
this._addUserName.classList.add("unfocused");
|
this._addUserName.classList.add("unfocused");
|
||||||
this._addUserName = this._addUserEmail;
|
this._addUserName = this._addUserEmail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!window.linkResetEnabled) {
|
||||||
|
this._sendPWR.classList.add("unfocused");
|
||||||
|
} else {
|
||||||
|
this._sendPWR.onclick = this.sendPWR;
|
||||||
|
}
|
||||||
/*if (!window.emailEnabled) {
|
/*if (!window.emailEnabled) {
|
||||||
this._deleteNotify.parentElement.classList.add("unfocused");
|
this._deleteNotify.parentElement.classList.add("unfocused");
|
||||||
this._deleteNotify.checked = false;
|
this._deleteNotify.checked = false;
|
||||||
|
@ -25,6 +25,7 @@ declare interface Window {
|
|||||||
matrixEnabled: boolean;
|
matrixEnabled: boolean;
|
||||||
ombiEnabled: boolean;
|
ombiEnabled: boolean;
|
||||||
usernameEnabled: boolean;
|
usernameEnabled: boolean;
|
||||||
|
linkResetEnabled: boolean;
|
||||||
token: string;
|
token: string;
|
||||||
buttonWidth: number;
|
buttonWidth: number;
|
||||||
transitionEvent: string;
|
transitionEvent: string;
|
||||||
@ -105,6 +106,7 @@ declare interface Modals {
|
|||||||
telegram: Modal;
|
telegram: Modal;
|
||||||
discord: Modal;
|
discord: Modal;
|
||||||
matrix: Modal;
|
matrix: Modal;
|
||||||
|
sendPWR?: Modal;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Invite {
|
interface Invite {
|
||||||
|
102
views.go
102
views.go
@ -116,23 +116,24 @@ func (app *appContext) AdminPage(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
license = string(l)
|
license = string(l)
|
||||||
gcHTML(gc, http.StatusOK, "admin.html", gin.H{
|
gcHTML(gc, http.StatusOK, "admin.html", gin.H{
|
||||||
"urlBase": app.getURLBase(gc),
|
"urlBase": app.getURLBase(gc),
|
||||||
"cssClass": app.cssClass,
|
"cssClass": app.cssClass,
|
||||||
"contactMessage": "",
|
"contactMessage": "",
|
||||||
"emailEnabled": emailEnabled,
|
"emailEnabled": emailEnabled,
|
||||||
"telegramEnabled": telegramEnabled,
|
"telegramEnabled": telegramEnabled,
|
||||||
"discordEnabled": discordEnabled,
|
"discordEnabled": discordEnabled,
|
||||||
"matrixEnabled": matrixEnabled,
|
"matrixEnabled": matrixEnabled,
|
||||||
"ombiEnabled": ombiEnabled,
|
"ombiEnabled": ombiEnabled,
|
||||||
"notifications": notificationsEnabled,
|
"linkResetEnabled": app.config.Section("password_resets").Key("link_reset").MustBool(false),
|
||||||
"version": version,
|
"notifications": notificationsEnabled,
|
||||||
"commit": commit,
|
"version": version,
|
||||||
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
"commit": commit,
|
||||||
"strings": app.storage.lang.Admin[lang].Strings,
|
"username": !app.config.Section("email").Key("no_username").MustBool(false),
|
||||||
"quantityStrings": app.storage.lang.Admin[lang].QuantityStrings,
|
"strings": app.storage.lang.Admin[lang].Strings,
|
||||||
"language": app.storage.lang.Admin[lang].JSON,
|
"quantityStrings": app.storage.lang.Admin[lang].QuantityStrings,
|
||||||
"langName": lang,
|
"language": app.storage.lang.Admin[lang].JSON,
|
||||||
"license": license,
|
"langName": lang,
|
||||||
|
"license": license,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,6 +155,11 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
|
|||||||
"success": false,
|
"success": false,
|
||||||
"ombiEnabled": app.config.Section("ombi").Key("enabled").MustBool(false),
|
"ombiEnabled": app.config.Section("ombi").Key("enabled").MustBool(false),
|
||||||
}
|
}
|
||||||
|
if _, ok := app.internalPWRs[pin]; !ok {
|
||||||
|
app.debug.Printf("Ignoring PWR request due to invalid internal PIN: %s", pin)
|
||||||
|
app.NoRouteHandler(gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
if setPassword {
|
if setPassword {
|
||||||
data["helpMessage"] = app.config.Section("ui").Key("help_message").String()
|
data["helpMessage"] = app.config.Section("ui").Key("help_message").String()
|
||||||
data["successMessage"] = app.config.Section("ui").Key("success_message").String()
|
data["successMessage"] = app.config.Section("ui").Key("success_message").String()
|
||||||
@ -177,33 +183,47 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
|
|||||||
app.debug.Println("PWR: Ignoring magic link visit from bot")
|
app.debug.Println("PWR: Ignoring magic link visit from bot")
|
||||||
data["success"] = true
|
data["success"] = true
|
||||||
data["pin"] = "NO-BO-TS"
|
data["pin"] = "NO-BO-TS"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// if reset, ok := app.internalPWRs[pin]; ok {
|
||||||
|
// status, err := app.jf.ResetPasswordAdmin(reset.ID)
|
||||||
|
// if !(status == 200 || status == 204) || err != nil {
|
||||||
|
// app.err.Printf("Password Reset failed (%d): %v", status, err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// status, err = app.jf.SetPassword(reset.ID, "", pin)
|
||||||
|
// if !(status == 200 || status == 204) || err != nil {
|
||||||
|
// app.err.Printf("Password Reset failed (%d): %v", status, err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// data["success"] = true
|
||||||
|
// data["pin"] = pin
|
||||||
|
// }
|
||||||
|
resp, status, err := app.jf.ResetPassword(pin)
|
||||||
|
if status == 200 && err == nil && resp.Success {
|
||||||
|
data["success"] = true
|
||||||
|
data["pin"] = pin
|
||||||
} else {
|
} else {
|
||||||
resp, status, err := app.jf.ResetPassword(pin)
|
app.err.Printf("Password Reset failed (%d): %v", status, err)
|
||||||
if status == 200 && err == nil && resp.Success {
|
}
|
||||||
data["success"] = true
|
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||||
data["pin"] = pin
|
jfUser, status, err := app.jf.UserByName(resp.UsersReset[0], false)
|
||||||
} else {
|
if status != 200 || err != nil {
|
||||||
app.err.Printf("Password Reset failed (%d): %v", status, err)
|
app.err.Printf("Failed to get user \"%s\" from jellyfin/emby (%d): %v", resp.UsersReset[0], status, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
ombiUser, status, err := app.getOmbiUser(jfUser.ID)
|
||||||
jfUser, status, err := app.jf.UserByName(resp.UsersReset[0], false)
|
if status != 200 || err != nil {
|
||||||
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 jellyfin/emby (%d): %v", resp.UsersReset[0], status, err)
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
ombiUser, status, err := app.getOmbiUser(jfUser.ID)
|
|
||||||
if status != 200 || err != nil {
|
|
||||||
app.err.Printf("Failed to get user \"%s\" from ombi (%d): %v", resp.UsersReset[0], status, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ombiUser["password"] = pin
|
|
||||||
status, err = app.ombi.ModifyUser(ombiUser)
|
|
||||||
if status != 200 || err != nil {
|
|
||||||
app.err.Printf("Failed to set password for ombi user \"%s\" (%d): %v", ombiUser["userName"], status, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
app.debug.Printf("Reset password for ombi user \"%s\"", ombiUser["userName"])
|
|
||||||
}
|
}
|
||||||
|
ombiUser["password"] = pin
|
||||||
|
status, err = app.ombi.ModifyUser(ombiUser)
|
||||||
|
if status != 200 || err != nil {
|
||||||
|
app.err.Printf("Failed to set password for ombi user \"%s\" (%d): %v", ombiUser["userName"], status, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
app.debug.Printf("Reset password for ombi user \"%s\"", ombiUser["userName"])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user