mirror of
https://github.com/hrfee/jfa-go.git
synced 2026-03-18 21:50:33 +01:00
Compare commits
2 Commits
d7fdf29c7c
...
76878976ee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76878976ee | ||
|
|
5aa640d63d |
@@ -1179,6 +1179,13 @@ sections:
|
||||
type: text
|
||||
value: /path/to/jellyfin
|
||||
description: Path to the folder Jellyfin puts password-reset files.
|
||||
- setting: watch_polling
|
||||
name: Use polling
|
||||
requires_restart: true
|
||||
depends_true: watch_directory
|
||||
type: bool
|
||||
value: false
|
||||
description: Use if mounting over network (NFS/SMB/SFTP). Watch the Jellyfin directory by checking periodically, rather than using OS APIs.
|
||||
- setting: link_reset
|
||||
name: Use reset link instead of PIN (Required for Ombi)
|
||||
requires_restart: true
|
||||
|
||||
1
go.mod
1
go.mod
@@ -109,6 +109,7 @@ require (
|
||||
github.com/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/quic-go/quic-go v0.57.0 // indirect
|
||||
github.com/radovskyb/watcher v1.0.7 // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/swaggo/swag v1.16.6 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -279,6 +279,8 @@ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.57.0 h1:AsSSrrMs4qI/hLrKlTH/TGQeTMY0ib1pAOX7vA3AdqE=
|
||||
github.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
|
||||
github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE=
|
||||
github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg=
|
||||
github.com/robert-nix/ansihtml v1.0.1 h1:VTiyQ6/+AxSJoSSLsMecnkh8i0ZqOEdiRl/odOc64fc=
|
||||
github.com/robert-nix/ansihtml v1.0.1/go.mod h1:CJwclxYaTPc2RfcxtanEACsYuTksh4yDXcNeHHKZINE=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
|
||||
133
pwreset.go
133
pwreset.go
@@ -10,6 +10,12 @@ import (
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
lm "github.com/hrfee/jfa-go/logmessages"
|
||||
pollingWatcher "github.com/radovskyb/watcher"
|
||||
)
|
||||
|
||||
const (
|
||||
RetryCount = 2
|
||||
RetryInterval = time.Second
|
||||
)
|
||||
|
||||
// GenInternalReset generates a local password reset PIN, for use with the PWR option on the Admin page.
|
||||
@@ -48,17 +54,35 @@ func (app *appContext) StartPWR() {
|
||||
return
|
||||
}
|
||||
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedStartDaemon, "PWR", err)
|
||||
usePolling := app.config.Section("password_resets").Key("watch_polling").MustBool(false)
|
||||
|
||||
if !messagesEnabled {
|
||||
return
|
||||
}
|
||||
defer watcher.Close()
|
||||
if usePolling {
|
||||
watcher := pollingWatcher.New()
|
||||
watcher.FilterOps(pollingWatcher.Write)
|
||||
|
||||
go pwrMonitor(app, watcher)
|
||||
err = watcher.Add(path)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedStartDaemon, "PWR", err)
|
||||
go monitorPolling(app, watcher)
|
||||
if err := watcher.Add(path); err != nil {
|
||||
app.err.Printf(lm.FailedStartDaemon, "PWR (polling)", err)
|
||||
}
|
||||
if err := watcher.Start(time.Second * 5); err != nil {
|
||||
app.err.Printf(lm.FailedStartDaemon, "PWR (polling)", err)
|
||||
}
|
||||
} else {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedStartDaemon, "PWR", err)
|
||||
return
|
||||
}
|
||||
defer watcher.Close()
|
||||
|
||||
go monitorFS(app, watcher)
|
||||
err = watcher.Add(path)
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedStartDaemon, "PWR", err)
|
||||
}
|
||||
}
|
||||
|
||||
waitForRestart()
|
||||
@@ -72,52 +96,55 @@ type PasswordReset struct {
|
||||
Internal bool `json:"Internal,omitempty"`
|
||||
}
|
||||
|
||||
func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
|
||||
if !messagesEnabled {
|
||||
func validatePWR(app *appContext, fname string, attempt int) {
|
||||
currentTime := time.Now()
|
||||
if !strings.Contains(fname, "passwordreset") {
|
||||
return
|
||||
}
|
||||
var pwr PasswordReset
|
||||
data, err := os.ReadFile(fname)
|
||||
if err != nil {
|
||||
app.debug.Printf(lm.FailedReading, fname, err)
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(data, &pwr)
|
||||
if len(pwr.Pin) == 0 || err != nil {
|
||||
app.debug.Printf(lm.FailedReading, fname, err)
|
||||
return
|
||||
}
|
||||
app.info.Printf(lm.NewPWRForUser, pwr.Username)
|
||||
if pwr.Expiry.Before(currentTime) {
|
||||
app.err.Printf(lm.PWRExpired, pwr.Username, pwr.Expiry)
|
||||
return
|
||||
}
|
||||
user, err := app.jf.UserByName(pwr.Username, false)
|
||||
if err != nil || user.ID == "" {
|
||||
app.err.Printf(lm.FailedGetUser, pwr.Username, lm.Jellyfin, err)
|
||||
return
|
||||
}
|
||||
name := app.getAddressOrName(user.ID)
|
||||
if name != "" {
|
||||
msg, err := app.email.constructReset(pwr, false)
|
||||
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedConstructPWRMessage, pwr.Username, err)
|
||||
} else if err := app.sendByID(msg, user.ID); err != nil {
|
||||
app.err.Printf(lm.FailedSendPWRMessage, pwr.Username, name, err)
|
||||
} else {
|
||||
app.err.Printf(lm.SentPWRMessage, pwr.Username, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func monitorFS(app *appContext, watcher *fsnotify.Watcher) {
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if event.Op&fsnotify.Write == fsnotify.Write && strings.Contains(event.Name, "passwordreset") {
|
||||
var pwr PasswordReset
|
||||
data, err := os.ReadFile(event.Name)
|
||||
if err != nil {
|
||||
app.debug.Printf(lm.FailedReading, event.Name, err)
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(data, &pwr)
|
||||
if len(pwr.Pin) == 0 || err != nil {
|
||||
app.debug.Printf(lm.FailedReading, event.Name, err)
|
||||
continue
|
||||
}
|
||||
app.info.Printf(lm.NewPWRForUser, pwr.Username)
|
||||
if currentTime := time.Now(); pwr.Expiry.After(currentTime) {
|
||||
user, err := app.jf.UserByName(pwr.Username, false)
|
||||
if err != nil || user.ID == "" {
|
||||
app.err.Printf(lm.FailedGetUser, pwr.Username, lm.Jellyfin, err)
|
||||
return
|
||||
}
|
||||
uid := user.ID
|
||||
name := app.getAddressOrName(uid)
|
||||
if name != "" {
|
||||
msg, err := app.email.constructReset(pwr, false)
|
||||
|
||||
if err != nil {
|
||||
app.err.Printf(lm.FailedConstructPWRMessage, pwr.Username, err)
|
||||
} else if err := app.sendByID(msg, uid); err != nil {
|
||||
app.err.Printf(lm.FailedSendPWRMessage, pwr.Username, name, err)
|
||||
} else {
|
||||
app.err.Printf(lm.SentPWRMessage, pwr.Username, name)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
app.err.Printf(lm.PWRExpired, pwr.Username, pwr.Expiry)
|
||||
}
|
||||
|
||||
if event.Has(fsnotify.Write) {
|
||||
validatePWR(app, event.Name, 0)
|
||||
}
|
||||
case err, ok := <-watcher.Errors:
|
||||
if !ok {
|
||||
@@ -127,3 +154,17 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func monitorPolling(app *appContext, watcher *pollingWatcher.Watcher) {
|
||||
for {
|
||||
select {
|
||||
case event := <-watcher.Event:
|
||||
validatePWR(app, event.Path, 0)
|
||||
case err := <-watcher.Error:
|
||||
app.err.Printf(lm.FailedStartDaemon, "PWR (polling)", err)
|
||||
return
|
||||
case <-watcher.Closed:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,6 @@ class DOMSetting {
|
||||
this._hideEl.classList.remove("unfocused");
|
||||
}
|
||||
document.dispatchEvent(changedEvent(this._section, this.setting, this.valueAsString(), v));
|
||||
console.log(`dispatched settings-${this._section}-${this.setting} = ${this.valueAsString()}/${v}`);
|
||||
}
|
||||
|
||||
private _advancedListener = (event: advancedEvent) => {
|
||||
|
||||
Reference in New Issue
Block a user