1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-11-14 14:20:11 +00:00
jfa-go/pwreset.go
Harvey Tindall ad40d7d8a9
fix bugs with restarts/interrupts
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
2023-06-11 19:50:50 +01:00

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)
}
}
}