mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 09:00:10 +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:
parent
15a317f84f
commit
711394232b
@ -66,7 +66,7 @@ func (app *appContext) SetJellyseerrProfile(gc *gin.Context) {
|
|||||||
profile.Jellyseerr.User = u.UserTemplate
|
profile.Jellyseerr.User = u.UserTemplate
|
||||||
n, err := app.js.GetNotificationPreferencesByID(jellyseerrID)
|
n, err := app.js.GetNotificationPreferencesByID(jellyseerrID)
|
||||||
if err != nil {
|
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)
|
respond(500, "Couldn't get user notification prefs", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -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)
|
errors, code, err := app.ombi.NewUser(req.Username, req.Password, req.Email, profile.Ombi)
|
||||||
if err != nil || code != 200 {
|
if err != nil || code != 200 {
|
||||||
app.err.Printf(lm.FailedCreateUser, lm.Ombi, req.Username, err)
|
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 {
|
} else {
|
||||||
app.info.Printf(lm.CreateUser, lm.Ombi, req.Username)
|
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 {
|
} else {
|
||||||
app.info.Printf(lm.FailedCreateUser, lm.Ombi, req.Username, err)
|
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 {
|
} else {
|
||||||
ombiUser, status, err = app.getOmbiUser(id)
|
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)
|
resp, status, err := app.ombi.SetNotificationPrefs(ombiUser, dID, tUser)
|
||||||
if !(status == 200 || status == 204) || err != nil {
|
if !(status == 200 || status == 204) || err != nil {
|
||||||
app.err.Printf(lm.FailedSyncContactMethods, lm.Ombi, err)
|
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
63
api.go
@ -1,10 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
"github.com/hrfee/mediabrowser"
|
"github.com/hrfee/mediabrowser"
|
||||||
"github.com/itchyny/timefmt-go"
|
"github.com/itchyny/timefmt-go"
|
||||||
"github.com/lithammer/shortuuid/v3"
|
"github.com/lithammer/shortuuid/v3"
|
||||||
@ -122,14 +124,14 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !valid || req.PIN == "" {
|
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)
|
gc.JSON(400, validation)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
isInternal := false
|
isInternal := false
|
||||||
|
|
||||||
if captcha && !app.verifyCaptcha(req.PIN, req.PIN, req.CaptchaText, true) {
|
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)
|
respond(400, "errorCaptcha", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -138,7 +140,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
|
|||||||
if reset, ok := app.internalPWRs[req.PIN]; ok {
|
if reset, ok := app.internalPWRs[req.PIN]; ok {
|
||||||
isInternal = true
|
isInternal = true
|
||||||
if time.Now().After(reset.Expiry) {
|
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)
|
respondBool(401, false, gc)
|
||||||
delete(app.internalPWRs, req.PIN)
|
delete(app.internalPWRs, req.PIN)
|
||||||
return
|
return
|
||||||
@ -148,7 +150,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
|
|||||||
|
|
||||||
status, err := app.jf.ResetPasswordAdmin(userID)
|
status, err := app.jf.ResetPasswordAdmin(userID)
|
||||||
if !(status == 200 || status == 204) || err != nil {
|
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)
|
respondBool(status, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -156,7 +158,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
|
|||||||
} else {
|
} else {
|
||||||
resp, status, err := app.jf.ResetPassword(req.PIN)
|
resp, status, err := app.jf.ResetPassword(req.PIN)
|
||||||
if status != 200 || err != nil || !resp.Success {
|
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)
|
respondBool(status, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -176,7 +178,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
|
|||||||
user, status, err = app.jf.UserByName(username, false)
|
user, status, err = app.jf.UserByName(username, false)
|
||||||
}
|
}
|
||||||
if status != 200 || err != nil {
|
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)
|
respondBool(500, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -195,31 +197,33 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
status, err = app.jf.SetPassword(user.ID, prevPassword, req.Password)
|
status, err = app.jf.SetPassword(user.ID, prevPassword, req.Password)
|
||||||
if !(status == 200 || status == 204) || err != nil {
|
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)
|
respondBool(500, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
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 {
|
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)
|
respondBool(200, true, gc)
|
||||||
return
|
return
|
||||||
}
|
} */
|
||||||
ombiUser, status, err := app.getOmbiUser(user.ID)
|
ombiUser, status, err := app.getOmbiUser(user.ID)
|
||||||
if status != 200 || err != nil {
|
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)
|
respondBool(200, true, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ombiUser["password"] = req.Password
|
ombiUser["password"] = req.Password
|
||||||
status, err = app.ombi.ModifyUser(ombiUser)
|
status, err = app.ombi.ModifyUser(ombiUser)
|
||||||
if status != 200 || err != nil {
|
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)
|
respondBool(200, true, gc)
|
||||||
return
|
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)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
@ -231,7 +235,6 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
|
|||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @tags Configuration
|
// @tags Configuration
|
||||||
func (app *appContext) GetConfig(gc *gin.Context) {
|
func (app *appContext) GetConfig(gc *gin.Context) {
|
||||||
app.info.Println("Config requested")
|
|
||||||
resp := app.configBase
|
resp := app.configBase
|
||||||
// Load language options
|
// Load language options
|
||||||
formOptions := app.storage.lang.User.getOptions()
|
formOptions := app.storage.lang.User.getOptions()
|
||||||
@ -341,7 +344,6 @@ func (app *appContext) GetConfig(gc *gin.Context) {
|
|||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @tags Configuration
|
// @tags Configuration
|
||||||
func (app *appContext) ModifyConfig(gc *gin.Context) {
|
func (app *appContext) ModifyConfig(gc *gin.Context) {
|
||||||
app.info.Println("Config modification requested")
|
|
||||||
var req configDTO
|
var req configDTO
|
||||||
gc.BindJSON(&req)
|
gc.BindJSON(&req)
|
||||||
// Load a new config, as we set various default values in app.config that shouldn't be stored.
|
// 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")
|
tempConfig.Section("").Key("first_run").SetValue("false")
|
||||||
if err := tempConfig.SaveTo(app.configPath); err != nil {
|
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)
|
respond(500, err.Error(), gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
app.debug.Println("Config saved")
|
app.info.Printf(lm.ModifyConfig, app.configPath)
|
||||||
gc.JSON(200, map[string]bool{"success": true})
|
gc.JSON(200, map[string]bool{"success": true})
|
||||||
if req["restart-program"] != nil && req["restart-program"].(bool) {
|
if req["restart-program"] != nil && req["restart-program"].(bool) {
|
||||||
app.info.Println("Restarting...")
|
app.Restart()
|
||||||
if TRAY {
|
|
||||||
TRAYRESTART <- true
|
|
||||||
} else {
|
|
||||||
RESTART <- true
|
|
||||||
}
|
|
||||||
// Safety Sleep (Ensure shutdown tasks get done)
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
}
|
}
|
||||||
app.loadConfig()
|
app.loadConfig()
|
||||||
// Reinitialize password validator on config change, as opposed to every applicable request like in python.
|
// Reinitialize password validator on config change, as opposed to every applicable request like in python.
|
||||||
if _, ok := req["password_validation"]; ok {
|
if _, ok := req["password_validation"]; ok {
|
||||||
app.debug.Println("Reinitializing validator")
|
|
||||||
validatorConf := ValidatorConf{
|
validatorConf := ValidatorConf{
|
||||||
"length": app.config.Section("password_validation").Key("min_length").MustInt(0),
|
"length": app.config.Section("password_validation").Key("min_length").MustInt(0),
|
||||||
"uppercase": app.config.Section("password_validation").Key("upper").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
|
// @tags Configuration
|
||||||
func (app *appContext) ApplyUpdate(gc *gin.Context) {
|
func (app *appContext) ApplyUpdate(gc *gin.Context) {
|
||||||
if !app.update.CanUpdate {
|
if !app.update.CanUpdate {
|
||||||
respond(400, "Update is manual", gc)
|
app.info.Printf(lm.FailedApplyUpdate, lm.UpdateManual)
|
||||||
|
respond(400, lm.UpdateManual, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := app.update.update()
|
err := app.update.update()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Printf("Failed to apply update: %v", err)
|
app.err.Printf(lm.FailedApplyUpdate, err)
|
||||||
respondBool(500, false, gc)
|
respondBool(500, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -452,8 +447,9 @@ func (app *appContext) ApplyUpdate(gc *gin.Context) {
|
|||||||
func (app *appContext) Logout(gc *gin.Context) {
|
func (app *appContext) Logout(gc *gin.Context) {
|
||||||
cookie, err := gc.Cookie("refresh")
|
cookie, err := gc.Cookie("refresh")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.debug.Printf("Couldn't get cookies: %s", err)
|
msg := fmt.Sprintf(lm.FailedGetCookies, "refresh", err)
|
||||||
respond(500, "Couldn't fetch cookies", gc)
|
app.debug.Println(msg)
|
||||||
|
respond(500, msg, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
app.invalidTokens = append(app.invalidTokens, cookie)
|
app.invalidTokens = append(app.invalidTokens, cookie)
|
||||||
@ -526,11 +522,7 @@ func (app *appContext) ServeLang(gc *gin.Context) {
|
|||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @tags Other
|
// @tags Other
|
||||||
func (app *appContext) restart(gc *gin.Context) {
|
func (app *appContext) restart(gc *gin.Context) {
|
||||||
app.info.Println("Restarting...")
|
app.Restart()
|
||||||
err := app.Restart()
|
|
||||||
if err != nil {
|
|
||||||
app.err.Printf("Couldn't restart, try restarting manually: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Returns the last 100 lines of the log.
|
// @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!
|
// no need to syscall.exec anymore!
|
||||||
func (app *appContext) Restart() error {
|
func (app *appContext) Restart() error {
|
||||||
|
app.info.Println(lm.Restarting)
|
||||||
if TRAY {
|
if TRAY {
|
||||||
TRAYRESTART <- true
|
TRAYRESTART <- true
|
||||||
} else {
|
} else {
|
||||||
|
65
auth.go
65
auth.go
@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
"github.com/hrfee/mediabrowser"
|
"github.com/hrfee/mediabrowser"
|
||||||
"github.com/lithammer/shortuuid/v3"
|
"github.com/lithammer/shortuuid/v3"
|
||||||
)
|
)
|
||||||
@ -41,6 +42,8 @@ func (app *appContext) webAuth() gin.HandlerFunc {
|
|||||||
return app.authenticate
|
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.
|
// 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) {
|
func CreateToken(userId, jfId string, admin bool) (string, string, error) {
|
||||||
var token, refresh string
|
var token, refresh string
|
||||||
@ -72,32 +75,26 @@ func (app *appContext) decodeValidateAuthHeader(gc *gin.Context) (claims jwt.Map
|
|||||||
ok = false
|
ok = false
|
||||||
header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2)
|
header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2)
|
||||||
if header[0] != "Bearer" {
|
if header[0] != "Bearer" {
|
||||||
app.debug.Println("Invalid authorization header")
|
app.authLog(lm.InvalidAuthHeader)
|
||||||
respond(401, "Unauthorized", gc)
|
respond(401, "Unauthorized", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
token, err := jwt.Parse(string(header[1]), checkToken)
|
token, err := jwt.Parse(string(header[1]), checkToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.debug.Printf("Auth denied: %s", err)
|
app.authLog(fmt.Sprintf(lm.FailedParseJWT, err))
|
||||||
respond(401, "Unauthorized", gc)
|
respond(401, "Unauthorized", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
claims, ok = token.Claims.(jwt.MapClaims)
|
claims, ok = token.Claims.(jwt.MapClaims)
|
||||||
if !ok {
|
if !ok {
|
||||||
app.debug.Println("Invalid JWT")
|
app.authLog(lm.FailedCastJWT)
|
||||||
respond(401, "Unauthorized", gc)
|
respond(401, "Unauthorized", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
expiryUnix := int64(claims["exp"].(float64))
|
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)
|
expiry := time.Unix(expiryUnix, 0)
|
||||||
if !(ok && token.Valid && claims["type"].(string) == "bearer" && expiry.After(time.Now())) {
|
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))
|
// app.debug.Printf("Expiry: %+v, OK: %t, Valid: %t, ClaimType: %s\n", expiry, ok, token.Valid, claims["type"].(string))
|
||||||
respond(401, "Unauthorized", gc)
|
respond(401, "Unauthorized", gc)
|
||||||
ok = false
|
ok = false
|
||||||
@ -115,7 +112,7 @@ func (app *appContext) authenticate(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
isAdminToken := claims["admin"].(bool)
|
isAdminToken := claims["admin"].(bool)
|
||||||
if !isAdminToken {
|
if !isAdminToken {
|
||||||
app.debug.Printf("Auth denied: Token was not for admin access")
|
app.authLog(lm.NonAdminToken)
|
||||||
respond(401, "Unauthorized", gc)
|
respond(401, "Unauthorized", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -130,14 +127,13 @@ func (app *appContext) authenticate(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !match {
|
if !match {
|
||||||
app.debug.Printf("Couldn't find user ID \"%s\"", userID)
|
app.authLog(fmt.Sprintf(lm.NonAdminUser, userID))
|
||||||
respond(401, "Unauthorized", gc)
|
respond(401, "Unauthorized", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gc.Set("jfId", jfID)
|
gc.Set("jfId", jfID)
|
||||||
gc.Set("userId", userID)
|
gc.Set("userId", userID)
|
||||||
gc.Set("userMode", false)
|
gc.Set("userMode", false)
|
||||||
app.debug.Println("Auth succeeded")
|
|
||||||
gc.Next()
|
gc.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,7 +156,7 @@ func (app *appContext) decodeValidateLoginHeader(gc *gin.Context, userpage bool)
|
|||||||
password = creds[1]
|
password = creds[1]
|
||||||
ok = false
|
ok = false
|
||||||
if username == "" || password == "" {
|
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)
|
respond(401, "Unauthorized", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -173,16 +169,16 @@ func (app *appContext) validateJellyfinCredentials(username, password string, gc
|
|||||||
user, status, err := app.authJf.Authenticate(username, password)
|
user, status, err := app.authJf.Authenticate(username, password)
|
||||||
if status != 200 || err != nil {
|
if status != 200 || err != nil {
|
||||||
if status == 401 || status == 400 {
|
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)
|
respond(401, "Unauthorized", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if status == 403 {
|
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)
|
respond(403, "yourAccountWasDisabled", gc)
|
||||||
return
|
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)
|
respond(500, "Jellyfin error", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -199,7 +195,7 @@ func (app *appContext) validateJellyfinCredentials(username, password string, gc
|
|||||||
// @tags Auth
|
// @tags Auth
|
||||||
// @Security getTokenAuth
|
// @Security getTokenAuth
|
||||||
func (app *appContext) getTokenLogin(gc *gin.Context) {
|
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)
|
username, password, ok := app.decodeValidateLoginHeader(gc, false)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
@ -209,13 +205,12 @@ func (app *appContext) getTokenLogin(gc *gin.Context) {
|
|||||||
for _, user := range app.adminUsers {
|
for _, user := range app.adminUsers {
|
||||||
if user.Username == username && user.Password == password {
|
if user.Username == username && user.Password == password {
|
||||||
match = true
|
match = true
|
||||||
app.debug.Println("Found existing user")
|
|
||||||
userID = user.UserID
|
userID = user.UserID
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !app.jellyfinLogin && !match {
|
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)
|
respond(401, "Unauthorized", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -233,7 +228,7 @@ func (app *appContext) getTokenLogin(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
accountsAdmin = accountsAdmin || (adminOnly && user.Policy.IsAdministrator)
|
accountsAdmin = accountsAdmin || (adminOnly && user.Policy.IsAdministrator)
|
||||||
if !accountsAdmin {
|
if !accountsAdmin {
|
||||||
app.debug.Printf("Auth denied: Users \"%s\" isn't admin", username)
|
app.authLog(fmt.Sprintf(lm.NonAdminUser, username))
|
||||||
respond(401, "Unauthorized", gc)
|
respond(401, "Unauthorized", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -243,12 +238,12 @@ func (app *appContext) getTokenLogin(gc *gin.Context) {
|
|||||||
newUser := User{
|
newUser := User{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
}
|
}
|
||||||
app.debug.Printf("Token generated for user \"%s\"", username)
|
app.debug.Printf(lm.GenerateToken, username)
|
||||||
app.adminUsers = append(app.adminUsers, newUser)
|
app.adminUsers = append(app.adminUsers, newUser)
|
||||||
}
|
}
|
||||||
token, refresh, err := CreateToken(userID, jfID, true)
|
token, refresh, err := CreateToken(userID, jfID, true)
|
||||||
if err != nil {
|
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)
|
respond(500, "Couldn't generate token", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -261,35 +256,29 @@ func (app *appContext) decodeValidateRefreshCookie(gc *gin.Context, cookieName s
|
|||||||
ok = false
|
ok = false
|
||||||
cookie, err := gc.Cookie(cookieName)
|
cookie, err := gc.Cookie(cookieName)
|
||||||
if err != nil || cookie == "" {
|
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)
|
respond(400, "Couldn't get token", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for _, token := range app.invalidTokens {
|
for _, token := range app.invalidTokens {
|
||||||
if cookie == token {
|
if cookie == token {
|
||||||
app.debug.Println("getTokenRefresh: Invalid token")
|
app.authLog(lm.LocallyInvalidatedJWT)
|
||||||
respond(401, "Invalid token", gc)
|
respond(401, lm.InvalidJWT, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
token, err := jwt.Parse(cookie, checkToken)
|
token, err := jwt.Parse(cookie, checkToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.debug.Println("getTokenRefresh: Invalid token")
|
app.authLog(lm.FailedParseJWT)
|
||||||
respond(400, "Invalid token", gc)
|
respond(400, lm.InvalidJWT, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
claims, ok = token.Claims.(jwt.MapClaims)
|
claims, ok = token.Claims.(jwt.MapClaims)
|
||||||
expiryUnix := int64(claims["exp"].(float64))
|
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)
|
expiry := time.Unix(expiryUnix, 0)
|
||||||
if !(ok && token.Valid && claims["type"].(string) == "refresh" && expiry.After(time.Now())) {
|
if !(ok && token.Valid && claims["type"].(string) == "refresh" && expiry.After(time.Now())) {
|
||||||
app.debug.Printf("getTokenRefresh: Invalid token: %+v", err)
|
app.authLog(lm.InvalidJWT)
|
||||||
respond(401, "Invalid token", gc)
|
respond(401, lm.InvalidJWT, gc)
|
||||||
ok = false
|
ok = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -304,7 +293,7 @@ func (app *appContext) decodeValidateRefreshCookie(gc *gin.Context, cookieName s
|
|||||||
// @Router /token/refresh [get]
|
// @Router /token/refresh [get]
|
||||||
// @tags Auth
|
// @tags Auth
|
||||||
func (app *appContext) getTokenRefresh(gc *gin.Context) {
|
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")
|
claims, ok := app.decodeValidateRefreshCookie(gc, "refresh")
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
@ -313,7 +302,7 @@ func (app *appContext) getTokenRefresh(gc *gin.Context) {
|
|||||||
jfID := claims["jfid"].(string)
|
jfID := claims["jfid"].(string)
|
||||||
jwt, refresh, err := CreateToken(userID, jfID, true)
|
jwt, refresh, err := CreateToken(userID, jfID, true)
|
||||||
if err != nil {
|
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)
|
respond(500, "Couldn't generate token", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
31
backups.go
31
backups.go
@ -7,6 +7,8 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -60,12 +62,12 @@ func (app *appContext) getBackups() *BackupList {
|
|||||||
path := app.config.Section("backups").Key("path").String()
|
path := app.config.Section("backups").Key("path").String()
|
||||||
err := os.MkdirAll(path, 0755)
|
err := os.MkdirAll(path, 0755)
|
||||||
if err != nil {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
items, err := os.ReadDir(path)
|
items, err := os.ReadDir(path)
|
||||||
if err != nil {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
backups := &BackupList{}
|
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))
|
t, err := time.Parse(BACKUP_DATEFMT, strings.TrimSuffix(strings.TrimPrefix(strings.TrimPrefix(item.Name(), BACKUP_UPLOAD_PREFIX), BACKUP_PREFIX), BACKUP_SUFFIX))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.debug.Printf("Failed to parse backup filename \"%s\": %v\n", item.Name(), err)
|
app.debug.Printf(lm.FailedParseTime, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
backups.dates[i] = t
|
backups.dates[i] = t
|
||||||
@ -101,36 +103,36 @@ func (app *appContext) makeBackup() (fileDetails CreateBackupDTO) {
|
|||||||
sort.Sort(backups)
|
sort.Sort(backups)
|
||||||
for _, item := range backups.files[:toDelete] {
|
for _, item := range backups.files[:toDelete] {
|
||||||
fullpath := filepath.Join(path, item.Name())
|
fullpath := filepath.Join(path, item.Name())
|
||||||
app.debug.Printf("Deleting old backup \"%s\"\n", item.Name())
|
|
||||||
err := os.Remove(fullpath)
|
err := os.Remove(fullpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Printf("Failed to delete old backup \"%s\": %v\n", fullpath, err)
|
app.err.Printf(lm.FailedDeleteOldBackup, fullpath, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
app.debug.Printf(lm.DeleteOldBackup, fullpath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fullpath := filepath.Join(path, fname)
|
fullpath := filepath.Join(path, fname)
|
||||||
f, err := os.Create(fullpath)
|
f, err := os.Create(fullpath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Printf("Failed to open backup file \"%s\": %v\n", fullpath, err)
|
app.err.Printf(lm.FailedOpen, fullpath, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
_, err = app.storage.db.Badger().Backup(f, 0)
|
_, err = app.storage.db.Badger().Backup(f, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Printf("Failed to create backup: %v\n", err)
|
app.err.Printf(lm.FailedCreateBackup, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fstat, err := f.Stat()
|
fstat, err := f.Stat()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Printf("Failed to get info on new backup: %v\n", err)
|
app.err.Printf(lm.FailedStat, fullpath, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fileDetails.Size = fileSize(fstat.Size())
|
fileDetails.Size = fileSize(fstat.Size())
|
||||||
fileDetails.Name = fname
|
fileDetails.Name = fname
|
||||||
fileDetails.Path = fullpath
|
fileDetails.Path = fullpath
|
||||||
// fmt.Printf("Created backup %+v\n", fileDetails)
|
app.debug.Printf(lm.CreateBackup, fileDetails)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,25 +141,25 @@ func (app *appContext) loadPendingBackup() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
oldPath := filepath.Join(app.dataPath, "db-"+string(time.Now().Unix())+"-pre-"+filepath.Base(LOADBAK))
|
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)
|
err := os.Rename(app.storage.db_path, oldPath)
|
||||||
if err != nil {
|
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()
|
app.ConnectDB()
|
||||||
defer app.storage.db.Close()
|
defer app.storage.db.Close()
|
||||||
|
|
||||||
f, err := os.Open(LOADBAK)
|
f, err := os.Open(LOADBAK)
|
||||||
if err != nil {
|
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)
|
err = app.storage.db.Badger().Load(f, 256)
|
||||||
f.Close()
|
f.Close()
|
||||||
if err != nil {
|
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 = ""
|
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
|
interval := time.Duration(app.config.Section("backups").Key("every_n_minutes").MustInt(1440)) * time.Minute
|
||||||
d := NewGenericDaemon(interval, app,
|
d := NewGenericDaemon(interval, app,
|
||||||
func(app *appContext) {
|
func(app *appContext) {
|
||||||
app.debug.Println("Backups: Creating backup")
|
|
||||||
app.makeBackup()
|
app.makeBackup()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
14
config.go
14
config.go
@ -7,8 +7,10 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/hrfee/jfa-go/easyproxy"
|
"github.com/hrfee/jfa-go/easyproxy"
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -140,7 +142,7 @@ func (app *appContext) loadConfig() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if allDisabled {
|
if allDisabled {
|
||||||
fmt.Println("SETALLTRUE")
|
app.info.Println(lm.EnableAllPWRMethods)
|
||||||
for _, v := range pwrMethods {
|
for _, v := range pwrMethods {
|
||||||
app.config.Section("user_page").Key(v).SetValue("true")
|
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.proxyConfig.Password = app.config.Section("advanced").Key("proxy_password").MustString("")
|
||||||
app.proxyTransport, err = easyproxy.NewTransport(app.proxyConfig)
|
app.proxyTransport, err = easyproxy.NewTransport(app.proxyConfig)
|
||||||
if err != nil {
|
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.proxyEnabled = true
|
||||||
|
app.info.Printf(lm.InitProxy, app.proxyConfig.Addr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.MustSetValue("updates", "enabled", "true")
|
app.MustSetValue("updates", "enabled", "true")
|
||||||
|
5
email.go
5
email.go
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/gomarkdown/markdown"
|
"github.com/gomarkdown/markdown"
|
||||||
"github.com/gomarkdown/markdown/html"
|
"github.com/gomarkdown/markdown/html"
|
||||||
"github.com/hrfee/jfa-go/easyproxy"
|
"github.com/hrfee/jfa-go/easyproxy"
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
"github.com/hrfee/mediabrowser"
|
"github.com/hrfee/mediabrowser"
|
||||||
"github.com/itchyny/timefmt-go"
|
"github.com/itchyny/timefmt-go"
|
||||||
"github.com/mailgun/mailgun-go/v4"
|
"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))
|
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)
|
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 {
|
if err != nil {
|
||||||
app.err.Printf("Error while initiating SMTP mailer: %v", err)
|
app.err.Printf(lm.FailedInitSMTP, err)
|
||||||
}
|
}
|
||||||
} else if method == "mailgun" {
|
} else if method == "mailgun" {
|
||||||
emailer.NewMailgun(app.config.Section("mailgun").Key("api_url").String(), app.config.Section("mailgun").Key("api_key").String())
|
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.
|
// Only used in html email.
|
||||||
template["pin_code"] = pwr.Pin
|
template["pin_code"] = pwr.Pin
|
||||||
} else {
|
} else {
|
||||||
app.info.Println("Couldn't generate PWR link: %v", err)
|
app.info.Println(lm.FailedGeneratePWRLink, err)
|
||||||
template["pin"] = pwr.Pin
|
template["pin"] = pwr.Pin
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package main
|
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
|
// 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) Name(name string) { d.name = name }
|
||||||
|
|
||||||
func (d *GenericDaemon) run() {
|
func (d *GenericDaemon) run() {
|
||||||
d.app.info.Printf("%s started", d.name)
|
d.app.info.Printf(lm.StartDaemon, d.name)
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-d.ShutdownChannel:
|
case <-d.ShutdownChannel:
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dgraph-io/badger/v3"
|
"github.com/dgraph-io/badger/v3"
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
"github.com/hrfee/mediabrowser"
|
"github.com/hrfee/mediabrowser"
|
||||||
"github.com/timshannon/badgerhold/v4"
|
"github.com/timshannon/badgerhold/v4"
|
||||||
)
|
)
|
||||||
@ -12,7 +13,7 @@ import (
|
|||||||
// meant to be called with other such housekeeping functions, so assumes
|
// meant to be called with other such housekeeping functions, so assumes
|
||||||
// the user cache is fresh.
|
// the user cache is fresh.
|
||||||
func (app *appContext) clearEmails() {
|
func (app *appContext) clearEmails() {
|
||||||
app.debug.Println("Housekeeping: removing unused email addresses")
|
app.debug.Println(lm.HousekeepingEmail)
|
||||||
emails := app.storage.GetEmails()
|
emails := app.storage.GetEmails()
|
||||||
for _, email := range emails {
|
for _, email := range emails {
|
||||||
_, _, err := app.jf.UserByID(email.JellyfinID, false)
|
_, _, 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.
|
// clearDiscord does the same as clearEmails, but for Discord Users.
|
||||||
func (app *appContext) clearDiscord() {
|
func (app *appContext) clearDiscord() {
|
||||||
app.debug.Println("Housekeeping: removing unused Discord IDs")
|
app.debug.Println(lm.HousekeepingDiscord)
|
||||||
discordUsers := app.storage.GetDiscord()
|
discordUsers := app.storage.GetDiscord()
|
||||||
for _, discordUser := range discordUsers {
|
for _, discordUser := range discordUsers {
|
||||||
_, _, err := app.jf.UserByID(discordUser.JellyfinID, false)
|
_, _, 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.
|
// clearMatrix does the same as clearEmails, but for Matrix Users.
|
||||||
func (app *appContext) clearMatrix() {
|
func (app *appContext) clearMatrix() {
|
||||||
app.debug.Println("Housekeeping: removing unused Matrix IDs")
|
app.debug.Println(lm.HousekeepingMatrix)
|
||||||
matrixUsers := app.storage.GetMatrix()
|
matrixUsers := app.storage.GetMatrix()
|
||||||
for _, matrixUser := range matrixUsers {
|
for _, matrixUser := range matrixUsers {
|
||||||
_, _, err := app.jf.UserByID(matrixUser.JellyfinID, false)
|
_, _, 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.
|
// clearTelegram does the same as clearEmails, but for Telegram Users.
|
||||||
func (app *appContext) clearTelegram() {
|
func (app *appContext) clearTelegram() {
|
||||||
app.debug.Println("Housekeeping: removing unused Telegram IDs")
|
app.debug.Println(lm.HousekeepingTelegram)
|
||||||
telegramUsers := app.storage.GetTelegram()
|
telegramUsers := app.storage.GetTelegram()
|
||||||
for _, telegramUser := range telegramUsers {
|
for _, telegramUser := range telegramUsers {
|
||||||
_, _, err := app.jf.UserByID(telegramUser.JellyfinID, false)
|
_, _, err := app.jf.UserByID(telegramUser.JellyfinID, false)
|
||||||
@ -75,7 +76,7 @@ func (app *appContext) clearTelegram() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) clearPWRCaptchas() {
|
func (app *appContext) clearPWRCaptchas() {
|
||||||
app.debug.Println("Housekeeping: Clearing old PWR Captchas")
|
app.debug.Println(lm.HousekeepingCaptcha)
|
||||||
captchas := map[string]Captcha{}
|
captchas := map[string]Captcha{}
|
||||||
for k, capt := range app.pwrCaptchas {
|
for k, capt := range app.pwrCaptchas {
|
||||||
if capt.Generated.Add(CAPTCHA_VALIDITY * time.Second).After(time.Now()) {
|
if capt.Generated.Add(CAPTCHA_VALIDITY * time.Second).After(time.Now()) {
|
||||||
@ -86,7 +87,7 @@ func (app *appContext) clearPWRCaptchas() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (app *appContext) clearActivities() {
|
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)
|
keepCount := app.config.Section("activity_log").Key("keep_n_records").MustInt(1000)
|
||||||
maxAgeDays := app.config.Section("activity_log").Key("delete_after_days").MustInt(90)
|
maxAgeDays := app.config.Section("activity_log").Key("delete_after_days").MustInt(90)
|
||||||
minAge := time.Now().AddDate(0, 0, -maxAgeDays)
|
minAge := time.Now().AddDate(0, 0, -maxAgeDays)
|
||||||
@ -103,7 +104,7 @@ func (app *appContext) clearActivities() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == badger.ErrTxnTooBig {
|
if err == badger.ErrTxnTooBig {
|
||||||
app.debug.Printf("Activities: Delete txn was too big, doing it manually.")
|
app.debug.Printf(lm.AcitivityLogTxnTooBig)
|
||||||
list := []Activity{}
|
list := []Activity{}
|
||||||
if errorSource == 0 {
|
if errorSource == 0 {
|
||||||
app.storage.db.Find(&list, badgerhold.Where("Time").Lt(minAge))
|
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 {
|
func newHousekeepingDaemon(interval time.Duration, app *appContext) *GenericDaemon {
|
||||||
d := NewGenericDaemon(interval, app,
|
d := NewGenericDaemon(interval, app,
|
||||||
func(app *appContext) {
|
func(app *appContext) {
|
||||||
app.debug.Println("Housekeeping: Checking for expired invites")
|
app.debug.Println(lm.HousekeepingInvites)
|
||||||
app.checkInvites()
|
app.checkInvites()
|
||||||
},
|
},
|
||||||
func(app *appContext) { app.clearActivities() },
|
func(app *appContext) { app.clearActivities() },
|
||||||
|
@ -5,20 +5,21 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hrfee/jfa-go/jellyseerr"
|
"github.com/hrfee/jfa-go/jellyseerr"
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
|
func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
|
||||||
user, imported, err := app.js.GetOrImportUser(jfID)
|
user, imported, err := app.js.GetOrImportUser(jfID)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
if imported {
|
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)
|
notif, err := app.js.GetNotificationPreferencesByID(user.ID)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
|
|||||||
if ok && email.Addr != "" && user.Email != email.Addr {
|
if ok && email.Addr != "" && user.Email != email.Addr {
|
||||||
err = app.js.ModifyMainUserSettings(jfID, jellyseerr.MainUserSettings{Email: email.Addr})
|
err = app.js.ModifyMainUserSettings(jfID, jellyseerr.MainUserSettings{Email: email.Addr})
|
||||||
if err != nil {
|
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 {
|
} else {
|
||||||
contactMethods[jellyseerr.FieldEmailEnabled] = email.Contact
|
contactMethods[jellyseerr.FieldEmailEnabled] = email.Contact
|
||||||
}
|
}
|
||||||
@ -51,7 +52,7 @@ func (app *appContext) SynchronizeJellyseerrUser(jfID string) {
|
|||||||
if len(contactMethods) != 0 {
|
if len(contactMethods) != 0 {
|
||||||
err := app.js.ModifyNotifications(jfID, contactMethods)
|
err := app.js.ModifyNotifications(jfID, contactMethods)
|
||||||
if err != nil {
|
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() {
|
func (app *appContext) SynchronizeJellyseerrUsers() {
|
||||||
users, status, err := app.jf.GetUsers(false)
|
users, status, err := app.jf.GetUsers(false)
|
||||||
if err != nil || status != 200 {
|
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
|
return
|
||||||
}
|
}
|
||||||
// I'm sure Jellyseerr can handle it,
|
// I'm sure Jellyseerr can handle it,
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
package logmessages
|
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 (
|
const (
|
||||||
Jellyseerr = "Jellyseerr"
|
Jellyseerr = "Jellyseerr"
|
||||||
Jellyfin = "Jellyfin"
|
Jellyfin = "Jellyfin"
|
||||||
@ -15,13 +22,17 @@ const (
|
|||||||
NoConfig = "Couldn't find default config file"
|
NoConfig = "Couldn't find default config file"
|
||||||
Write = "Wrote to \"%s\""
|
Write = "Wrote to \"%s\""
|
||||||
FailedWriting = "Failed to write to \"%s\": %v"
|
FailedWriting = "Failed to write to \"%s\": %v"
|
||||||
|
FailedCreateDir = "Failed to create directory \"%s\": %v"
|
||||||
FailedReading = "Failed to read from \"%s\": %v"
|
FailedReading = "Failed to read from \"%s\": %v"
|
||||||
FailedOpen = "Failed to open \"%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\""
|
CopyConfig = "Copied default configuration to \"%s\""
|
||||||
FailedCopyConfig = "Failed to copy default configuration to \"%s\": %v"
|
FailedCopyConfig = "Failed to copy default configuration to \"%s\": %v"
|
||||||
LoadConfig = "Loaded config file \"%s\""
|
LoadConfig = "Loaded config file \"%s\""
|
||||||
FailedLoadConfig = "Failed to load config file \"%s\": %v"
|
FailedLoadConfig = "Failed to load config file \"%s\": %v"
|
||||||
|
ModifyConfig = "Config saved to \"%s\""
|
||||||
|
|
||||||
SocketPath = "Socket Path: \"%s\""
|
SocketPath = "Socket Path: \"%s\""
|
||||||
FailedSocketConnect = "Couldn't establish socket connection at \"%s\": %v"
|
FailedSocketConnect = "Couldn't establish socket connection at \"%s\": %v"
|
||||||
@ -67,6 +78,8 @@ const (
|
|||||||
|
|
||||||
QuitReceived = "Restart/Quit signal received, please be patient."
|
QuitReceived = "Restart/Quit signal received, please be patient."
|
||||||
Quitting = "Shutting down..."
|
Quitting = "Shutting down..."
|
||||||
|
Restarting = "Restarting..."
|
||||||
|
FailedHardRestartWindows = "hard restarts not available on windows"
|
||||||
Quit = "Server shut down."
|
Quit = "Server shut down."
|
||||||
FailedQuit = "Server shutdown failed: %v"
|
FailedQuit = "Server shutdown failed: %v"
|
||||||
|
|
||||||
@ -83,6 +96,7 @@ const (
|
|||||||
DeleteInvite = "Deleting invite \"%s\""
|
DeleteInvite = "Deleting invite \"%s\""
|
||||||
FailedDeleteInvite = "Failed to delete invite \"%s\": %v"
|
FailedDeleteInvite = "Failed to delete invite \"%s\": %v"
|
||||||
GenerateInvite = "Generating new invite"
|
GenerateInvite = "Generating new invite"
|
||||||
|
FailedGenerateInvite = "Failed to generate new invite: %v"
|
||||||
InvalidInviteCode = "Invalid invite code \"%s\""
|
InvalidInviteCode = "Invalid invite code \"%s\""
|
||||||
|
|
||||||
FailedSendToTooltipNoUser = "Failed: \"%s\" not found"
|
FailedSendToTooltipNoUser = "Failed: \"%s\" not found"
|
||||||
@ -94,12 +108,14 @@ const (
|
|||||||
|
|
||||||
SetAdminNotify = "Set \"%s\" to %t for admin address \"%s\""
|
SetAdminNotify = "Set \"%s\" to %t for admin address \"%s\""
|
||||||
|
|
||||||
// api-jellyseerr.go
|
// *jellyseerr*.go
|
||||||
FailedGetUsers = "Failed to get user(s) from %s: %v"
|
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.
|
// 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"
|
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"
|
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
|
// api-messages.go
|
||||||
FailedGetCustomMessage = "Failed to get custom message \"%s\""
|
FailedGetCustomMessage = "Failed to get custom message \"%s\""
|
||||||
@ -107,9 +123,10 @@ const (
|
|||||||
|
|
||||||
// Matrix
|
// Matrix
|
||||||
InvalidPIN = "Invalid PIN \"%s\""
|
InvalidPIN = "Invalid PIN \"%s\""
|
||||||
|
ExpiredPIN = "Expired PIN \"%s\""
|
||||||
|
InvalidPassword = "Invalid Password"
|
||||||
UnauthorizedPIN = "Unauthorized PIN \"%s\""
|
UnauthorizedPIN = "Unauthorized PIN \"%s\""
|
||||||
FailedCreateRoom = "Failed to create room: %v"
|
FailedCreateRoom = "Failed to create room: %v"
|
||||||
FailedGenerateToken = "Failed to generate token: %v"
|
|
||||||
|
|
||||||
// api-profiles.go
|
// api-profiles.go
|
||||||
SetDefaultProfile = "Setting default profile to \"%s\""
|
SetDefaultProfile = "Setting default profile to \"%s\""
|
||||||
@ -123,6 +140,7 @@ const (
|
|||||||
FailedGetJellyfinDisplayPrefs = "Failed to get DisplayPreferences for user \"%s\" from " + Jellyfin + ": %v"
|
FailedGetJellyfinDisplayPrefs = "Failed to get DisplayPreferences for user \"%s\" from " + Jellyfin + ": %v"
|
||||||
ProfileNoHomescreen = "No homescreen template in profile \"%s\""
|
ProfileNoHomescreen = "No homescreen template in profile \"%s\""
|
||||||
Profile = "profile"
|
Profile = "profile"
|
||||||
|
Lang = "language"
|
||||||
User = "user"
|
User = "user"
|
||||||
ApplyingTemplatesFrom = "Applying templates from %s: \"%s\" to %d users"
|
ApplyingTemplatesFrom = "Applying templates from %s: \"%s\" to %d users"
|
||||||
DelayingRequests = "Delay will be added between requests (count = %d)"
|
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"
|
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"
|
IncorrectCaptcha = "captcha incorrect"
|
||||||
|
|
||||||
@ -162,6 +180,123 @@ const (
|
|||||||
UserEmailAdjusted = "Email for user \"%s\" adjusted"
|
UserEmailAdjusted = "Email for user \"%s\" adjusted"
|
||||||
UserAdminAdjusted = "Admin state for user \"%s\" set to %t"
|
UserAdminAdjusted = "Admin state for user \"%s\" set to %t"
|
||||||
UserLabelAdjusted = "Label for user \"%s\" set to \"%s\""
|
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 (
|
const (
|
||||||
@ -169,7 +304,24 @@ const (
|
|||||||
FailedParseJWT = "Failed to parse JWT: %v"
|
FailedParseJWT = "Failed to parse JWT: %v"
|
||||||
FailedCastJWT = "JWT claims unreadable"
|
FailedCastJWT = "JWT claims unreadable"
|
||||||
InvalidJWT = "JWT was invalidated, of incorrect type or has expired"
|
InvalidJWT = "JWT was invalidated, of incorrect type or has expired"
|
||||||
|
LocallyInvalidatedJWT = "JWT is listed as invalidated"
|
||||||
FailedSignJWT = "Failed to sign JWT: %v"
|
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 (
|
const (
|
||||||
@ -209,6 +361,10 @@ const (
|
|||||||
FailedSendExpiryAdjustmentMessage = "Failed to send expiry adjustment message for \"%s\" to \"%s\": %v"
|
FailedSendExpiryAdjustmentMessage = "Failed to send expiry adjustment message for \"%s\" to \"%s\": %v"
|
||||||
SentExpiryAdjustmentMessage = "Sent expiry adjustment message for \"%s\" to \"%s\""
|
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"
|
FailedConstructAnnouncementMessage = "Failed to construct announcement message for \"%s\": %v"
|
||||||
FailedSendAnnouncementMessage = "Failed to send announcement message for \"%s\" to \"%s\": %v"
|
FailedSendAnnouncementMessage = "Failed to send announcement message for \"%s\" to \"%s\": %v"
|
||||||
SentAnnouncementMessage = "Sent announcement message for \"%s\" to \"%s\""
|
SentAnnouncementMessage = "Sent announcement message for \"%s\" to \"%s\""
|
||||||
|
11
matrix.go
11
matrix.go
@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gomarkdown/markdown"
|
"github.com/gomarkdown/markdown"
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
"github.com/timshannon/badgerhold/v4"
|
"github.com/timshannon/badgerhold/v4"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
@ -118,13 +119,13 @@ func (d *MatrixDaemon) generateAccessToken(homeserver, username, password string
|
|||||||
|
|
||||||
func (d *MatrixDaemon) run() {
|
func (d *MatrixDaemon) run() {
|
||||||
startTime := d.start
|
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)
|
syncer := d.bot.Syncer.(*mautrix.DefaultSyncer)
|
||||||
HandleSyncerCrypto(startTime, d, syncer)
|
HandleSyncerCrypto(startTime, d, syncer)
|
||||||
syncer.OnEventType(event.EventMessage, d.handleMessage)
|
syncer.OnEventType(event.EventMessage, d.handleMessage)
|
||||||
|
|
||||||
if err := d.bot.Sync(); err != nil {
|
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,
|
list,
|
||||||
)
|
)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
@ -203,7 +204,7 @@ func (d *MatrixDaemon) CreateRoom(userID string) (roomID id.RoomID, encrypted bo
|
|||||||
func (d *MatrixDaemon) SendStart(userID string) (ok bool) {
|
func (d *MatrixDaemon) SendStart(userID string) (ok bool) {
|
||||||
roomID, encrypted, err := d.CreateRoom(userID)
|
roomID, encrypted, err := d.CreateRoom(userID)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
lang := "en-us"
|
lang := "en-us"
|
||||||
@ -226,7 +227,7 @@ func (d *MatrixDaemon) SendStart(userID string) (ok bool) {
|
|||||||
roomID,
|
roomID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
ok = true
|
ok = true
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
|
//go:build e2ee
|
||||||
// +build e2ee
|
// +build e2ee
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
"maunium.net/go/mautrix"
|
"maunium.net/go/mautrix"
|
||||||
"maunium.net/go/mautrix/crypto"
|
"maunium.net/go/mautrix/crypto"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
@ -65,22 +68,22 @@ type olmLogger struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o olmLogger) Error(message string, args ...interface{}) {
|
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{}) {
|
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{}) {
|
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{}) {
|
func (o olmLogger) Trace(message string, args ...interface{}) {
|
||||||
if strings.HasPrefix(message, "Got membership state event") {
|
if strings.HasPrefix(message, "Got membership state event") {
|
||||||
return
|
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) {
|
func InitMatrixCrypto(d *MatrixDaemon) (err error) {
|
||||||
@ -155,7 +158,7 @@ func HandleSyncerCrypto(startTime int64, d *MatrixDaemon, syncer *mautrix.Defaul
|
|||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
if err != nil {
|
if err != nil {
|
||||||
d.app.err.Printf("Failed to decrypt Matrix message: %v", err)
|
d.app.err.Printf(lm.FailedDecryptMatrixMessage, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
d.handleMessage(source, decrypted)
|
d.handleMessage(source, decrypted)
|
||||||
@ -180,7 +183,7 @@ func EncryptRoom(d *MatrixDaemon, room *mautrix.RespCreateRoom, userID id.UserID
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
encrypted = true
|
encrypted = true
|
||||||
} else {
|
} else {
|
||||||
d.app.debug.Printf("Matrix: Failed to enable encryption in room: %v", err)
|
d.app.debug.Printf(lm.FailedEnableMatrixEncryption, room.RoomID, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
d.isEncrypted[room.RoomID] = encrypted
|
d.isEncrypted[room.RoomID] = encrypted
|
||||||
|
@ -9,6 +9,8 @@ import (
|
|||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// NOTE: This is the one file where log messages are not part of logmessages/logmessages.go
|
||||||
|
|
||||||
func runMigrations(app *appContext) {
|
func runMigrations(app *appContext) {
|
||||||
migrateProfiles(app)
|
migrateProfiles(app)
|
||||||
migrateBootstrap(app)
|
migrateBootstrap(app)
|
||||||
|
34
pwreset.go
34
pwreset.go
@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"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.
|
// 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() {
|
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()
|
path := app.config.Section("password_resets").Key("watch_directory").String()
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
watcher, err := fsnotify.NewWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Printf("Couldn't initialise password reset daemon")
|
app.err.Printf(lm.FailedStartDaemon, "PWR", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer watcher.Close()
|
defer watcher.Close()
|
||||||
@ -56,7 +57,7 @@ func (app *appContext) StartPWR() {
|
|||||||
go pwrMonitor(app, watcher)
|
go pwrMonitor(app, watcher)
|
||||||
err = watcher.Add(path)
|
err = watcher.Add(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Printf("Failed to start password reset daemon: %s", err)
|
app.err.Printf(lm.FailedStartDaemon, "PWR", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
waitForRestart()
|
waitForRestart()
|
||||||
@ -84,43 +85,36 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
|
|||||||
var pwr PasswordReset
|
var pwr PasswordReset
|
||||||
data, err := os.ReadFile(event.Name)
|
data, err := os.ReadFile(event.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.debug.Printf("PWR: Failed to read file: %v", err)
|
app.debug.Printf(lm.FailedReading, event.Name, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(data, &pwr)
|
err = json.Unmarshal(data, &pwr)
|
||||||
if len(pwr.Pin) == 0 || err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
app.info.Printf("New password reset for user \"%s\"", pwr.Username)
|
app.info.Printf("New password reset for user \"%s\"", pwr.Username)
|
||||||
if currentTime := time.Now(); pwr.Expiry.After(currentTime) {
|
if currentTime := time.Now(); pwr.Expiry.After(currentTime) {
|
||||||
user, status, err := app.jf.UserByName(pwr.Username, false)
|
user, status, err := app.jf.UserByName(pwr.Username, false)
|
||||||
if !(status == 200 || status == 204) || err != nil {
|
if !(status == 200 || status == 204) || err != nil || user.ID == "" {
|
||||||
app.err.Printf("Failed to get users from Jellyfin: Code %d", status)
|
app.err.Printf(lm.FailedGetUser, pwr.Username, lm.Jellyfin, err)
|
||||||
app.debug.Printf("Error: %s", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
uid := user.ID
|
uid := user.ID
|
||||||
if uid == "" {
|
|
||||||
app.err.Printf("Couldn't get user ID for user \"%s\"", pwr.Username)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
name := app.getAddressOrName(uid)
|
name := app.getAddressOrName(uid)
|
||||||
if name != "" {
|
if name != "" {
|
||||||
msg, err := app.email.constructReset(pwr, app, false)
|
msg, err := app.email.constructReset(pwr, app, false)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Printf("Failed to construct password reset message for \"%s\"", pwr.Username)
|
app.err.Printf(lm.FailedConstructPWRMessage, pwr.Username, err)
|
||||||
app.debug.Printf("%s: Error: %s", pwr.Username, err)
|
|
||||||
} else if err := app.sendByID(msg, uid); err != nil {
|
} else if err := app.sendByID(msg, uid); err != nil {
|
||||||
app.err.Printf("Failed to send password reset message to \"%s\"", name)
|
app.err.Printf(lm.FailedSendPWRMessage, pwr.Username, name, err)
|
||||||
app.debug.Printf("%s: Error: %s", pwr.Username, err)
|
|
||||||
} else {
|
} else {
|
||||||
app.info.Printf("Sent password reset message to \"%s\"", name)
|
app.err.Printf(lm.SentPWRMessage, pwr.Username, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
app.err.Printf("Password reset daemon: %s", err)
|
app.err.Printf(lm.FailedStartDaemon, "PWR", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
|
)
|
||||||
|
|
||||||
func (app *appContext) HardRestart() error {
|
func (app *appContext) HardRestart() error {
|
||||||
return fmt.Errorf("hard restarts not available on windows")
|
return fmt.Errorf(lm.FailedHardRestartWindows)
|
||||||
}
|
}
|
||||||
|
15
router.go
15
router.go
@ -11,6 +11,7 @@ import (
|
|||||||
"github.com/gin-contrib/pprof"
|
"github.com/gin-contrib/pprof"
|
||||||
"github.com/gin-contrib/static"
|
"github.com/gin-contrib/static"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
swaggerFiles "github.com/swaggo/files"
|
swaggerFiles "github.com/swaggo/files"
|
||||||
ginSwagger "github.com/swaggo/gin-swagger"
|
ginSwagger "github.com/swaggo/gin-swagger"
|
||||||
)
|
)
|
||||||
@ -21,17 +22,17 @@ func (app *appContext) loadHTML(router *gin.Engine) {
|
|||||||
templatePath := "html"
|
templatePath := "html"
|
||||||
htmlFiles, err := fs.ReadDir(localFS, templatePath)
|
htmlFiles, err := fs.ReadDir(localFS, templatePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Fatalf("Couldn't access template directory: \"%s\"", templatePath)
|
app.err.Fatalf(lm.FailedReading, templatePath, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
loadInternal := []string{}
|
loadInternal := []string{}
|
||||||
loadExternal := []string{}
|
loadExternal := []string{}
|
||||||
for _, f := range htmlFiles {
|
for _, f := range htmlFiles {
|
||||||
if _, err := os.Stat(filepath.Join(customPath, f.Name())); os.IsNotExist(err) {
|
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()))
|
loadInternal = append(loadInternal, FSJoin(templatePath, f.Name()))
|
||||||
} else {
|
} 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())))
|
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 {
|
if len(loadInternal) != 0 {
|
||||||
tmpl, err = template.ParseFS(localFS, loadInternal...)
|
tmpl, err = template.ParseFS(localFS, loadInternal...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Fatalf("Failed to load templates: %v", err)
|
app.err.Fatalf(lm.FailedLoadTemplates, lm.Internal, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(loadExternal) != 0 {
|
if len(loadExternal) != 0 {
|
||||||
tmpl, err = tmpl.ParseFiles(loadExternal...)
|
tmpl, err = tmpl.ParseFiles(loadExternal...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Fatalf("Failed to load external templates: %v", err)
|
app.err.Fatalf(lm.FailedLoadTemplates, lm.External, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
router.SetHTMLTemplate(tmpl)
|
router.SetHTMLTemplate(tmpl)
|
||||||
@ -96,7 +97,7 @@ func (app *appContext) loadRouter(address string, debug bool) *gin.Engine {
|
|||||||
router.Use(static.Serve("/", app.webFS))
|
router.Use(static.Serve("/", app.webFS))
|
||||||
router.NoRoute(app.NoRouteHandler)
|
router.NoRoute(app.NoRouteHandler)
|
||||||
if *PPROF {
|
if *PPROF {
|
||||||
app.debug.Println("Loading pprof")
|
app.debug.Println(lm.RegisterPprof)
|
||||||
pprof.Register(router)
|
pprof.Register(router)
|
||||||
}
|
}
|
||||||
SRV = &http.Server{
|
SRV = &http.Server{
|
||||||
@ -165,7 +166,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if *SWAGGER {
|
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 {
|
for _, p := range routePrefixes {
|
||||||
router.GET(p+"/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
router.GET(p+"/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||||
}
|
}
|
||||||
|
3
setup.go
3
setup.go
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/hrfee/jfa-go/easyproxy"
|
"github.com/hrfee/jfa-go/easyproxy"
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
"github.com/hrfee/mediabrowser"
|
"github.com/hrfee/mediabrowser"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -104,7 +105,7 @@ func (app *appContext) TestJF(gc *gin.Context) {
|
|||||||
case 404:
|
case 404:
|
||||||
msg = "error404"
|
msg = "error404"
|
||||||
}
|
}
|
||||||
app.info.Printf("Auth failed with code %d (%s)", status, err)
|
app.err.Printf(lm.FailedAuthJellyfin, req.Server, status, err)
|
||||||
if msg != "" {
|
if msg != "" {
|
||||||
respond(status, msg, gc)
|
respond(status, msg, gc)
|
||||||
} else {
|
} else {
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/hrfee/jfa-go/jellyseerr"
|
"github.com/hrfee/jfa-go/jellyseerr"
|
||||||
"github.com/hrfee/jfa-go/logger"
|
"github.com/hrfee/jfa-go/logger"
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
"github.com/hrfee/mediabrowser"
|
"github.com/hrfee/mediabrowser"
|
||||||
"github.com/timshannon/badgerhold/v4"
|
"github.com/timshannon/badgerhold/v4"
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
@ -175,10 +176,10 @@ func (app *appContext) ConnectDB() {
|
|||||||
opts.ValueDir = app.storage.db_path
|
opts.ValueDir = app.storage.db_path
|
||||||
db, err := badgerhold.Open(opts)
|
db, err := badgerhold.Open(opts)
|
||||||
if err != nil {
|
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.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.
|
// GetEmails returns a copy of the store.
|
||||||
|
13
telegram.go
13
telegram.go
@ -7,6 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
tg "github.com/go-telegram-bot-api/telegram-bot-api"
|
tg "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
"github.com/timshannon/badgerhold/v4"
|
"github.com/timshannon/badgerhold/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -96,12 +97,12 @@ func (t *TelegramDaemon) NewAssignedAuthToken(id string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (t *TelegramDaemon) run() {
|
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 := tg.NewUpdate(0)
|
||||||
u.Timeout = 60
|
u.Timeout = 60
|
||||||
updates, err := t.bot.GetUpdatesChan(u)
|
updates, err := t.bot.GetUpdatesChan(u)
|
||||||
if err != nil {
|
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
|
telegramEnabled = false
|
||||||
return
|
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"})
|
content += t.app.storage.lang.Telegram[lang].Strings.template("languageMessage", tmpl{"command": "/lang"})
|
||||||
err := t.Reply(upd, content)
|
err := t.Reply(upd, content)
|
||||||
if err != nil {
|
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)
|
err := t.Reply(upd, list)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
@ -232,14 +233,14 @@ func (t *TelegramDaemon) commandPIN(upd *tg.Update, sects []string, lang string)
|
|||||||
if !ok || time.Now().After(token.Expiry) {
|
if !ok || time.Now().After(token.Expiry) {
|
||||||
err := t.QuoteReply(upd, t.app.storage.lang.Telegram[lang].Strings.get("invalidPIN"))
|
err := t.QuoteReply(upd, t.app.storage.lang.Telegram[lang].Strings.get("invalidPIN"))
|
||||||
if err != nil {
|
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)
|
delete(t.tokens, upd.Message.Text)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := t.QuoteReply(upd, t.app.storage.lang.Telegram[lang].Strings.get("pinSuccess"))
|
err := t.QuoteReply(upd, t.app.storage.lang.Telegram[lang].Strings.get("pinSuccess"))
|
||||||
if err != nil {
|
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{
|
t.verifiedTokens[upd.Message.Text] = TelegramVerifiedToken{
|
||||||
ChatID: upd.Message.Chat.ID,
|
ChatID: upd.Message.Chat.ID,
|
||||||
|
@ -16,6 +16,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
|
|
||||||
"github.com/hrfee/jfa-go/common"
|
"github.com/hrfee/jfa-go/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -560,15 +562,16 @@ func (app *appContext) checkForUpdates() {
|
|||||||
if err != nil && strings.Contains(err.Error(), "strconv.ParseInt") {
|
if err != nil && strings.Contains(err.Error(), "strconv.ParseInt") {
|
||||||
app.err.Println("No new updates available.")
|
app.err.Println("No new updates available.")
|
||||||
} else if status != -1 { // -1 means updates disabled, we don't need to log it.
|
} 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
|
return
|
||||||
}
|
}
|
||||||
if tag != app.tag && tag.IsNew() {
|
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)
|
update, status, err := app.updater.GetUpdate(tag)
|
||||||
if status != 200 || err != nil {
|
if status != 200 || err != nil {
|
||||||
app.err.Printf("Failed to get update (%d): %v", status, err)
|
app.err.Printf(lm.FailedGetUpdate, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
app.tag = tag
|
app.tag = tag
|
||||||
|
18
user-auth.go
18
user-auth.go
@ -1,9 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (app *appContext) userAuth() gin.HandlerFunc {
|
func (app *appContext) userAuth() gin.HandlerFunc {
|
||||||
@ -13,7 +15,7 @@ func (app *appContext) userAuth() gin.HandlerFunc {
|
|||||||
func (app *appContext) userAuthenticate(gc *gin.Context) {
|
func (app *appContext) userAuthenticate(gc *gin.Context) {
|
||||||
jellyfinLogin := app.config.Section("ui").Key("jellyfin_login").MustBool(true)
|
jellyfinLogin := app.config.Section("ui").Key("jellyfin_login").MustBool(true)
|
||||||
if !jellyfinLogin {
|
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)
|
respond(500, "Contact Admin", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -27,7 +29,6 @@ func (app *appContext) userAuthenticate(gc *gin.Context) {
|
|||||||
|
|
||||||
gc.Set("jfId", jfID)
|
gc.Set("jfId", jfID)
|
||||||
gc.Set("userMode", true)
|
gc.Set("userMode", true)
|
||||||
app.debug.Println("Auth succeeded")
|
|
||||||
gc.Next()
|
gc.Next()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,11 +42,11 @@ func (app *appContext) userAuthenticate(gc *gin.Context) {
|
|||||||
// @Security getUserTokenAuth
|
// @Security getUserTokenAuth
|
||||||
func (app *appContext) getUserTokenLogin(gc *gin.Context) {
|
func (app *appContext) getUserTokenLogin(gc *gin.Context) {
|
||||||
if !app.config.Section("ui").Key("jellyfin_login").MustBool(true) {
|
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)
|
respond(500, "Contact Admin", gc)
|
||||||
return
|
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)
|
username, password, ok := app.decodeValidateLoginHeader(gc, true)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
@ -58,12 +59,11 @@ func (app *appContext) getUserTokenLogin(gc *gin.Context) {
|
|||||||
|
|
||||||
token, refresh, err := CreateToken(user.ID, user.ID, false)
|
token, refresh, err := CreateToken(user.ID, user.ID, false)
|
||||||
if err != nil {
|
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)
|
respond(500, "Couldn't generate user token", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
app.debug.Printf("Token generated for non-admin user \"%s\"", username)
|
|
||||||
uri := "/my"
|
uri := "/my"
|
||||||
if strings.HasPrefix(gc.Request.RequestURI, app.URLBase) {
|
if strings.HasPrefix(gc.Request.RequestURI, app.URLBase) {
|
||||||
uri = "/accounts/my"
|
uri = "/accounts/my"
|
||||||
@ -81,12 +81,12 @@ func (app *appContext) getUserTokenLogin(gc *gin.Context) {
|
|||||||
func (app *appContext) getUserTokenRefresh(gc *gin.Context) {
|
func (app *appContext) getUserTokenRefresh(gc *gin.Context) {
|
||||||
jellyfinLogin := app.config.Section("ui").Key("jellyfin_login").MustBool(true)
|
jellyfinLogin := app.config.Section("ui").Key("jellyfin_login").MustBool(true)
|
||||||
if !jellyfinLogin {
|
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)
|
respond(500, "Contact Admin", gc)
|
||||||
return
|
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")
|
claims, ok := app.decodeValidateRefreshCookie(gc, "user-refresh")
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
@ -96,7 +96,7 @@ func (app *appContext) getUserTokenRefresh(gc *gin.Context) {
|
|||||||
|
|
||||||
jwt, refresh, err := CreateToken(jfID, jfID, false)
|
jwt, refresh, err := CreateToken(jfID, jfID, false)
|
||||||
if err != nil {
|
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)
|
respond(500, "Couldn't generate user token", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
22
user-d.go
22
user-d.go
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
"github.com/hrfee/mediabrowser"
|
"github.com/hrfee/mediabrowser"
|
||||||
"github.com/lithammer/shortuuid/v3"
|
"github.com/lithammer/shortuuid/v3"
|
||||||
)
|
)
|
||||||
@ -21,17 +22,17 @@ func (app *appContext) checkUsers() {
|
|||||||
if len(app.storage.GetUserExpiries()) == 0 {
|
if len(app.storage.GetUserExpiries()) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
app.info.Println("Daemon: Checking for user expiry")
|
app.info.Println(lm.CheckUserExpiries)
|
||||||
users, status, err := app.jf.GetUsers(false)
|
users, status, err := app.jf.GetUsers(false)
|
||||||
if err != nil || status != 200 {
|
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
|
return
|
||||||
}
|
}
|
||||||
mode := "disable"
|
mode := "disable"
|
||||||
term := "Disabling"
|
phrase := lm.DisableExpiredUser
|
||||||
if app.config.Section("user_expiry").Key("behaviour").MustString("disable_user") == "delete_user" {
|
if app.config.Section("user_expiry").Key("behaviour").MustString("disable_user") == "delete_user" {
|
||||||
mode = "delete"
|
mode = "delete"
|
||||||
term = "Deleting"
|
phrase = lm.DeleteExpiredUser
|
||||||
}
|
}
|
||||||
contact := false
|
contact := false
|
||||||
if messagesEnabled && app.config.Section("user_expiry").Key("send_email").MustBool(true) {
|
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() {
|
for _, expiry := range app.storage.GetUserExpiries() {
|
||||||
id := expiry.JellyfinID
|
id := expiry.JellyfinID
|
||||||
if _, ok := userExists[id]; !ok {
|
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)
|
app.storage.DeleteUserExpiryKey(expiry.JellyfinID)
|
||||||
} else if time.Now().After(expiry.Expiry) {
|
} else if time.Now().After(expiry.Expiry) {
|
||||||
found := false
|
found := false
|
||||||
@ -58,11 +59,10 @@ func (app *appContext) checkUsers() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
if !found {
|
||||||
app.info.Printf("Expired user already deleted, ignoring.")
|
|
||||||
app.storage.DeleteUserExpiryKey(expiry.JellyfinID)
|
app.storage.DeleteUserExpiryKey(expiry.JellyfinID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
app.info.Printf("%s expired user \"%s\"", term, user.Name)
|
app.info.Printf(phrase, user.Name)
|
||||||
|
|
||||||
// Record activity
|
// Record activity
|
||||||
activity := Activity{
|
activity := Activity{
|
||||||
@ -83,7 +83,7 @@ func (app *appContext) checkUsers() {
|
|||||||
activity.Type = ActivityDisabled
|
activity.Type = ActivityDisabled
|
||||||
}
|
}
|
||||||
if !(status == 200 || status == 204) || err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,11 +98,11 @@ func (app *appContext) checkUsers() {
|
|||||||
name := app.getAddressOrName(user.ID)
|
name := app.getAddressOrName(user.ID)
|
||||||
msg, err := app.email.constructUserExpired(app, false)
|
msg, err := app.email.constructUserExpired(app, false)
|
||||||
if err != nil {
|
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 {
|
} 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 {
|
} else {
|
||||||
app.info.Printf("Sent expiry notification to \"%s\"", name)
|
app.err.Printf(lm.SentExpiryMessage, user.ID, name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
60
views.go
60
views.go
@ -4,6 +4,7 @@ import (
|
|||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
@ -16,6 +17,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
"github.com/gomarkdown/markdown"
|
"github.com/gomarkdown/markdown"
|
||||||
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
"github.com/hrfee/mediabrowser"
|
"github.com/hrfee/mediabrowser"
|
||||||
"github.com/lithammer/shortuuid/v3"
|
"github.com/lithammer/shortuuid/v3"
|
||||||
"github.com/steambap/captcha"
|
"github.com/steambap/captcha"
|
||||||
@ -66,10 +68,9 @@ func (app *appContext) pushResources(gc *gin.Context, page Page) {
|
|||||||
toPush = []string{}
|
toPush = []string{}
|
||||||
}
|
}
|
||||||
if pusher := gc.Writer.Pusher(); pusher != nil {
|
if pusher := gc.Writer.Pusher(); pusher != nil {
|
||||||
app.debug.Println("Using HTTP2 Server push")
|
|
||||||
for _, f := range toPush {
|
for _, f := range toPush {
|
||||||
if err := pusher.Push(app.URLBase+f, nil); err != nil {
|
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
|
var license string
|
||||||
l, err := fs.ReadFile(localFS, "LICENSE")
|
l, err := fs.ReadFile(localFS, "LICENSE")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.debug.Printf("Failed to load LICENSE: %s", err)
|
app.debug.Printf(lm.FailedReading, "LICENSE", err)
|
||||||
license = ""
|
license = ""
|
||||||
}
|
}
|
||||||
license = string(l)
|
license = string(l)
|
||||||
fontLicense, err := fs.ReadFile(localFS, filepath.Join("web", "fonts", "OFL.txt"))
|
fontLicense, err := fs.ReadFile(localFS, filepath.Join("web", "fonts", "OFL.txt"))
|
||||||
if err != nil {
|
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"
|
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)
|
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 it's a bot, pretend to be a success so the preview is nice.
|
||||||
if isBot {
|
if isBot {
|
||||||
app.debug.Println("PWR: Ignoring magic link visit from bot")
|
app.debug.Println(lm.IgnoreBotPWR)
|
||||||
data["success"] = true
|
data["success"] = true
|
||||||
data["pin"] = "NO-BO-TS"
|
data["pin"] = "NO-BO-TS"
|
||||||
return
|
return
|
||||||
@ -338,13 +339,13 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
|
|||||||
if !isInternal && !setPassword {
|
if !isInternal && !setPassword {
|
||||||
resp, status, err = app.jf.ResetPassword(pin)
|
resp, status, err = app.jf.ResetPassword(pin)
|
||||||
} else if time.Now().After(pwr.Expiry) {
|
} 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)
|
app.NoRouteHandler(gc)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
status, err = app.jf.ResetPasswordAdmin(pwr.ID)
|
status, err = app.jf.ResetPasswordAdmin(pwr.ID)
|
||||||
if !(status == 200 || status == 204) || err != nil {
|
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 {
|
} else {
|
||||||
status, err = app.jf.SetPassword(pwr.ID, "", pin)
|
status, err = app.jf.SetPassword(pwr.ID, "", pin)
|
||||||
}
|
}
|
||||||
@ -358,7 +359,7 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
|
|||||||
username = resp.UsersReset[0]
|
username = resp.UsersReset[0]
|
||||||
}
|
}
|
||||||
} else {
|
} 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.
|
// 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) {
|
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
||||||
jfUser, status, err := app.jf.UserByName(username, false)
|
jfUser, status, err := app.jf.UserByName(username, false)
|
||||||
if status != 200 || err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
ombiUser, status, err := app.getOmbiUser(jfUser.ID)
|
ombiUser, status, err := app.getOmbiUser(jfUser.ID)
|
||||||
if status != 200 || err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
ombiUser["password"] = pin
|
ombiUser["password"] = pin
|
||||||
status, err = app.ombi.ModifyUser(ombiUser)
|
status, err = app.ombi.ModifyUser(ombiUser)
|
||||||
if status != 200 || err != nil {
|
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
|
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)
|
capt, err := captcha.New(300, 100)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Printf("Failed to generate captcha: %v", err)
|
app.err.Printf(lm.FailedGenerateCaptcha, err)
|
||||||
respondBool(500, false, gc)
|
respondBool(500, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -470,7 +471,7 @@ func (app *appContext) GenCaptcha(gc *gin.Context) {
|
|||||||
captchaID := genAuthToken()
|
captchaID := genAuthToken()
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
if err := capt.WriteImage(bufio.NewWriter(&buf)); err != nil {
|
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)
|
respondBool(500, false, gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -503,8 +504,12 @@ func (app *appContext) verifyCaptcha(code, id, text string, isPWR bool) bool {
|
|||||||
ok := true
|
ok := true
|
||||||
if !isPWR {
|
if !isPWR {
|
||||||
inv, ok := app.storage.GetInvitesKey(code)
|
inv, ok := app.storage.GetInvitesKey(code)
|
||||||
if !ok || (!isPWR && inv.Captchas == nil) {
|
if !ok {
|
||||||
app.debug.Printf("Couldn't find invite \"%s\"", code)
|
app.debug.Printf(lm.InvalidInviteCode, code)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !isPWR && inv.Captchas == nil {
|
||||||
|
app.debug.Printf(lm.CaptchaNotFound, id, code)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
c, ok = inv.Captchas[id]
|
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]
|
c, ok = app.pwrCaptchas[code]
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
app.debug.Printf("Couldn't find Captcha \"%s\"", id)
|
app.debug.Printf(lm.CaptchaNotFound, id, code)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return strings.ToLower(c.Answer) == strings.ToLower(text)
|
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")
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
resp, err := http.DefaultClient.Do(req)
|
resp, err := http.DefaultClient.Do(req)
|
||||||
if err != nil || resp.StatusCode != 200 {
|
if err == nil && resp.StatusCode != 200 {
|
||||||
app.err.Printf("Failed to read reCAPTCHA status (%d): %+v\n", resp.Status, err)
|
err = fmt.Errorf("failed (error %d)", resp.StatusCode)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf(lm.FailedVerifyReCAPTCHA, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
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)
|
body, err := io.ReadAll(resp.Body)
|
||||||
err = json.Unmarshal(body, &data)
|
err = json.Unmarshal(body, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Printf("Failed to unmarshal reCAPTCHA response: %+v\n", err)
|
app.err.Printf(lm.FailedVerifyReCAPTCHA, err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
hostname := app.config.Section("captcha").Key("recaptcha_hostname").MustString("")
|
hostname := app.config.Section("captcha").Key("recaptcha_hostname").MustString("")
|
||||||
if strings.ToLower(data.Hostname) != strings.ToLower(hostname) && data.Hostname != "" {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(data.ErrorCodes) > 0 {
|
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
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,20 +660,19 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
token, err := jwt.Parse(key, checkToken)
|
token, err := jwt.Parse(key, checkToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fail()
|
fail()
|
||||||
app.err.Printf("Failed to parse key: %s", err)
|
app.debug.Printf(lm.FailedParseJWT, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
claims, ok := token.Claims.(jwt.MapClaims)
|
claims, ok := token.Claims.(jwt.MapClaims)
|
||||||
expiry := time.Unix(int64(claims["exp"].(float64)), 0)
|
expiry := time.Unix(int64(claims["exp"].(float64)), 0)
|
||||||
if !(ok && token.Valid && claims["invite"].(string) == code && claims["type"].(string) == "confirmation" && expiry.After(time.Now())) {
|
if !(ok && token.Valid && claims["invite"].(string) == code && claims["type"].(string) == "confirmation" && expiry.After(time.Now())) {
|
||||||
fail()
|
fail()
|
||||||
app.debug.Printf("Invalid key")
|
app.debug.Printf(lm.InvalidJWT)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f, success := app.newUser(req, true, gc)
|
f, success := app.newUser(req, true, gc)
|
||||||
if !success {
|
if !success {
|
||||||
app.err.Printf("Failed to create new user")
|
// Not meant for us. Calling this is bad but at least gives us log output.
|
||||||
// Not meant for us. Calling this will be a mess, but at least it might give us some information.
|
|
||||||
f(gc)
|
f(gc)
|
||||||
fail()
|
fail()
|
||||||
return
|
return
|
||||||
|
Loading…
Reference in New Issue
Block a user