1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-06-23 01:47:46 +02:00
jfa-go/pwreset.go
Harvey Tindall f88f71d933
pwreset: Stop daemon on restart, dont fail if json is malformed
daemon now stops on the RESTART signal, and when it fails to read JSON,
it no longer stops the whole daemon.
2023-06-11 16:35:41 +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)
}
<-RESTART
}
// 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)
}
}
}