1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-11-09 20:00:12 +00:00

logmessages: all log strings in one file

EXCEPT: migrations.go, log strings there aren't gonna be repeated
anywhere else, are very specific, and will probably change a lot.
This commit is contained in:
Harvey Tindall 2024-08-01 20:17:05 +01:00
parent 15a317f84f
commit 711394232b
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
25 changed files with 410 additions and 1740 deletions

1503
:w

File diff suppressed because it is too large Load Diff

View File

@ -66,7 +66,7 @@ func (app *appContext) SetJellyseerrProfile(gc *gin.Context) {
profile.Jellyseerr.User = u.UserTemplate
n, err := app.js.GetNotificationPreferencesByID(jellyseerrID)
if err != nil {
app.err.Printf(lm.FailedGetJellyseerrNotificationPrefs, err)
app.err.Printf(lm.FailedGetJellyseerrNotificationPrefs, gc.Param("id"), err)
respond(500, "Couldn't get user notification prefs", gc)
return
}

View File

@ -91,7 +91,7 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) {
errors, code, err := app.ombi.NewUser(req.Username, req.Password, req.Email, profile.Ombi)
if err != nil || code != 200 {
app.err.Printf(lm.FailedCreateUser, lm.Ombi, req.Username, err)
app.debug.Printf(lm.AdditionalOmbiErrors, strings.Join(errors, ", "))
app.debug.Printf(lm.AdditionalErrors, lm.Ombi, strings.Join(errors, ", "))
} else {
app.info.Printf(lm.CreateUser, lm.Ombi, req.Username)
}
@ -465,7 +465,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
}
} else {
app.info.Printf(lm.FailedCreateUser, lm.Ombi, req.Username, err)
app.debug.Printf(lm.AdditionalOmbiErrors, strings.Join(errors, ", "))
app.debug.Printf(lm.AdditionalErrors, lm.Ombi, strings.Join(errors, ", "))
}
} else {
ombiUser, status, err = app.getOmbiUser(id)
@ -490,7 +490,7 @@ func (app *appContext) newUser(req newUserDTO, confirmed bool, gc *gin.Context)
resp, status, err := app.ombi.SetNotificationPrefs(ombiUser, dID, tUser)
if !(status == 200 || status == 204) || err != nil {
app.err.Printf(lm.FailedSyncContactMethods, lm.Ombi, err)
app.debug.Printf(lm.AdditionalOmbiErrors, resp)
app.debug.Printf(lm.AdditionalErrors, lm.Ombi, resp)
}
}
}

63
api.go
View File

@ -1,10 +1,12 @@
package main
import (
"fmt"
"strings"
"time"
"github.com/gin-gonic/gin"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/mediabrowser"
"github.com/itchyny/timefmt-go"
"github.com/lithammer/shortuuid/v3"
@ -122,14 +124,14 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
}
}
if !valid || req.PIN == "" {
app.info.Printf("%s: Password reset failed: Invalid password", req.PIN)
app.info.Printf(lm.FailedChangePassword, lm.Jellyfin, "?", lm.InvalidPassword)
gc.JSON(400, validation)
return
}
isInternal := false
if captcha && !app.verifyCaptcha(req.PIN, req.PIN, req.CaptchaText, true) {
app.info.Printf("%s: PWR Failed: Captcha Incorrect", req.PIN)
app.info.Printf(lm.FailedChangePassword, lm.Jellyfin, "?", lm.IncorrectCaptcha)
respond(400, "errorCaptcha", gc)
return
}
@ -138,7 +140,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
if reset, ok := app.internalPWRs[req.PIN]; ok {
isInternal = true
if time.Now().After(reset.Expiry) {
app.info.Printf("Password reset failed: PIN \"%s\" has expired", reset.PIN)
app.info.Printf(lm.FailedChangePassword, lm.Jellyfin, "?", fmt.Sprintf(lm.ExpiredPIN, reset.PIN))
respondBool(401, false, gc)
delete(app.internalPWRs, req.PIN)
return
@ -148,7 +150,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
status, err := app.jf.ResetPasswordAdmin(userID)
if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Password Reset failed (%d): %v", status, err)
app.err.Printf(lm.FailedChangePassword, lm.Jellyfin, userID, err)
respondBool(status, false, gc)
return
}
@ -156,7 +158,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
} else {
resp, status, err := app.jf.ResetPassword(req.PIN)
if status != 200 || err != nil || !resp.Success {
app.err.Printf("Password Reset failed (%d): %v", status, err)
app.err.Printf(lm.FailedChangePassword, lm.Jellyfin, userID, err)
respondBool(status, false, gc)
return
}
@ -176,7 +178,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
user, status, err = app.jf.UserByName(username, false)
}
if status != 200 || err != nil {
app.err.Printf("Failed to get user \"%s\" (%d): %v", username, status, err)
app.err.Printf(lm.FailedGetUser, userID, lm.Jellyfin, err)
respondBool(500, false, gc)
return
}
@ -195,31 +197,33 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
}
status, err = app.jf.SetPassword(user.ID, prevPassword, req.Password)
if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to change password for \"%s\" (%d): %v", username, status, err)
app.err.Printf(lm.FailedChangePassword, lm.Jellyfin, user.ID, err)
respondBool(500, false, gc)
return
}
if app.config.Section("ombi").Key("enabled").MustBool(false) {
// Silently fail for changing ombi passwords
// This makes no sense so has been commented out.
// It probably did at some point in the past.
/* Silently fail for changing ombi passwords
if (status != 200 && status != 204) || err != nil {
app.err.Printf("Failed to get user \"%s\" from jellyfin/emby (%d): %v", username, status, err)
app.err.Printf(lm.FailedGetUser, user.ID, lm.Jellyfin, err)
respondBool(200, true, gc)
return
}
} */
ombiUser, status, err := app.getOmbiUser(user.ID)
if status != 200 || err != nil {
app.err.Printf("Failed to get user \"%s\" from ombi (%d): %v", username, status, err)
app.err.Printf(lm.FailedGetUser, user.ID, lm.Ombi, err)
respondBool(200, true, gc)
return
}
ombiUser["password"] = req.Password
status, err = app.ombi.ModifyUser(ombiUser)
if status != 200 || err != nil {
app.err.Printf("Failed to set password for ombi user \"%s\" (%d): %v", ombiUser["userName"], status, err)
app.err.Printf(lm.FailedChangePassword, lm.Ombi, user.ID, err)
respondBool(200, true, gc)
return
}
app.debug.Printf("Reset password for ombi user \"%s\"", ombiUser["userName"])
app.debug.Printf(lm.ChangePassword, lm.Ombi, user.ID)
}
respondBool(200, true, gc)
}
@ -231,7 +235,6 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
// @Security Bearer
// @tags Configuration
func (app *appContext) GetConfig(gc *gin.Context) {
app.info.Println("Config requested")
resp := app.configBase
// Load language options
formOptions := app.storage.lang.User.getOptions()
@ -341,7 +344,6 @@ func (app *appContext) GetConfig(gc *gin.Context) {
// @Security Bearer
// @tags Configuration
func (app *appContext) ModifyConfig(gc *gin.Context) {
app.info.Println("Config modification requested")
var req configDTO
gc.BindJSON(&req)
// Load a new config, as we set various default values in app.config that shouldn't be stored.
@ -366,26 +368,18 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
}
tempConfig.Section("").Key("first_run").SetValue("false")
if err := tempConfig.SaveTo(app.configPath); err != nil {
app.err.Printf("Failed to save config to \"%s\": %v", app.configPath, err)
app.err.Printf(lm.FailedWriting, app.configPath, err)
respond(500, err.Error(), gc)
return
}
app.debug.Println("Config saved")
app.info.Printf(lm.ModifyConfig, app.configPath)
gc.JSON(200, map[string]bool{"success": true})
if req["restart-program"] != nil && req["restart-program"].(bool) {
app.info.Println("Restarting...")
if TRAY {
TRAYRESTART <- true
} else {
RESTART <- true
}
// Safety Sleep (Ensure shutdown tasks get done)
time.Sleep(time.Second)
app.Restart()
}
app.loadConfig()
// Reinitialize password validator on config change, as opposed to every applicable request like in python.
if _, ok := req["password_validation"]; ok {
app.debug.Println("Reinitializing validator")
validatorConf := ValidatorConf{
"length": app.config.Section("password_validation").Key("min_length").MustInt(0),
"uppercase": app.config.Section("password_validation").Key("upper").MustInt(0),
@ -425,12 +419,13 @@ func (app *appContext) CheckUpdate(gc *gin.Context) {
// @tags Configuration
func (app *appContext) ApplyUpdate(gc *gin.Context) {
if !app.update.CanUpdate {
respond(400, "Update is manual", gc)
app.info.Printf(lm.FailedApplyUpdate, lm.UpdateManual)
respond(400, lm.UpdateManual, gc)
return
}
err := app.update.update()
if err != nil {
app.err.Printf("Failed to apply update: %v", err)
app.err.Printf(lm.FailedApplyUpdate, err)
respondBool(500, false, gc)
return
}
@ -452,8 +447,9 @@ func (app *appContext) ApplyUpdate(gc *gin.Context) {
func (app *appContext) Logout(gc *gin.Context) {
cookie, err := gc.Cookie("refresh")
if err != nil {
app.debug.Printf("Couldn't get cookies: %s", err)
respond(500, "Couldn't fetch cookies", gc)
msg := fmt.Sprintf(lm.FailedGetCookies, "refresh", err)
app.debug.Println(msg)
respond(500, msg, gc)
return
}
app.invalidTokens = append(app.invalidTokens, cookie)
@ -526,11 +522,7 @@ func (app *appContext) ServeLang(gc *gin.Context) {
// @Security Bearer
// @tags Other
func (app *appContext) restart(gc *gin.Context) {
app.info.Println("Restarting...")
err := app.Restart()
if err != nil {
app.err.Printf("Couldn't restart, try restarting manually: %v", err)
}
app.Restart()
}
// @Summary Returns the last 100 lines of the log.
@ -544,6 +536,7 @@ func (app *appContext) GetLog(gc *gin.Context) {
// no need to syscall.exec anymore!
func (app *appContext) Restart() error {
app.info.Println(lm.Restarting)
if TRAY {
TRAYRESTART <- true
} else {

65
auth.go
View File

@ -9,6 +9,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/mediabrowser"
"github.com/lithammer/shortuuid/v3"
)
@ -41,6 +42,8 @@ func (app *appContext) webAuth() gin.HandlerFunc {
return app.authenticate
}
func (app *appContext) authLog(v any) { app.debug.Printf(lm.FailedAuthRequest, v) }
// CreateToken returns a web token as well as a refresh token, which can be used to obtain new tokens.
func CreateToken(userId, jfId string, admin bool) (string, string, error) {
var token, refresh string
@ -72,32 +75,26 @@ func (app *appContext) decodeValidateAuthHeader(gc *gin.Context) (claims jwt.Map
ok = false
header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2)
if header[0] != "Bearer" {
app.debug.Println("Invalid authorization header")
app.authLog(lm.InvalidAuthHeader)
respond(401, "Unauthorized", gc)
return
}
token, err := jwt.Parse(string(header[1]), checkToken)
if err != nil {
app.debug.Printf("Auth denied: %s", err)
app.authLog(fmt.Sprintf(lm.FailedParseJWT, err))
respond(401, "Unauthorized", gc)
return
}
claims, ok = token.Claims.(jwt.MapClaims)
if !ok {
app.debug.Println("Invalid JWT")
app.authLog(lm.FailedCastJWT)
respond(401, "Unauthorized", gc)
return
}
expiryUnix := int64(claims["exp"].(float64))
if err != nil {
app.debug.Printf("Auth denied: %s", err)
respond(401, "Unauthorized", gc)
ok = false
return
}
expiry := time.Unix(expiryUnix, 0)
if !(ok && token.Valid && claims["type"].(string) == "bearer" && expiry.After(time.Now())) {
app.debug.Printf("Auth denied: Invalid token")
app.authLog(lm.InvalidJWT)
// app.debug.Printf("Expiry: %+v, OK: %t, Valid: %t, ClaimType: %s\n", expiry, ok, token.Valid, claims["type"].(string))
respond(401, "Unauthorized", gc)
ok = false
@ -115,7 +112,7 @@ func (app *appContext) authenticate(gc *gin.Context) {
}
isAdminToken := claims["admin"].(bool)
if !isAdminToken {
app.debug.Printf("Auth denied: Token was not for admin access")
app.authLog(lm.NonAdminToken)
respond(401, "Unauthorized", gc)
return
}
@ -130,14 +127,13 @@ func (app *appContext) authenticate(gc *gin.Context) {
}
}
if !match {
app.debug.Printf("Couldn't find user ID \"%s\"", userID)
app.authLog(fmt.Sprintf(lm.NonAdminUser, userID))
respond(401, "Unauthorized", gc)
return
}
gc.Set("jfId", jfID)
gc.Set("userId", userID)
gc.Set("userMode", false)
app.debug.Println("Auth succeeded")
gc.Next()
}
@ -160,7 +156,7 @@ func (app *appContext) decodeValidateLoginHeader(gc *gin.Context, userpage bool)
password = creds[1]
ok = false
if username == "" || password == "" {
app.logIpDebug(gc, userpage, "Auth denied: blank username/password")
app.logIpDebug(gc, userpage, fmt.Sprintf(lm.FailedAuthRequest, lm.EmptyUserOrPass))
respond(401, "Unauthorized", gc)
return
}
@ -173,16 +169,16 @@ func (app *appContext) validateJellyfinCredentials(username, password string, gc
user, status, err := app.authJf.Authenticate(username, password)
if status != 200 || err != nil {
if status == 401 || status == 400 {
app.logIpInfo(gc, userpage, "Auth denied: Invalid username/password (Jellyfin)")
app.logIpInfo(gc, userpage, fmt.Sprintf(lm.FailedAuthRequest, lm.InvalidUserOrPass))
respond(401, "Unauthorized", gc)
return
}
if status == 403 {
app.logIpInfo(gc, userpage, "Auth denied: Jellyfin account disabled")
app.logIpInfo(gc, userpage, fmt.Sprintf(lm.FailedAuthRequest, lm.UserDisabled))
respond(403, "yourAccountWasDisabled", gc)
return
}
app.err.Printf("Auth failed: Couldn't authenticate with Jellyfin (%d/%s)", status, err)
app.authLog(fmt.Sprintf(lm.FailedAuthJellyfin, app.jf.Server, status, err))
respond(500, "Jellyfin error", gc)
return
}
@ -199,7 +195,7 @@ func (app *appContext) validateJellyfinCredentials(username, password string, gc
// @tags Auth
// @Security getTokenAuth
func (app *appContext) getTokenLogin(gc *gin.Context) {
app.logIpInfo(gc, false, "Token requested (login attempt)")
app.logIpInfo(gc, false, fmt.Sprintf(lm.RequestingToken, lm.TokenLoginAttempt))
username, password, ok := app.decodeValidateLoginHeader(gc, false)
if !ok {
return
@ -209,13 +205,12 @@ func (app *appContext) getTokenLogin(gc *gin.Context) {
for _, user := range app.adminUsers {
if user.Username == username && user.Password == password {
match = true
app.debug.Println("Found existing user")
userID = user.UserID
break
}
}
if !app.jellyfinLogin && !match {
app.logIpInfo(gc, false, "Auth denied: Invalid username/password")
app.logIpInfo(gc, false, fmt.Sprintf(lm.FailedAuthRequest, lm.InvalidUserOrPass))
respond(401, "Unauthorized", gc)
return
}
@ -233,7 +228,7 @@ func (app *appContext) getTokenLogin(gc *gin.Context) {
}
accountsAdmin = accountsAdmin || (adminOnly && user.Policy.IsAdministrator)
if !accountsAdmin {
app.debug.Printf("Auth denied: Users \"%s\" isn't admin", username)
app.authLog(fmt.Sprintf(lm.NonAdminUser, username))
respond(401, "Unauthorized", gc)
return
}
@ -243,12 +238,12 @@ func (app *appContext) getTokenLogin(gc *gin.Context) {
newUser := User{
UserID: userID,
}
app.debug.Printf("Token generated for user \"%s\"", username)
app.debug.Printf(lm.GenerateToken, username)
app.adminUsers = append(app.adminUsers, newUser)
}
token, refresh, err := CreateToken(userID, jfID, true)
if err != nil {
app.err.Printf("getToken failed: Couldn't generate token (%s)", err)
app.err.Printf(lm.FailedGenerateToken, err)
respond(500, "Couldn't generate token", gc)
return
}
@ -261,35 +256,29 @@ func (app *appContext) decodeValidateRefreshCookie(gc *gin.Context, cookieName s
ok = false
cookie, err := gc.Cookie(cookieName)
if err != nil || cookie == "" {
app.debug.Printf("getTokenRefresh denied: Couldn't get token: %s", err)
app.authLog(fmt.Sprintf(lm.FailedGetCookies, cookieName, err))
respond(400, "Couldn't get token", gc)
return
}
for _, token := range app.invalidTokens {
if cookie == token {
app.debug.Println("getTokenRefresh: Invalid token")
respond(401, "Invalid token", gc)
app.authLog(lm.LocallyInvalidatedJWT)
respond(401, lm.InvalidJWT, gc)
return
}
}
token, err := jwt.Parse(cookie, checkToken)
if err != nil {
app.debug.Println("getTokenRefresh: Invalid token")
respond(400, "Invalid token", gc)
app.authLog(lm.FailedParseJWT)
respond(400, lm.InvalidJWT, gc)
return
}
claims, ok = token.Claims.(jwt.MapClaims)
expiryUnix := int64(claims["exp"].(float64))
if err != nil {
app.debug.Printf("getTokenRefresh: Invalid token expiry: %s", err)
respond(401, "Invalid token", gc)
ok = false
return
}
expiry := time.Unix(expiryUnix, 0)
if !(ok && token.Valid && claims["type"].(string) == "refresh" && expiry.After(time.Now())) {
app.debug.Printf("getTokenRefresh: Invalid token: %+v", err)
respond(401, "Invalid token", gc)
app.authLog(lm.InvalidJWT)
respond(401, lm.InvalidJWT, gc)
ok = false
return
}
@ -304,7 +293,7 @@ func (app *appContext) decodeValidateRefreshCookie(gc *gin.Context, cookieName s
// @Router /token/refresh [get]
// @tags Auth
func (app *appContext) getTokenRefresh(gc *gin.Context) {
app.logIpInfo(gc, false, "Token requested (refresh token)")
app.logIpInfo(gc, false, fmt.Sprintf(lm.RequestingToken, lm.TokenRefresh))
claims, ok := app.decodeValidateRefreshCookie(gc, "refresh")
if !ok {
return
@ -313,7 +302,7 @@ func (app *appContext) getTokenRefresh(gc *gin.Context) {
jfID := claims["jfid"].(string)
jwt, refresh, err := CreateToken(userID, jfID, true)
if err != nil {
app.err.Printf("getTokenRefresh failed: Couldn't generate token (%s)", err)
app.err.Printf(lm.FailedGenerateToken, err)
respond(500, "Couldn't generate token", gc)
return
}

View File

@ -7,6 +7,8 @@ import (
"sort"
"strings"
"time"
lm "github.com/hrfee/jfa-go/logmessages"
)
const (
@ -60,12 +62,12 @@ func (app *appContext) getBackups() *BackupList {
path := app.config.Section("backups").Key("path").String()
err := os.MkdirAll(path, 0755)
if err != nil {
app.err.Printf("Failed to create backup directory \"%s\": %v\n", path, err)
app.err.Printf(lm.FailedCreateDir, path, err)
return nil
}
items, err := os.ReadDir(path)
if err != nil {
app.err.Printf("Failed to read backup directory \"%s\": %v\n", path, err)
app.err.Printf(lm.FailedReading, path, err)
return nil
}
backups := &BackupList{}
@ -78,7 +80,7 @@ func (app *appContext) getBackups() *BackupList {
}
t, err := time.Parse(BACKUP_DATEFMT, strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(item.Name(), BACKUP_UPLOAD_PREFIX), BACKUP_PREFIX), BACKUP_SUFFIX))
if err != nil {
app.debug.Printf("Failed to parse backup filename \"%s\": %v\n", item.Name(), err)
app.debug.Printf(lm.FailedParseTime, err)
continue
}
backups.dates[i] = t
@ -101,36 +103,36 @@ func (app *appContext) makeBackup() (fileDetails CreateBackupDTO) {
sort.Sort(backups)
for _, item := range backups.files[:toDelete] {
fullpath := filepath.Join(path, item.Name())
app.debug.Printf("Deleting old backup \"%s\"\n", item.Name())
err := os.Remove(fullpath)
if err != nil {
app.err.Printf("Failed to delete old backup \"%s\": %v\n", fullpath, err)
app.err.Printf(lm.FailedDeleteOldBackup, fullpath, err)
return
}
app.debug.Printf(lm.DeleteOldBackup, fullpath)
}
}
fullpath := filepath.Join(path, fname)
f, err := os.Create(fullpath)
if err != nil {
app.err.Printf("Failed to open backup file \"%s\": %v\n", fullpath, err)
app.err.Printf(lm.FailedOpen, fullpath, err)
return
}
defer f.Close()
_, err = app.storage.db.Badger().Backup(f, 0)
if err != nil {
app.err.Printf("Failed to create backup: %v\n", err)
app.err.Printf(lm.FailedCreateBackup, err)
return
}
fstat, err := f.Stat()
if err != nil {
app.err.Printf("Failed to get info on new backup: %v\n", err)
app.err.Printf(lm.FailedStat, fullpath, err)
return
}
fileDetails.Size = fileSize(fstat.Size())
fileDetails.Name = fname
fileDetails.Path = fullpath
// fmt.Printf("Created backup %+v\n", fileDetails)
app.debug.Printf(lm.CreateBackup, fileDetails)
return
}
@ -139,25 +141,25 @@ func (app *appContext) loadPendingBackup() {
return
}
oldPath := filepath.Join(app.dataPath, "db-"+string(time.Now().Unix())+"-pre-"+filepath.Base(LOADBAK))
app.info.Printf("Moving existing database to \"%s\"\n", oldPath)
err := os.Rename(app.storage.db_path, oldPath)
if err != nil {
app.err.Fatalf("Failed to move existing database: %v\n", err)
app.err.Fatalf(lm.FailedMoveOldDB, oldPath, err)
}
app.info.Printf(lm.MoveOldDB, oldPath)
app.ConnectDB()
defer app.storage.db.Close()
f, err := os.Open(LOADBAK)
if err != nil {
app.err.Fatalf("Failed to open backup file \"%s\": %v\n", LOADBAK, err)
app.err.Fatalf(lm.FailedOpen, LOADBAK, err)
}
err = app.storage.db.Badger().Load(f, 256)
f.Close()
if err != nil {
app.err.Fatalf("Failed to restore backup file \"%s\": %v\n", LOADBAK, err)
app.err.Fatalf(lm.FailedRestoreDB, LOADBAK, err)
}
app.info.Printf("Restored backup \"%s\".", LOADBAK)
app.info.Printf(lm.RestoreDB, LOADBAK)
LOADBAK = ""
}
@ -165,7 +167,6 @@ func newBackupDaemon(app *appContext) *GenericDaemon {
interval := time.Duration(app.config.Section("backups").Key("every_n_minutes").MustInt(1440)) * time.Minute
d := NewGenericDaemon(interval, app,
func(app *appContext) {
app.debug.Println("Backups: Creating backup")
app.makeBackup()
},
)

View File

@ -7,8 +7,10 @@ import (
"path/filepath"
"strconv"
"strings"
"time"
"github.com/hrfee/jfa-go/easyproxy"
lm "github.com/hrfee/jfa-go/logmessages"
"gopkg.in/ini.v1"
)
@ -140,7 +142,7 @@ func (app *appContext) loadConfig() error {
}
}
if allDisabled {
fmt.Println("SETALLTRUE")
app.info.Println(lm.EnableAllPWRMethods)
for _, v := range pwrMethods {
app.config.Section("user_page").Key(v).SetValue("true")
}
@ -175,9 +177,15 @@ func (app *appContext) loadConfig() error {
app.proxyConfig.Password = app.config.Section("advanced").Key("proxy_password").MustString("")
app.proxyTransport, err = easyproxy.NewTransport(app.proxyConfig)
if err != nil {
app.err.Printf("Failed to initialize Proxy: %v\n", err)
app.err.Printf(lm.FailedInitProxy, app.proxyConfig.Addr, err)
// As explained in lm.FailedInitProxy, sleep here might grab the admin's attention,
// Since we don't crash on this failing.
time.Sleep(15 * time.Second)
app.proxyEnabled = false
} else {
app.proxyEnabled = true
app.info.Printf(lm.InitProxy, app.proxyConfig.Addr)
}
app.proxyEnabled = true
}
app.MustSetValue("updates", "enabled", "true")

View File

@ -20,6 +20,7 @@ import (
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/hrfee/jfa-go/easyproxy"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/mediabrowser"
"github.com/itchyny/timefmt-go"
"github.com/mailgun/mailgun-go/v4"
@ -95,7 +96,7 @@ func NewEmailer(app *appContext) *Emailer {
authType := sMail.AuthType(app.config.Section("smtp").Key("auth_type").MustInt(4))
err := emailer.NewSMTP(app.config.Section("smtp").Key("server").String(), app.config.Section("smtp").Key("port").MustInt(465), username, password, sslTLS, app.config.Section("smtp").Key("ssl_cert").MustString(""), app.config.Section("smtp").Key("hello_hostname").String(), app.config.Section("smtp").Key("cert_validation").MustBool(true), authType, proxyConf)
if err != nil {
app.err.Printf("Error while initiating SMTP mailer: %v", err)
app.err.Printf(lm.FailedInitSMTP, err)
}
} else if method == "mailgun" {
emailer.NewMailgun(app.config.Section("mailgun").Key("api_url").String(), app.config.Section("mailgun").Key("api_key").String())
@ -580,7 +581,7 @@ func (emailer *Emailer) resetValues(pwr PasswordReset, app *appContext, noSub bo
// Only used in html email.
template["pin_code"] = pwr.Pin
} else {
app.info.Println("Couldn't generate PWR link: %v", err)
app.info.Println(lm.FailedGeneratePWRLink, err)
template["pin"] = pwr.Pin
}
} else {

View File

@ -1,6 +1,10 @@
package main
import "time"
import (
"time"
lm "github.com/hrfee/jfa-go/logmessages"
)
// https://bbengfort.github.io/snippets/2016/06/26/background-work-goroutines-timer.html THANKS
@ -36,7 +40,7 @@ func NewGenericDaemon(interval time.Duration, app *appContext, jobs ...func(app
func (d *GenericDaemon) Name(name string) { d.name = name }
func (d *GenericDaemon) run() {
d.app.info.Printf("%s started", d.name)
d.app.info.Printf(lm.StartDaemon, d.name)
for {
select {
case <-d.ShutdownChannel:

View File

@ -4,6 +4,7 @@ import (
"time"
"github.com/dgraph-io/badger/v3"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/mediabrowser"
"github.com/timshannon/badgerhold/v4"
)
@ -12,7 +13,7 @@ import (
// meant to be called with other such housekeeping functions, so assumes
// the user cache is fresh.
func (app *appContext) clearEmails() {
app.debug.Println("Housekeeping: removing unused email addresses")
app.debug.Println(lm.HousekeepingEmail)
emails := app.storage.GetEmails()
for _, email := range emails {
_, _, err := app.jf.UserByID(email.JellyfinID, false)
@ -28,7 +29,7 @@ func (app *appContext) clearEmails() {
// clearDiscord does the same as clearEmails, but for Discord Users.
func (app *appContext) clearDiscord() {
app.debug.Println("Housekeeping: removing unused Discord IDs")
app.debug.Println(lm.HousekeepingDiscord)
discordUsers := app.storage.GetDiscord()
for _, discordUser := range discordUsers {
_, _, err := app.jf.UserByID(discordUser.JellyfinID, false)
@ -44,7 +45,7 @@ func (app *appContext) clearDiscord() {
// clearMatrix does the same as clearEmails, but for Matrix Users.
func (app *appContext) clearMatrix() {
app.debug.Println("Housekeeping: removing unused Matrix IDs")
app.debug.Println(lm.HousekeepingMatrix)
matrixUsers := app.storage.GetMatrix()
for _, matrixUser := range matrixUsers {
_, _, err := app.jf.UserByID(matrixUser.JellyfinID, false)
@ -60,7 +61,7 @@ func (app *appContext) clearMatrix() {
// clearTelegram does the same as clearEmails, but for Telegram Users.
func (app *appContext) clearTelegram() {
app.debug.Println("Housekeeping: removing unused Telegram IDs")
app.debug.Println(lm.HousekeepingTelegram)
telegramUsers := app.storage.GetTelegram()
for _, telegramUser := range telegramUsers {
_, _, err := app.jf.UserByID(telegramUser.JellyfinID, false)
@ -75,7 +76,7 @@ func (app *appContext) clearTelegram() {
}
func (app *appContext) clearPWRCaptchas() {
app.debug.Println("Housekeeping: Clearing old PWR Captchas")
app.debug.Println(lm.HousekeepingCaptcha)
captchas := map[string]Captcha{}
for k, capt := range app.pwrCaptchas {
if capt.Generated.Add(CAPTCHA_VALIDITY * time.Second).After(time.Now()) {
@ -86,7 +87,7 @@ func (app *appContext) clearPWRCaptchas() {
}
func (app *appContext) clearActivities() {
app.debug.Println("Housekeeping: Cleaning up Activity log...")
app.debug.Println(lm.HousekeepingActivity)
keepCount := app.config.Section("activity_log").Key("keep_n_records").MustInt(1000)
maxAgeDays := app.config.Section("activity_log").Key("delete_after_days").MustInt(90)
minAge := time.Now().AddDate(0, 0, -maxAgeDays)
@ -103,7 +104,7 @@ func (app *appContext) clearActivities() {
}
}
if err == badger.ErrTxnTooBig {
app.debug.Printf("Activities: Delete txn was too big, doing it manually.")
app.debug.Printf(lm.AcitivityLogTxnTooBig)
list := []Activity{}
if errorSource == 0 {
app.storage.db.Find(&list, badgerhold.Where("Time").Lt(minAge))
@ -119,7 +120,7 @@ func (app *appContext) clearActivities() {
func newHousekeepingDaemon(interval time.Duration, app *appContext) *GenericDaemon {
d := NewGenericDaemon(interval, app,
func(app *appContext) {
app.debug.Println("Housekeeping: Checking for expired invites")
app.debug.Println(lm.HousekeepingInvites)
app.checkInvites()
},
func(app *appContext) { app.clearActivities() },

View File

@ -5,20 +5,21 @@ import (
"time"
"github.com/hrfee/jfa-go/jellyseerr"
lm "github.com/hrfee/jfa-go/logmessages"
)
func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
user, imported, err := app.js.GetOrImportUser(jfID)
if err != nil {
app.debug.Printf("Failed to get or trigger import for Jellyseerr (user \"%s\"): %v", jfID, err)
app.debug.Printf(lm.FailedMustGetJellyseerrUser, jfID, err)
return
}
if imported {
app.debug.Printf("Jellyseerr: Triggered import for Jellyfin user \"%s\" (ID %d)", jfID, user.ID)
app.debug.Printf(lm.ImportJellyseerrUser, jfID, user.ID)
}
notif, err := app.js.GetNotificationPreferencesByID(user.ID)
if err != nil {
app.debug.Printf("Failed to get notification prefs for Jellyseerr (user \"%s\"): %v", jfID, err)
app.debug.Printf(lm.FailedGetJellyseerrNotificationPrefs, jfID, err)
return
}
@ -27,7 +28,7 @@ func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
if ok && email.Addr != "" && user.Email != email.Addr {
err = app.js.ModifyMainUserSettings(jfID, jellyseerr.MainUserSettings{Email: email.Addr})
if err != nil {
app.err.Printf("Failed to set Jellyseerr email address: %v\n", err)
app.err.Printf(lm.FailedSetEmailAddress, lm.Jellyseerr, jfID, err)
} else {
contactMethods[jellyseerr.FieldEmailEnabled] = email.Contact
}
@ -51,7 +52,7 @@ func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
if len(contactMethods) != 0 {
err := app.js.ModifyNotifications(jfID, contactMethods)
if err != nil {
app.err.Printf("Failed to sync contact methods with Jellyseerr: %v", err)
app.err.Printf(lm.FailedSyncContactMethods, lm.Jellyseerr, err)
}
}
}
@ -59,7 +60,7 @@ func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
func (app *appContext) SynchronizeJellyseerrUsers() {
users, status, err := app.jf.GetUsers(false)
if err != nil || status != 200 {
app.err.Printf("Failed to get users (%d): %s", status, err)
app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
return
}
// I'm sure Jellyseerr can handle it,

View File

@ -1,5 +1,12 @@
package logmessages
/* Log strings for (almost) all the program.
* Helps avoid writing redundant, slightly different
* strings constantly.
* Also would help if I were to ever set up translation
* for logs. Mostly split by file, but obviously there's
* re-use, and occasionally related stuff is grouped.
*/
const (
Jellyseerr = "Jellyseerr"
Jellyfin = "Jellyfin"
@ -12,16 +19,20 @@ const (
// main.go
FailedLogging = "Failed to start log wrapper: %v\n"
NoConfig = "Couldn't find default config file"
Write = "Wrote to \"%s\""
FailedWriting = "Failed to write to \"%s\": %v"
FailedReading = "Failed to read from \"%s\": %v"
FailedOpen = "Failed to open \"%s\": %v"
NoConfig = "Couldn't find default config file"
Write = "Wrote to \"%s\""
FailedWriting = "Failed to write to \"%s\": %v"
FailedCreateDir = "Failed to create directory \"%s\": %v"
FailedReading = "Failed to read from \"%s\": %v"
FailedOpen = "Failed to open \"%s\": %v"
FailedStat = "Failed to stat \"%s\": %v"
PathNotFound = "Path \"%s\" not found"
CopyConfig = "Copied default configuration to \"%s\""
FailedCopyConfig = "Failed to copy default configuration to \"%s\": %v"
LoadConfig = "Loaded config file \"%s\""
FailedLoadConfig = "Failed to load config file \"%s\": %v"
ModifyConfig = "Config saved to \"%s\""
SocketPath = "Socket Path: \"%s\""
FailedSocketConnect = "Couldn't establish socket connection at \"%s\": %v"
@ -65,10 +76,12 @@ const (
Serving = "Loaded @ \"%s\""
QuitReceived = "Restart/Quit signal received, please be patient."
Quitting = "Shutting down..."
Quit = "Server shut down."
FailedQuit = "Server shutdown failed: %v"
QuitReceived = "Restart/Quit signal received, please be patient."
Quitting = "Shutting down..."
Restarting = "Restarting..."
FailedHardRestartWindows = "hard restarts not available on windows"
Quit = "Server shut down."
FailedQuit = "Server shutdown failed: %v"
// api-activities.go
FailedDBReadActivities = "Failed to read activities from DB: %v"
@ -79,11 +92,12 @@ const (
FailedGetUpload = "Failed to retrieve file from form data: %v"
// api-invites.go
DeleteOldInvite = "Deleting old invite \"%s\""
DeleteInvite = "Deleting invite \"%s\""
FailedDeleteInvite = "Failed to delete invite \"%s\": %v"
GenerateInvite = "Generating new invite"
InvalidInviteCode = "Invalid invite code \"%s\""
DeleteOldInvite = "Deleting old invite \"%s\""
DeleteInvite = "Deleting invite \"%s\""
FailedDeleteInvite = "Failed to delete invite \"%s\": %v"
GenerateInvite = "Generating new invite"
FailedGenerateInvite = "Failed to generate new invite: %v"
InvalidInviteCode = "Invalid invite code \"%s\""
FailedSendToTooltipNoUser = "Failed: \"%s\" not found"
FailedSendToTooltipMultiUser = "Failed: \"%s\" linked to multiple users"
@ -94,22 +108,25 @@ const (
SetAdminNotify = "Set \"%s\" to %t for admin address \"%s\""
// api-jellyseerr.go
// *jellyseerr*.go
FailedGetUsers = "Failed to get user(s) from %s: %v"
// FIXME: Once done, look back at uses of FailedGetUsers for places where this would make more sense.
FailedGetUser = "Failed to get user \"%s\" from %s: %v"
FailedGetJellyseerrNotificationPrefs = "Failed to get user's notification prefs from " + Jellyseerr + ": %v"
FailedGetJellyseerrNotificationPrefs = "Failed to get user \"%s\"'s notification prefs from " + Jellyseerr + ": %v"
FailedSyncContactMethods = "Failed to sync contact methods with %s: %v"
ImportJellyseerrUser = "Triggered import for " + Jellyseerr + " user \"%s\" (New ID: %d)"
FailedMustGetJellyseerrUser = "Failed to get or trigger import for " + Jellyseerr + " user \"%s\": %v"
// api-messages.go
FailedGetCustomMessage = "Failed to get custom message \"%s\""
SetContactPrefForService = "Set contact preference for %s (\"%s\"): %t"
// Matrix
InvalidPIN = "Invalid PIN \"%s\""
UnauthorizedPIN = "Unauthorized PIN \"%s\""
FailedCreateRoom = "Failed to create room: %v"
FailedGenerateToken = "Failed to generate token: %v"
InvalidPIN = "Invalid PIN \"%s\""
ExpiredPIN = "Expired PIN \"%s\""
InvalidPassword = "Invalid Password"
UnauthorizedPIN = "Unauthorized PIN \"%s\""
FailedCreateRoom = "Failed to create room: %v"
// api-profiles.go
SetDefaultProfile = "Setting default profile to \"%s\""
@ -123,6 +140,7 @@ const (
FailedGetJellyfinDisplayPrefs = "Failed to get DisplayPreferences for user \"%s\" from " + Jellyfin + ": %v"
ProfileNoHomescreen = "No homescreen template in profile \"%s\""
Profile = "profile"
Lang = "language"
User = "user"
ApplyingTemplatesFrom = "Applying templates from %s: \"%s\" to %d users"
DelayingRequests = "Delay will be added between requests (count = %d)"
@ -153,7 +171,7 @@ const (
FailedSetEmailAddress = "Failed to set email address for %s user \"%s\": %v"
AdditionalOmbiErrors = "Additional errors from " + Ombi + ": %v"
AdditionalErrors = "Additional errors from %s: %v"
IncorrectCaptcha = "captcha incorrect"
@ -162,14 +180,148 @@ const (
UserEmailAdjusted = "Email for user \"%s\" adjusted"
UserAdminAdjusted = "Admin state for user \"%s\" set to %t"
UserLabelAdjusted = "Label for user \"%s\" set to \"%s\""
// api.go
ApplyUpdate = "Applied update"
FailedApplyUpdate = "Failed to apply update: %v"
UpdateManual = "update is manual"
// backups.go
DeleteOldBackup = "Deleted old backup \"%s\""
FailedDeleteOldBackup = "Failed to delete old backup \"%s\": %v"
CreateBackup = "Created database backup \"%+v\""
FailedCreateBackup = "Faled to create database backup: %v"
MoveOldDB = "Moved existing database to \"%s\""
FailedMoveOldDB = "Failed to move existing database to \"%s\": %v"
RestoreDB = "Restored database from \"%s\""
FailedRestoreDB = "Failed to resotre database from \"%s\": %v"
// config.go
EnableAllPWRMethods = "No PWR method preferences set in [user_page], all will be enabled"
InitProxy = "Initialized proxy @ \"%s\""
FailedInitProxy = "Failed to initialize proxy @ \"%s\": %v\nStartup will pause for a bit to grab your attention."
// discord.go
StartDaemon = "Started %s daemon"
FailedStartDaemon = "Failed to start %s daemon: %v"
FailedGetDiscordGuildMembers = "Failed to get " + Discord + " guild members: %v"
FailedGetDiscordGuild = "Failed to get " + Discord + " guild: %v"
FailedGetDiscordRoles = "Failed to get " + Discord + " roles: %v"
FailedCreateDiscordInviteChannel = "Failed to create " + Discord + " invite channel: %v"
InviteChannelEmpty = "no invite channel set in settings"
FailedGetDiscordChannels = "Failed to get " + Discord + " channel(s): %v"
FailedGetDiscordChannel = "Failed to get " + Discord + " channel \"%s\": %v"
MonitorAllDiscordChannels = "Will monitor all " + Discord + " channels"
FailedCreateDiscordDMChannel = "Failed to create " + Discord + " private DM channel with \"%s\": %v"
NotFound = "not found"
RegisterDiscordChoice = "Registered " + Discord + " %s choice \"%s\""
FailedRegisterDiscordChoices = "Failed to register " + Discord + " %s choices: %v"
FailedDeregDiscordChoice = "Failed to deregister " + Discord + " %s choice \"%s\": %v"
RegisterDiscordCommand = "Registered " + Discord + " command \"%s\""
FailedRegisterDiscordCommand = "Failed to register " + Discord + " command \"%s\": %v"
FailedGetDiscordCommands = "Failed to get " + Discord + " commands: %v"
FailedDeregDiscordCommand = "Failed to deregister " + Discord + " command \"%s\": %v"
FailedReply = "Failed to reply to %s message from \"%s\": %v"
FailedMessage = "Failed to send %s message to \"%s\": %v"
IgnoreOutOfChannelMessage = "Ignoring out-of-channel %s message"
FailedGenerateDiscordInvite = "Failed to generate " + Discord + " invite: %v"
// email.go
FailedInitSMTP = "Failed to initialize SMTP mailer: %v"
FailedGeneratePWRLink = "Failed to generate PWR link: %v"
// housekeeping-d.go
hk = "Housekeeping: "
hkcu = hk + "cleaning up "
HousekeepingEmail = hkcu + Email + " addresses"
HousekeepingDiscord = hkcu + Discord + " IDs"
HousekeepingTelegram = hkcu + Telegram + " IDs"
HousekeepingMatrix = hkcu + Matrix + " IDs"
HousekeepingCaptcha = hkcu + "PWR Captchas"
HousekeepingActivity = hkcu + "Activity log"
HousekeepingInvites = hkcu + "Invites"
ActivityLogTxnTooBig = hk + "Activity log delete transaction was too big, going one-by-one"
// matrix*.go
FailedSyncMatrix = "Failed to sync " + Matrix + " daemon: %v"
FailedCreateMatrixRoom = "Failed to create " + Matrix + " room with user \"%s\": %v"
MatrixOLMLog = "Matrix/OLM: %v"
MatrixOLMTraceLog = "Matrix/OLM [TRACE]:"
FailedDecryptMatrixMessage = "Failed to decrypt " + Matrix + " E2EE'd message: %v"
FailedEnableMatrixEncryption = "Failed to enable encryption in " + Matrix + " room \"%s\": %v"
// NOTE: "migrations.go" is the one file where log messages are not part of logmessages/logmessages.go.
// pwreset.go
PWRExpired = "PWR for user \"%s\" already expired @ %s, check system time!"
// router.go
UseDefaultHTML = "Using default HTML \"%s\""
UseCustomHTML = "Using custom HTML \"%s\""
FailedLoadTemplates = "Failed to load %s templates: %v"
Internal = "internal"
External = "external"
RegisterPprof = "Registered pprof"
SwaggerWarning = "Warning: Swagger should not be used on a public instance."
// storage.go
ConnectDB = "Connected to DB \"%s\""
FailedConnectDB = "Failed to open/connect to database \"%s\": %v"
// updater.go
NoUpdate = "No new updates available"
FoundUpdate = "Found update"
FailedGetUpdateTag = "Failed to get latest tag: %v"
FailedGetUpdate = "Failed to get update: %v"
UpdateTagDetails = "Update/Tag details: %+v"
// user-auth.go
UserPage = "userpage"
UserPageRequiresJellyfinAuth = "Jellyfin login must be enabled for user page access."
// user-d.go
CheckUserExpiries = "Checking for user expiry"
DeleteExpiryForOldUser = "Deleting expiry for old user \"%s\""
DeleteExpiredUser = "Deleting expired user \"%s\""
DisableExpiredUser = "Disabling expired user \"%s\""
FailedDeleteOrDisableExpiredUser = "Failed to delete/disable expired user \"%s\": %v"
// views.go
FailedServerPush = "Failed to use HTTP/2 Server Push: %v"
IgnoreBotPWR = "Ignore PWR magic link visit from bot"
ReCAPTCHA = "ReCAPTCHA"
FailedGenerateCaptcha = "Failed to generate captcha: %v"
CaptchaNotFound = "Captcha \"%s\" not found in invite \"%s\""
FailedVerifyReCAPTCHA = "Failed to verify reCAPTCHA: %v"
InvalidHostname = "invalid hostname (wanted \"%s\", got \"%s\")"
)
const (
FailedGetCookies = "Failed to get cookie(s) \"%s\": %v"
FailedParseJWT = "Failed to parse JWT: %v"
FailedCastJWT = "JWT claims unreadable"
InvalidJWT = "JWT was invalidated, of incorrect type or has expired"
FailedSignJWT = "Failed to sign JWT: %v"
FailedGetCookies = "Failed to get cookie(s) \"%s\": %v"
FailedParseJWT = "Failed to parse JWT: %v"
FailedCastJWT = "JWT claims unreadable"
InvalidJWT = "JWT was invalidated, of incorrect type or has expired"
LocallyInvalidatedJWT = "JWT is listed as invalidated"
FailedSignJWT = "Failed to sign JWT: %v"
RequestingToken = "Token requested (%s)"
TokenLoginAttempt = "login attempt"
TokenRefresh = "refresh token"
UserTokenLoginAttempt = UserPage + " " + TokenLoginAttempt
UserTokenRefresh = UserPage + " " + TokenRefresh
GenerateToken = "Token generated for user \"%s\""
FailedGenerateToken = "Failed to generate token: %v"
FailedAuthRequest = "Failed to authorize request: %v"
InvalidAuthHeader = "invalid auth header"
NonAdminToken = "token not for admin use"
NonAdminUser = "user \"%s\" not admin"
InvalidUserOrPass = "invalid user/pass"
EmptyUserOrPass = "invalid user/pass"
UserDisabled = "user is disabled"
)
const (
@ -209,6 +361,10 @@ const (
FailedSendExpiryAdjustmentMessage = "Failed to send expiry adjustment message for \"%s\" to \"%s\": %v"
SentExpiryAdjustmentMessage = "Sent expiry adjustment message for \"%s\" to \"%s\""
FailedConstructExpiryMessage = "Failed to construct expiry message for \"%s\": %v"
FailedSendExpiryMessage = "Failed to send expiry message for \"%s\" to \"%s\": %v"
SentExpiryMessage = "Sent expiry message for \"%s\" to \"%s\""
FailedConstructAnnouncementMessage = "Failed to construct announcement message for \"%s\": %v"
FailedSendAnnouncementMessage = "Failed to send announcement message for \"%s\" to \"%s\": %v"
SentAnnouncementMessage = "Sent announcement message for \"%s\" to \"%s\""

View File

@ -6,6 +6,7 @@ import (
"time"
"github.com/gomarkdown/markdown"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/timshannon/badgerhold/v4"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
@ -118,13 +119,13 @@ func (d *MatrixDaemon) generateAccessToken(homeserver, username, password string
func (d *MatrixDaemon) run() {
startTime := d.start
d.app.info.Println("Starting Matrix bot daemon")
d.app.info.Println(lm.StartDaemon, lm.Matrix)
syncer := d.bot.Syncer.(*mautrix.DefaultSyncer)
HandleSyncerCrypto(startTime, d, syncer)
syncer.OnEventType(event.EventMessage, d.handleMessage)
if err := d.bot.Sync(); err != nil {
d.app.err.Printf("Matrix sync failed: %v", err)
d.app.err.Printf(lm.FailedSyncMatrix, err)
}
}
@ -170,7 +171,7 @@ func (d *MatrixDaemon) commandLang(evt *event.Event, code, lang string) {
list,
)
if err != nil {
d.app.err.Printf("Matrix: Failed to send message to \"%s\": %v", evt.Sender, err)
d.app.err.Printf(lm.FailedReply, lm.Matrix, evt.Sender, err)
}
return
}
@ -203,7 +204,7 @@ func (d *MatrixDaemon) CreateRoom(userID string) (roomID id.RoomID, encrypted bo
func (d *MatrixDaemon) SendStart(userID string) (ok bool) {
roomID, encrypted, err := d.CreateRoom(userID)
if err != nil {
d.app.err.Printf("Failed to create room for user \"%s\": %v", userID, err)
d.app.err.Printf(lm.FailedCreateMatrixRoom, userID, err)
return
}
lang := "en-us"
@ -226,7 +227,7 @@ func (d *MatrixDaemon) SendStart(userID string) (ok bool) {
roomID,
)
if err != nil {
d.app.err.Printf("Matrix: Failed to send welcome message to \"%s\": %v", userID, err)
d.app.err.Printf(lm.FailedMessage, lm.Matrix, userID, err)
return
}
ok = true

View File

@ -1,10 +1,13 @@
//go:build e2ee
// +build e2ee
package main
import (
"fmt"
"strings"
lm "github.com/hrfee/jfa-go/logmessages"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto"
"maunium.net/go/mautrix/event"
@ -65,22 +68,22 @@ type olmLogger struct {
}
func (o olmLogger) Error(message string, args ...interface{}) {
o.app.err.Printf("OLM: "+message+"\n", args)
o.app.err.Printf(lm.MatrixOlmLog, fmt.Sprintf(message, args))
}
func (o olmLogger) Warn(message string, args ...interface{}) {
o.app.info.Printf("OLM: "+message+"\n", args)
o.app.info.Printf(lm.MatrixOlmLog, fmt.Sprintf(message, args))
}
func (o olmLogger) Debug(message string, args ...interface{}) {
o.app.debug.Printf("OLM: "+message+"\n", args)
o.app.debug.Printf(lm.MatrixOlmLog, fmt.Sprintf(message, args))
}
func (o olmLogger) Trace(message string, args ...interface{}) {
if strings.HasPrefix(message, "Got membership state event") {
return
}
o.app.debug.Printf("OLM [TRACE]: "+message+"\n", args)
o.app.debug.Printf(lm.MatrixOlmTracelog, fmt.Sprintf(message, args))
}
func InitMatrixCrypto(d *MatrixDaemon) (err error) {
@ -155,7 +158,7 @@ func HandleSyncerCrypto(startTime int64, d *MatrixDaemon, syncer *mautrix.Defaul
// return
// }
if err != nil {
d.app.err.Printf("Failed to decrypt Matrix message: %v", err)
d.app.err.Printf(lm.FailedDecryptMatrixMessage, err)
return
}
d.handleMessage(source, decrypted)
@ -180,7 +183,7 @@ func EncryptRoom(d *MatrixDaemon, room *mautrix.RespCreateRoom, userID id.UserID
if err == nil {
encrypted = true
} else {
d.app.debug.Printf("Matrix: Failed to enable encryption in room: %v", err)
d.app.debug.Printf(lm.FailedEnableMatrixEncryption, room.RoomID, err)
return
}
d.isEncrypted[room.RoomID] = encrypted

View File

@ -9,6 +9,8 @@ import (
"gopkg.in/ini.v1"
)
// NOTE: This is the one file where log messages are not part of logmessages/logmessages.go
func runMigrations(app *appContext) {
migrateProfiles(app)
migrateBootstrap(app)

View File

@ -8,6 +8,7 @@ import (
"time"
"github.com/fsnotify/fsnotify"
lm "github.com/hrfee/jfa-go/logmessages"
)
// GenInternalReset generates a local password reset PIN, for use with the PWR option on the Admin page.
@ -39,16 +40,16 @@ func (app *appContext) GenResetLink(pin string) (string, error) {
}
func (app *appContext) StartPWR() {
app.info.Println("Starting password reset daemon")
app.info.Println(lm.StartDaemon, "PWR")
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)
app.err.Printf(lm.FailedStartDaemon, "PWR", fmt.Sprintf(lm.PathNotFound, path))
return
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
app.err.Printf("Couldn't initialise password reset daemon")
app.err.Printf(lm.FailedStartDaemon, "PWR", err)
return
}
defer watcher.Close()
@ -56,7 +57,7 @@ func (app *appContext) StartPWR() {
go pwrMonitor(app, watcher)
err = watcher.Add(path)
if err != nil {
app.err.Printf("Failed to start password reset daemon: %s", err)
app.err.Printf(lm.FailedStartDaemon, "PWR", err)
}
waitForRestart()
@ -84,43 +85,36 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
var pwr PasswordReset
data, err := os.ReadFile(event.Name)
if err != nil {
app.debug.Printf("PWR: Failed to read file: %v", err)
app.debug.Printf(lm.FailedReading, event.Name, err)
return
}
err = json.Unmarshal(data, &pwr)
if len(pwr.Pin) == 0 || err != nil {
app.debug.Printf("PWR: Failed to read PIN: %v", err)
app.debug.Printf(lm.FailedReading, event.Name, 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)
if !(status == 200 || status == 204) || err != nil || user.ID == "" {
app.err.Printf(lm.FailedGetUser, pwr.Username, lm.Jellyfin, err)
return
}
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)
app.err.Printf(lm.FailedConstructPWRMessage, 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)
app.err.Printf(lm.FailedSendPWRMessage, pwr.Username, name, err)
} else {
app.info.Printf("Sent password reset message to \"%s\"", name)
app.err.Printf(lm.SentPWRMessage, pwr.Username, name)
}
}
} else {
app.err.Printf("Password reset for user \"%s\" has already expired (%s). Check your time settings.", pwr.Username, pwr.Expiry)
app.err.Printf(lm.PWRExpired, pwr.Username, pwr.Expiry)
}
}
@ -128,7 +122,7 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
if !ok {
return
}
app.err.Printf("Password reset daemon: %s", err)
app.err.Printf(lm.FailedStartDaemon, "PWR", err)
}
}
}

View File

@ -1,7 +1,11 @@
package main
import "fmt"
import (
"fmt"
lm "github.com/hrfee/jfa-go/logmessages"
)
func (app *appContext) HardRestart() error {
return fmt.Errorf("hard restarts not available on windows")
return fmt.Errorf(lm.FailedHardRestartWindows)
}

View File

@ -11,6 +11,7 @@ import (
"github.com/gin-contrib/pprof"
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
lm "github.com/hrfee/jfa-go/logmessages"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
@ -21,17 +22,17 @@ func (app *appContext) loadHTML(router *gin.Engine) {
templatePath := "html"
htmlFiles, err := fs.ReadDir(localFS, templatePath)
if err != nil {
app.err.Fatalf("Couldn't access template directory: \"%s\"", templatePath)
app.err.Fatalf(lm.FailedReading, templatePath, err)
return
}
loadInternal := []string{}
loadExternal := []string{}
for _, f := range htmlFiles {
if _, err := os.Stat(filepath.Join(customPath, f.Name())); os.IsNotExist(err) {
app.debug.Printf("Using default \"%s\"", f.Name())
app.debug.Printf(lm.UseDefaultHTML, f.Name())
loadInternal = append(loadInternal, FSJoin(templatePath, f.Name()))
} else {
app.info.Printf("Using custom \"%s\"", f.Name())
app.info.Printf(lm.UseCustomHTML, f.Name())
loadExternal = append(loadExternal, filepath.Join(filepath.Join(customPath, f.Name())))
}
}
@ -39,13 +40,13 @@ func (app *appContext) loadHTML(router *gin.Engine) {
if len(loadInternal) != 0 {
tmpl, err = template.ParseFS(localFS, loadInternal...)
if err != nil {
app.err.Fatalf("Failed to load templates: %v", err)
app.err.Fatalf(lm.FailedLoadTemplates, lm.Internal, err)
}
}
if len(loadExternal) != 0 {
tmpl, err = tmpl.ParseFiles(loadExternal...)
if err != nil {
app.err.Fatalf("Failed to load external templates: %v", err)
app.err.Fatalf(lm.FailedLoadTemplates, lm.External, err)
}
}
router.SetHTMLTemplate(tmpl)
@ -96,7 +97,7 @@ func (app *appContext) loadRouter(address string, debug bool) *gin.Engine {
router.Use(static.Serve("/", app.webFS))
router.NoRoute(app.NoRouteHandler)
if *PPROF {
app.debug.Println("Loading pprof")
app.debug.Println(lm.RegisterPprof)
pprof.Register(router)
}
SRV = &http.Server{
@ -165,7 +166,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
}
}
if *SWAGGER {
app.info.Print(warning("\n\nWARNING: Swagger should not be used on a public instance.\n\n"))
app.info.Print(warning(lm.SwaggerWarning))
for _, p := range routePrefixes {
router.GET(p+"/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
}

View File

@ -8,6 +8,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/easyproxy"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/mediabrowser"
)
@ -104,7 +105,7 @@ func (app *appContext) TestJF(gc *gin.Context) {
case 404:
msg = "error404"
}
app.info.Printf("Auth failed with code %d (%s)", status, err)
app.err.Printf(lm.FailedAuthJellyfin, req.Server, status, err)
if msg != "" {
respond(status, msg, gc)
} else {

View File

@ -13,6 +13,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/hrfee/jfa-go/jellyseerr"
"github.com/hrfee/jfa-go/logger"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/mediabrowser"
"github.com/timshannon/badgerhold/v4"
"gopkg.in/ini.v1"
@ -175,10 +176,10 @@ func (app *appContext) ConnectDB() {
opts.ValueDir = app.storage.db_path
db, err := badgerhold.Open(opts)
if err != nil {
app.err.Fatalf("Failed to open db \"%s\": %v", app.storage.db_path, err)
app.err.Fatalf(lm.FailedConnectDB, app.storage.db_path, err)
}
app.storage.db = db
app.info.Printf("Connected to DB \"%s\"", app.storage.db_path)
app.info.Printf(lm.ConnectDB, app.storage.db_path)
}
// GetEmails returns a copy of the store.

View File

@ -7,6 +7,7 @@ import (
"time"
tg "github.com/go-telegram-bot-api/telegram-bot-api"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/timshannon/badgerhold/v4"
)
@ -96,12 +97,12 @@ func (t *TelegramDaemon) NewAssignedAuthToken(id string) string {
}
func (t *TelegramDaemon) run() {
t.app.info.Println("Starting Telegram bot daemon")
t.app.info.Println(lm.StartDaemon, lm.Telegram)
u := tg.NewUpdate(0)
u.Timeout = 60
updates, err := t.bot.GetUpdatesChan(u)
if err != nil {
t.app.err.Printf("Failed to start Telegram daemon: %v", err)
t.app.err.Printf(lm.FailedStartDaemon, lm.Telegram, err)
telegramEnabled = false
return
}
@ -199,7 +200,7 @@ func (t *TelegramDaemon) commandStart(upd *tg.Update, sects []string, lang strin
content += t.app.storage.lang.Telegram[lang].Strings.template("languageMessage", tmpl{"command": "/lang"})
err := t.Reply(upd, content)
if err != nil {
t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err)
t.app.err.Printf(lm.FailedReply, lm.Telegram, upd.Message.From.UserName, err)
}
}
@ -211,7 +212,7 @@ func (t *TelegramDaemon) commandLang(upd *tg.Update, sects []string, lang string
}
err := t.Reply(upd, list)
if err != nil {
t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err)
t.app.err.Printf(lm.FailedReply, lm.Telegram, upd.Message.From.UserName, err)
}
return
}
@ -232,14 +233,14 @@ func (t *TelegramDaemon) commandPIN(upd *tg.Update, sects []string, lang string)
if !ok || time.Now().After(token.Expiry) {
err := t.QuoteReply(upd, t.app.storage.lang.Telegram[lang].Strings.get("invalidPIN"))
if err != nil {
t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err)
t.app.err.Printf(lm.FailedReply, lm.Telegram, upd.Message.From.UserName, err)
}
delete(t.tokens, upd.Message.Text)
return
}
err := t.QuoteReply(upd, t.app.storage.lang.Telegram[lang].Strings.get("pinSuccess"))
if err != nil {
t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err)
t.app.err.Printf(lm.FailedReply, lm.Telegram, upd.Message.From.UserName, err)
}
t.verifiedTokens[upd.Message.Text] = TelegramVerifiedToken{
ChatID: upd.Message.Chat.ID,

View File

@ -16,6 +16,8 @@ import (
"strings"
"time"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/jfa-go/common"
)
@ -560,15 +562,16 @@ func (app *appContext) checkForUpdates() {
if err != nil && strings.Contains(err.Error(), "strconv.ParseInt") {
app.err.Println("No new updates available.")
} else if status != -1 { // -1 means updates disabled, we don't need to log it.
app.err.Printf("Failed to get latest tag (%d): %v", status, err)
app.err.Printf(lm.FailedGetUpdateTag, err)
}
return
}
if tag != app.tag && tag.IsNew() {
app.info.Println("Update found")
app.info.Println(lm.FoundUpdate)
app.debug.Printf(lm.UpdateTagDetails, tag)
update, status, err := app.updater.GetUpdate(tag)
if status != 200 || err != nil {
app.err.Printf("Failed to get update (%d): %v", status, err)
app.err.Printf(lm.FailedGetUpdate, err)
return
}
app.tag = tag

View File

@ -1,9 +1,11 @@
package main
import (
"fmt"
"strings"
"github.com/gin-gonic/gin"
lm "github.com/hrfee/jfa-go/logmessages"
)
func (app *appContext) userAuth() gin.HandlerFunc {
@ -13,7 +15,7 @@ func (app *appContext) userAuth() gin.HandlerFunc {
func (app *appContext) userAuthenticate(gc *gin.Context) {
jellyfinLogin := app.config.Section("ui").Key("jellyfin_login").MustBool(true)
if !jellyfinLogin {
app.err.Println("Enable Jellyfin Login to use the User Page feature.")
app.err.Printf(lm.FailedAuthRequest, lm.UserPageRequiresJellyfinAuth)
respond(500, "Contact Admin", gc)
return
}
@ -27,7 +29,6 @@ func (app *appContext) userAuthenticate(gc *gin.Context) {
gc.Set("jfId", jfID)
gc.Set("userMode", true)
app.debug.Println("Auth succeeded")
gc.Next()
}
@ -41,11 +42,11 @@ func (app *appContext) userAuthenticate(gc *gin.Context) {
// @Security getUserTokenAuth
func (app *appContext) getUserTokenLogin(gc *gin.Context) {
if !app.config.Section("ui").Key("jellyfin_login").MustBool(true) {
app.err.Println("Enable Jellyfin Login to use the User Page feature.")
app.err.Printf(lm.FailedAuthRequest, lm.UserPageRequiresJellyfinAuth)
respond(500, "Contact Admin", gc)
return
}
app.logIpInfo(gc, true, "UserToken requested (login attempt)")
app.logIpInfo(gc, true, fmt.Sprintf(lm.RequestingToken, lm.UserTokenLoginAttempt))
username, password, ok := app.decodeValidateLoginHeader(gc, true)
if !ok {
return
@ -58,12 +59,11 @@ func (app *appContext) getUserTokenLogin(gc *gin.Context) {
token, refresh, err := CreateToken(user.ID, user.ID, false)
if err != nil {
app.err.Printf("getUserToken failed: Couldn't generate user token (%s)", err)
app.err.Printf(lm.FailedGenerateToken, err)
respond(500, "Couldn't generate user token", gc)
return
}
app.debug.Printf("Token generated for non-admin user \"%s\"", username)
uri := "/my"
if strings.HasPrefix(gc.Request.RequestURI, app.URLBase) {
uri = "/accounts/my"
@ -81,12 +81,12 @@ func (app *appContext) getUserTokenLogin(gc *gin.Context) {
func (app *appContext) getUserTokenRefresh(gc *gin.Context) {
jellyfinLogin := app.config.Section("ui").Key("jellyfin_login").MustBool(true)
if !jellyfinLogin {
app.err.Println("Enable Jellyfin Login to use the User Page feature.")
app.err.Printf(lm.FailedAuthRequest, lm.UserPageRequiresJellyfinAuth)
respond(500, "Contact Admin", gc)
return
}
app.logIpInfo(gc, true, "UserToken request (refresh token)")
app.logIpInfo(gc, true, fmt.Sprintf(lm.RequestingToken, lm.UserTokenRefresh))
claims, ok := app.decodeValidateRefreshCookie(gc, "user-refresh")
if !ok {
return
@ -96,7 +96,7 @@ func (app *appContext) getUserTokenRefresh(gc *gin.Context) {
jwt, refresh, err := CreateToken(jfID, jfID, false)
if err != nil {
app.err.Printf("getUserToken failed: Couldn't generate user token (%s)", err)
app.err.Printf(lm.FailedGenerateToken, err)
respond(500, "Couldn't generate user token", gc)
return
}

View File

@ -3,6 +3,7 @@ package main
import (
"time"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/mediabrowser"
"github.com/lithammer/shortuuid/v3"
)
@ -21,17 +22,17 @@ func (app *appContext) checkUsers() {
if len(app.storage.GetUserExpiries()) == 0 {
return
}
app.info.Println("Daemon: Checking for user expiry")
app.info.Println(lm.CheckUserExpiries)
users, status, err := app.jf.GetUsers(false)
if err != nil || status != 200 {
app.err.Printf("Failed to get users (%d): %s", status, err)
app.err.Printf(lm.FailedGetUsers, lm.Jellyfin, err)
return
}
mode := "disable"
term := "Disabling"
phrase := lm.DisableExpiredUser
if app.config.Section("user_expiry").Key("behaviour").MustString("disable_user") == "delete_user" {
mode = "delete"
term = "Deleting"
phrase = lm.DeleteExpiredUser
}
contact := false
if messagesEnabled && app.config.Section("user_expiry").Key("send_email").MustBool(true) {
@ -45,7 +46,7 @@ func (app *appContext) checkUsers() {
for _, expiry := range app.storage.GetUserExpiries() {
id := expiry.JellyfinID
if _, ok := userExists[id]; !ok {
app.info.Printf("Deleting expiry for non-existent user \"%s\"", id)
app.info.Printf(lm.DeleteExpiryForOldUser, id)
app.storage.DeleteUserExpiryKey(expiry.JellyfinID)
} else if time.Now().After(expiry.Expiry) {
found := false
@ -58,11 +59,10 @@ func (app *appContext) checkUsers() {
}
}
if !found {
app.info.Printf("Expired user already deleted, ignoring.")
app.storage.DeleteUserExpiryKey(expiry.JellyfinID)
continue
}
app.info.Printf("%s expired user \"%s\"", term, user.Name)
app.info.Printf(phrase, user.Name)
// Record activity
activity := Activity{
@ -83,7 +83,7 @@ func (app *appContext) checkUsers() {
activity.Type = ActivityDisabled
}
if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to %s \"%s\" (%d): %s", mode, user.Name, status, err)
app.err.Printf(lm.FailedDeleteOrDisableExpiredUser, user.ID, err)
continue
}
@ -98,11 +98,11 @@ func (app *appContext) checkUsers() {
name := app.getAddressOrName(user.ID)
msg, err := app.email.constructUserExpired(app, false)
if err != nil {
app.err.Printf("Failed to construct expiry message for \"%s\": %s", user.Name, err)
app.err.Printf(lm.FailedConstructExpiryMessage, user.ID, err)
} else if err := app.sendByID(msg, user.ID); err != nil {
app.err.Printf("Failed to send expiry message to \"%s\": %s", name, err)
app.err.Printf(lm.FailedConstructExpiryMessage, user.ID, name, err)
} else {
app.info.Printf("Sent expiry notification to \"%s\"", name)
app.err.Printf(lm.SentExpiryMessage, user.ID, name)
}
}
}

View File

@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"html/template"
"io"
"io/fs"
@ -16,6 +17,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
"github.com/gomarkdown/markdown"
lm "github.com/hrfee/jfa-go/logmessages"
"github.com/hrfee/mediabrowser"
"github.com/lithammer/shortuuid/v3"
"github.com/steambap/captcha"
@ -66,10 +68,9 @@ func (app *appContext) pushResources(gc *gin.Context, page Page) {
toPush = []string{}
}
if pusher := gc.Writer.Pusher(); pusher != nil {
app.debug.Println("Using HTTP2 Server push")
for _, f := range toPush {
if err := pusher.Push(app.URLBase+f, nil); err != nil {
app.debug.Printf("Failed HTTP2 ServerPush of \"%s\": %+v", f, err)
app.debug.Printf(lm.FailedServerPush, err)
}
}
}
@ -139,13 +140,13 @@ func (app *appContext) AdminPage(gc *gin.Context) {
var license string
l, err := fs.ReadFile(localFS, "LICENSE")
if err != nil {
app.debug.Printf("Failed to load LICENSE: %s", err)
app.debug.Printf(lm.FailedReading, "LICENSE", err)
license = ""
}
license = string(l)
fontLicense, err := fs.ReadFile(localFS, filepath.Join("web", "fonts", "OFL.txt"))
if err != nil {
app.debug.Printf("Failed to load OFL.txt: %s", err)
app.debug.Printf(lm.FailedReading, "fontLicense", err)
}
license += "---Hanken Grotesk---\n\n"
@ -312,7 +313,7 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
defer gcHTML(gc, http.StatusOK, "password-reset.html", data)
// If it's a bot, pretend to be a success so the preview is nice.
if isBot {
app.debug.Println("PWR: Ignoring magic link visit from bot")
app.debug.Println(lm.IgnoreBotPWR)
data["success"] = true
data["pin"] = "NO-BO-TS"
return
@ -338,13 +339,13 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
if !isInternal && !setPassword {
resp, status, err = app.jf.ResetPassword(pin)
} else if time.Now().After(pwr.Expiry) {
app.debug.Printf("Ignoring PWR request due to expired internal PIN: %s", pin)
app.debug.Printf(lm.FailedChangePassword, lm.Jellyfin, "?", fmt.Sprintf(lm.ExpiredPIN, pin))
app.NoRouteHandler(gc)
return
} else {
status, err = app.jf.ResetPasswordAdmin(pwr.ID)
if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Password Reset failed (%d): %v", status, err)
app.err.Printf(lm.FailedChangePassword, lm.Jellyfin, "?", err)
} else {
status, err = app.jf.SetPassword(pwr.ID, "", pin)
}
@ -358,7 +359,7 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
username = resp.UsersReset[0]
}
} else {
app.err.Printf("Password Reset failed (%d): %v", status, err)
app.err.Printf(lm.FailedChangePassword, lm.Jellyfin, "?", err)
}
// Only log PWRs we know the user for.
@ -378,21 +379,21 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
if app.config.Section("ombi").Key("enabled").MustBool(false) {
jfUser, status, err := app.jf.UserByName(username, false)
if status != 200 || err != nil {
app.err.Printf("Failed to get user \"%s\" from jellyfin/emby (%d): %v", username, status, err)
app.err.Printf(lm.FailedGetUser, username, lm.Jellyfin, err)
return
}
ombiUser, status, err := app.getOmbiUser(jfUser.ID)
if status != 200 || err != nil {
app.err.Printf("Failed to get user \"%s\" from ombi (%d): %v", username, status, err)
app.err.Printf(lm.FailedGetUser, username, lm.Ombi, err)
return
}
ombiUser["password"] = pin
status, err = app.ombi.ModifyUser(ombiUser)
if status != 200 || err != nil {
app.err.Printf("Failed to set password for ombi user \"%s\" (%d): %v", ombiUser["userName"], status, err)
app.err.Printf(lm.FailedChangePassword, lm.Ombi, ombiUser["userName"], err)
return
}
app.debug.Printf("Reset password for ombi user \"%s\"", ombiUser["userName"])
app.debug.Printf(lm.ChangePassword, lm.Ombi, ombiUser["userName"])
}
}
@ -460,7 +461,7 @@ func (app *appContext) GenCaptcha(gc *gin.Context) {
}
capt, err := captcha.New(300, 100)
if err != nil {
app.err.Printf("Failed to generate captcha: %v", err)
app.err.Printf(lm.FailedGenerateCaptcha, err)
respondBool(500, false, gc)
return
}
@ -470,7 +471,7 @@ func (app *appContext) GenCaptcha(gc *gin.Context) {
captchaID := genAuthToken()
var buf bytes.Buffer
if err := capt.WriteImage(bufio.NewWriter(&buf)); err != nil {
app.err.Printf("Failed to render captcha: %v", err)
app.err.Printf(lm.FailedGenerateCaptcha, err)
respondBool(500, false, gc)
return
}
@ -503,8 +504,12 @@ func (app *appContext) verifyCaptcha(code, id, text string, isPWR bool) bool {
ok := true
if !isPWR {
inv, ok := app.storage.GetInvitesKey(code)
if !ok || (!isPWR && inv.Captchas == nil) {
app.debug.Printf("Couldn't find invite \"%s\"", code)
if !ok {
app.debug.Printf(lm.InvalidInviteCode, code)
return false
}
if !isPWR && inv.Captchas == nil {
app.debug.Printf(lm.CaptchaNotFound, id, code)
return false
}
c, ok = inv.Captchas[id]
@ -512,7 +517,7 @@ func (app *appContext) verifyCaptcha(code, id, text string, isPWR bool) bool {
c, ok = app.pwrCaptchas[code]
}
if !ok {
app.debug.Printf("Couldn't find Captcha \"%s\"", id)
app.debug.Printf(lm.CaptchaNotFound, id, code)
return false
}
return strings.ToLower(c.Answer) == strings.ToLower(text)
@ -534,8 +539,11 @@ func (app *appContext) verifyCaptcha(code, id, text string, isPWR bool) bool {
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil || resp.StatusCode != 200 {
app.err.Printf("Failed to read reCAPTCHA status (%d): %+v\n", resp.Status, err)
if err == nil && resp.StatusCode != 200 {
err = fmt.Errorf("failed (error %d)", resp.StatusCode)
}
if err != nil {
app.err.Printf(lm.FailedVerifyReCAPTCHA, err)
return false
}
defer resp.Body.Close()
@ -543,18 +551,19 @@ func (app *appContext) verifyCaptcha(code, id, text string, isPWR bool) bool {
body, err := io.ReadAll(resp.Body)
err = json.Unmarshal(body, &data)
if err != nil {
app.err.Printf("Failed to unmarshal reCAPTCHA response: %+v\n", err)
app.err.Printf(lm.FailedVerifyReCAPTCHA, err)
return false
}
hostname := app.config.Section("captcha").Key("recaptcha_hostname").MustString("")
if strings.ToLower(data.Hostname) != strings.ToLower(hostname) && data.Hostname != "" {
app.debug.Printf("Invalidating reCAPTCHA request: Hostnames didn't match (Wanted \"%s\", got \"%s\"\n", hostname, data.Hostname)
err = fmt.Errorf(lm.InvalidHostname, hostname, data.Hostname)
app.err.Printf(lm.FailedVerifyReCAPTCHA, err)
return false
}
if len(data.ErrorCodes) > 0 {
app.err.Printf("reCAPTCHA returned errors: %+v\n", data.ErrorCodes)
app.err.Printf(lm.AdditionalErrors, lm.ReCAPTCHA, data.ErrorCodes)
return false
}
@ -651,20 +660,19 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
token, err := jwt.Parse(key, checkToken)
if err != nil {
fail()
app.err.Printf("Failed to parse key: %s", err)
app.debug.Printf(lm.FailedParseJWT, err)
return
}
claims, ok := token.Claims.(jwt.MapClaims)
expiry := time.Unix(int64(claims["exp"].(float64)), 0)
if !(ok && token.Valid && claims["invite"].(string) == code && claims["type"].(string) == "confirmation" && expiry.After(time.Now())) {
fail()
app.debug.Printf("Invalid key")
app.debug.Printf(lm.InvalidJWT)
return
}
f, success := app.newUser(req, true, gc)
if !success {
app.err.Printf("Failed to create new user")
// Not meant for us. Calling this will be a mess, but at least it might give us some information.
// Not meant for us. Calling this is bad but at least gives us log output.
f(gc)
fail()
return