mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 17:10:10 +00:00
Harvey Tindall
ad40d7d8a9
The password reset daemon wasn't being closed on restarts, so an extra pwr would be sent w/ every restart. Restarts & Interrupts (Ctrl-C) rarely worked, as there were multiple listeners to the "RESTART" channel, and I didn't know the message was consumed by whoever got it first, meaning if the main thread didn't get it first, the app wouldn't quit. Listeners are now registered, and the restart message is re-broadcasted until everyone's got it. Fixes #264
123 lines
3.4 KiB
Go
123 lines
3.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"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() {
|
|
app.info.Println("Starting password reset daemon")
|
|
path := app.config.Section("password_resets").Key("watch_directory").String()
|
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
app.err.Printf("Failed to start password reset daemon: Directory \"%s\" doesn't exist", path)
|
|
return
|
|
}
|
|
|
|
watcher, err := fsnotify.NewWatcher()
|
|
if err != nil {
|
|
app.err.Printf("Couldn't initialise password reset daemon")
|
|
return
|
|
}
|
|
defer watcher.Close()
|
|
|
|
go pwrMonitor(app, watcher)
|
|
err = watcher.Add(path)
|
|
if err != nil {
|
|
app.err.Printf("Failed to start password reset daemon: %s", err)
|
|
}
|
|
|
|
waitForRestart()
|
|
}
|
|
|
|
// PasswordReset represents a passwordreset-xyz.json file generated by Jellyfin.
|
|
type PasswordReset struct {
|
|
Pin string `json:"Pin"`
|
|
Username string `json:"UserName"`
|
|
Expiry time.Time `json:"ExpirationDate"`
|
|
Internal bool `json:"Internal,omitempty"`
|
|
}
|
|
|
|
func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
|
|
if !emailEnabled {
|
|
return
|
|
}
|
|
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("PWR: Failed to read file: %v", err)
|
|
return
|
|
}
|
|
err = json.Unmarshal(data, &pwr)
|
|
if len(pwr.Pin) == 0 || err != nil {
|
|
app.debug.Printf("PWR: Failed to read PIN: %v", err)
|
|
continue
|
|
}
|
|
app.info.Printf("New password reset for user \"%s\"", pwr.Username)
|
|
if currentTime := time.Now(); pwr.Expiry.After(currentTime) {
|
|
user, status, err := app.jf.UserByName(pwr.Username, false)
|
|
if !(status == 200 || status == 204) || err != nil {
|
|
app.err.Printf("Failed to get users from Jellyfin: Code %d", status)
|
|
app.debug.Printf("Error: %s", err)
|
|
return
|
|
}
|
|
app.storage.loadEmails()
|
|
uid := user.ID
|
|
if uid == "" {
|
|
app.err.Printf("Couldn't get user ID for user \"%s\"", pwr.Username)
|
|
return
|
|
}
|
|
name := app.getAddressOrName(uid)
|
|
if name != "" {
|
|
msg, err := app.email.constructReset(pwr, app, false)
|
|
|
|
if err != nil {
|
|
app.err.Printf("Failed to construct password reset message for \"%s\"", pwr.Username)
|
|
app.debug.Printf("%s: Error: %s", pwr.Username, err)
|
|
} else if err := app.sendByID(msg, uid); err != nil {
|
|
app.err.Printf("Failed to send password reset message to \"%s\"", name)
|
|
app.debug.Printf("%s: Error: %s", pwr.Username, err)
|
|
} else {
|
|
app.info.Printf("Sent password reset message to \"%s\"", name)
|
|
}
|
|
}
|
|
} else {
|
|
app.err.Printf("Password reset for user \"%s\" has already expired (%s). Check your time settings.", pwr.Username, pwr.Expiry)
|
|
}
|
|
|
|
}
|
|
case err, ok := <-watcher.Errors:
|
|
if !ok {
|
|
return
|
|
}
|
|
app.err.Printf("Password reset daemon: %s", err)
|
|
}
|
|
}
|
|
}
|