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
|
type: text
|
||||||
value: /path/to/jellyfin
|
value: /path/to/jellyfin
|
||||||
description: Path to the folder Jellyfin puts password-reset files.
|
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
|
- setting: link_reset
|
||||||
name: Use reset link instead of PIN (Required for Ombi)
|
name: Use reset link instead of PIN (Required for Ombi)
|
||||||
requires_restart: true
|
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/petermattis/goid v0.0.0-20251121121749-a11dd1a45f9a // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.57.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/rs/zerolog v1.34.0 // indirect
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||||
github.com/swaggo/swag v1.16.6 // 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/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 h1:AsSSrrMs4qI/hLrKlTH/TGQeTMY0ib1pAOX7vA3AdqE=
|
||||||
github.com/quic-go/quic-go v0.57.0/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
|
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 h1:VTiyQ6/+AxSJoSSLsMecnkh8i0ZqOEdiRl/odOc64fc=
|
||||||
github.com/robert-nix/ansihtml v1.0.1/go.mod h1:CJwclxYaTPc2RfcxtanEACsYuTksh4yDXcNeHHKZINE=
|
github.com/robert-nix/ansihtml v1.0.1/go.mod h1:CJwclxYaTPc2RfcxtanEACsYuTksh4yDXcNeHHKZINE=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
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"
|
"github.com/fsnotify/fsnotify"
|
||||||
lm "github.com/hrfee/jfa-go/logmessages"
|
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.
|
// 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
watcher, err := fsnotify.NewWatcher()
|
usePolling := app.config.Section("password_resets").Key("watch_polling").MustBool(false)
|
||||||
if err != nil {
|
|
||||||
app.err.Printf(lm.FailedStartDaemon, "PWR", err)
|
if !messagesEnabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer watcher.Close()
|
if usePolling {
|
||||||
|
watcher := pollingWatcher.New()
|
||||||
|
watcher.FilterOps(pollingWatcher.Write)
|
||||||
|
|
||||||
go pwrMonitor(app, watcher)
|
go monitorPolling(app, watcher)
|
||||||
err = watcher.Add(path)
|
if err := watcher.Add(path); err != nil {
|
||||||
if err != nil {
|
app.err.Printf(lm.FailedStartDaemon, "PWR (polling)", err)
|
||||||
app.err.Printf(lm.FailedStartDaemon, "PWR", 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()
|
waitForRestart()
|
||||||
@@ -72,52 +96,55 @@ type PasswordReset struct {
|
|||||||
Internal bool `json:"Internal,omitempty"`
|
Internal bool `json:"Internal,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
|
func validatePWR(app *appContext, fname string, attempt int) {
|
||||||
if !messagesEnabled {
|
currentTime := time.Now()
|
||||||
|
if !strings.Contains(fname, "passwordreset") {
|
||||||
return
|
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 {
|
for {
|
||||||
select {
|
select {
|
||||||
case event, ok := <-watcher.Events:
|
case event, ok := <-watcher.Events:
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if event.Op&fsnotify.Write == fsnotify.Write && strings.Contains(event.Name, "passwordreset") {
|
if event.Has(fsnotify.Write) {
|
||||||
var pwr PasswordReset
|
validatePWR(app, event.Name, 0)
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
case err, ok := <-watcher.Errors:
|
case err, ok := <-watcher.Errors:
|
||||||
if !ok {
|
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");
|
this._hideEl.classList.remove("unfocused");
|
||||||
}
|
}
|
||||||
document.dispatchEvent(changedEvent(this._section, this.setting, this.valueAsString(), v));
|
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) => {
|
private _advancedListener = (event: advancedEvent) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user