diff --git a/api.go b/api.go
index 66e6896..f8067a4 100644
--- a/api.go
+++ b/api.go
@@ -105,12 +105,12 @@ func timeDiff(a, b time.Time) (year, month, day, hour, min, sec int) {
}
func (app *appContext) checkInvites() {
- current_time := time.Now()
+ currentTime := time.Now()
app.storage.loadInvites()
changed := false
for code, data := range app.storage.invites {
expiry := data.ValidTill
- if !current_time.After(expiry) {
+ if !currentTime.After(expiry) {
continue
}
app.debug.Printf("Housekeeping: Deleting old invite %s", code)
@@ -144,7 +144,7 @@ func (app *appContext) checkInvites() {
}
func (app *appContext) checkInvite(code string, used bool, username string) bool {
- current_time := time.Now()
+ currentTime := time.Now()
app.storage.loadInvites()
changed := false
inv, match := app.storage.invites[code]
@@ -152,7 +152,7 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
return false
}
expiry := inv.ValidTill
- if current_time.After(expiry) {
+ if currentTime.After(expiry) {
app.debug.Printf("Housekeeping: Deleting old invite %s", code)
notify := inv.Notify
if app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 {
@@ -188,7 +188,7 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
// 0 means infinite i guess?
newInv.RemainingUses -= 1
}
- newInv.UsedBy = append(newInv.UsedBy, []string{username, app.formatDatetime(current_time)})
+ newInv.UsedBy = append(newInv.UsedBy, []string{username, app.formatDatetime(currentTime)})
if !del {
app.storage.invites[code] = newInv
}
@@ -256,15 +256,17 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) {
}
if len(app.storage.policy) != 0 {
status, err = app.jf.SetPolicy(id, app.storage.policy)
- if !(status == 200 || status == 204) {
+ if !(status == 200 || status == 204 || err == nil) {
app.err.Printf("%s: Failed to set user policy: Code %d", req.Username, status)
+ app.debug.Printf("%s: Error: %s", req.Username, err)
}
}
if len(app.storage.configuration) != 0 && len(app.storage.displayprefs) != 0 {
status, err = app.jf.SetConfiguration(id, app.storage.configuration)
if (status == 200 || status == 204) && err == nil {
status, err = app.jf.SetDisplayPreferences(id, app.storage.displayprefs)
- } else {
+ }
+ if !((status == 200 || status == 204) && err == nil) {
app.err.Printf("%s: Failed to set configuration template: Code %d", req.Username, status)
}
}
@@ -364,8 +366,9 @@ func (app *appContext) NewUser(gc *gin.Context) {
if len(profile.Policy) != 0 {
app.debug.Printf("Applying policy from profile \"%s\"", invite.Profile)
status, err = app.jf.SetPolicy(id, profile.Policy)
- if !(status == 200 || status == 204) {
+ if !((status == 200 || status == 204) && err == nil) {
app.err.Printf("%s: Failed to set user policy: Code %d", req.Code, status)
+ app.debug.Printf("%s: Error: %s", req.Code, err)
}
}
if len(profile.Configuration) != 0 && len(profile.Displayprefs) != 0 {
@@ -373,8 +376,10 @@ func (app *appContext) NewUser(gc *gin.Context) {
status, err = app.jf.SetConfiguration(id, profile.Configuration)
if (status == 200 || status == 204) && err == nil {
status, err = app.jf.SetDisplayPreferences(id, profile.Displayprefs)
- } else {
+ }
+ if !((status == 200 || status == 204) && err == nil) {
app.err.Printf("%s: Failed to set configuration template: Code %d", req.Code, status)
+ app.debug.Printf("%s: Error: %s", req.Code, err)
}
}
}
@@ -482,18 +487,18 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
app.debug.Println("Generating new invite")
app.storage.loadInvites()
gc.BindJSON(&req)
- current_time := time.Now()
- valid_till := current_time.AddDate(0, 0, req.Days)
- valid_till = valid_till.Add(time.Hour*time.Duration(req.Hours) + time.Minute*time.Duration(req.Minutes))
+ currentTime := time.Now()
+ validTill := currentTime.AddDate(0, 0, req.Days)
+ validTill = validTill.Add(time.Hour*time.Duration(req.Hours) + time.Minute*time.Duration(req.Minutes))
// make sure code doesn't begin with number
- invite_code := shortuuid.New()
- _, err := strconv.Atoi(string(invite_code[0]))
+ inviteCode := shortuuid.New()
+ _, err := strconv.Atoi(string(inviteCode[0]))
for err == nil {
- invite_code = shortuuid.New()
- _, err = strconv.Atoi(string(invite_code[0]))
+ inviteCode = shortuuid.New()
+ _, err = strconv.Atoi(string(inviteCode[0]))
}
var invite Invite
- invite.Created = current_time
+ invite.Created = currentTime
if req.MultipleUses {
if req.NoLimit {
invite.NoLimit = true
@@ -503,21 +508,21 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
} else {
invite.RemainingUses = 1
}
- invite.ValidTill = valid_till
+ invite.ValidTill = validTill
if req.Email != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) {
- app.debug.Printf("%s: Sending invite email", invite_code)
+ app.debug.Printf("%s: Sending invite email", inviteCode)
invite.Email = req.Email
- msg, err := app.email.constructInvite(invite_code, invite, app)
+ msg, err := app.email.constructInvite(inviteCode, invite, app)
if err != nil {
invite.Email = fmt.Sprintf("Failed to send to %s", req.Email)
- app.err.Printf("%s: Failed to construct invite email", invite_code)
- app.debug.Printf("%s: Error: %s", invite_code, err)
+ app.err.Printf("%s: Failed to construct invite email", inviteCode)
+ app.debug.Printf("%s: Error: %s", inviteCode, err)
} else if err := app.email.send(req.Email, msg); err != nil {
invite.Email = fmt.Sprintf("Failed to send to %s", req.Email)
- app.err.Printf("%s: %s", invite_code, invite.Email)
- app.debug.Printf("%s: Error: %s", invite_code, err)
+ app.err.Printf("%s: %s", inviteCode, invite.Email)
+ app.debug.Printf("%s: Error: %s", inviteCode, err)
} else {
- app.info.Printf("%s: Sent invite email to %s", invite_code, req.Email)
+ app.info.Printf("%s: Sent invite email to %s", inviteCode, req.Email)
}
}
if req.Profile != "" {
@@ -527,7 +532,7 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
invite.Profile = "Default"
}
}
- app.storage.invites[invite_code] = invite
+ app.storage.invites[inviteCode] = invite
app.storage.storeInvites()
respondBool(200, true, gc)
}
@@ -972,7 +977,7 @@ func (app *appContext) ModifyEmails(gc *gin.Context) {
// @Produce json
// @Param userSettingsDTO body userSettingsDTO true "Parameters for applying settings"
// @Success 200 {object} errorListDTO
-// @Failure 500 {object} errorListDTO "Lists of errors that occured while applying settings"
+// @Failure 500 {object} errorListDTO "Lists of errors that occurred while applying settings"
// @Router /users/settings [post]
// @Security Bearer
// @tags Profiles & Settings
@@ -1063,7 +1068,7 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
func (app *appContext) GetConfig(gc *gin.Context) {
app.info.Println("Config requested")
resp := map[string]interface{}{}
- langPath := filepath.Join(app.local_path, "lang", "form")
+ langPath := filepath.Join(app.localPath, "lang", "form")
app.lang.langFiles, _ = ioutil.ReadDir(langPath)
app.lang.langOptions = make([]string, len(app.lang.langFiles))
chosenLang := app.config.Section("ui").Key("language").MustString("en-us") + ".json"
@@ -1124,7 +1129,7 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
app.info.Println("Config modification requested")
var req configDTO
gc.BindJSON(&req)
- tempConfig, _ := ini.Load(app.config_path)
+ tempConfig, _ := ini.Load(app.configPath)
for section, settings := range req {
if section != "restart-program" {
_, err := tempConfig.GetSection(section)
@@ -1145,7 +1150,7 @@ func (app *appContext) ModifyConfig(gc *gin.Context) {
}
}
}
- tempConfig.SaveTo(app.config_path)
+ tempConfig.SaveTo(app.configPath)
app.debug.Println("Config saved")
gc.JSON(200, map[string]bool{"success": true})
if req["restart-program"] != nil && req["restart-program"].(bool) {
diff --git a/config.go b/config.go
index 93f3acd..71e1724 100644
--- a/config.go
+++ b/config.go
@@ -4,6 +4,7 @@ import (
"fmt"
"path/filepath"
"strconv"
+ "strings"
"gopkg.in/ini.v1"
)
@@ -36,7 +37,7 @@ func (app *appContext) loadDefaults() (err error) {
func (app *appContext) loadConfig() error {
var err error
- app.config, err = ini.Load(app.config_path)
+ app.config, err = ini.Load(app.configPath)
if err != nil {
return err
}
@@ -48,32 +49,32 @@ func (app *appContext) loadConfig() error {
// key.SetValue(filepath.Join(app.data_path, (key.Name() + ".json")))
// }
if key.Name() != "html_templates" {
- key.SetValue(key.MustString(filepath.Join(app.data_path, (key.Name() + ".json"))))
+ key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json"))))
}
}
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template"} {
// if app.config.Section("files").Key(key).MustString("") == "" {
// key.SetValue(filepath.Join(app.data_path, (key.Name() + ".json")))
// }
- app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.data_path, (key + ".json"))))
+ app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".json"))))
}
-
+ app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/")
app.config.Section("email").Key("no_username").SetValue(strconv.FormatBool(app.config.Section("email").Key("no_username").MustBool(false)))
- app.config.Section("password_resets").Key("email_html").SetValue(app.config.Section("password_resets").Key("email_html").MustString(filepath.Join(app.local_path, "email.html")))
- app.config.Section("password_resets").Key("email_text").SetValue(app.config.Section("password_resets").Key("email_text").MustString(filepath.Join(app.local_path, "email.txt")))
+ app.config.Section("password_resets").Key("email_html").SetValue(app.config.Section("password_resets").Key("email_html").MustString(filepath.Join(app.localPath, "email.html")))
+ app.config.Section("password_resets").Key("email_text").SetValue(app.config.Section("password_resets").Key("email_text").MustString(filepath.Join(app.localPath, "email.txt")))
- app.config.Section("invite_emails").Key("email_html").SetValue(app.config.Section("invite_emails").Key("email_html").MustString(filepath.Join(app.local_path, "invite-email.html")))
- app.config.Section("invite_emails").Key("email_text").SetValue(app.config.Section("invite_emails").Key("email_text").MustString(filepath.Join(app.local_path, "invite-email.txt")))
+ app.config.Section("invite_emails").Key("email_html").SetValue(app.config.Section("invite_emails").Key("email_html").MustString(filepath.Join(app.localPath, "invite-email.html")))
+ app.config.Section("invite_emails").Key("email_text").SetValue(app.config.Section("invite_emails").Key("email_text").MustString(filepath.Join(app.localPath, "invite-email.txt")))
- app.config.Section("notifications").Key("expiry_html").SetValue(app.config.Section("notifications").Key("expiry_html").MustString(filepath.Join(app.local_path, "expired.html")))
- app.config.Section("notifications").Key("expiry_text").SetValue(app.config.Section("notifications").Key("expiry_text").MustString(filepath.Join(app.local_path, "expired.txt")))
+ app.config.Section("notifications").Key("expiry_html").SetValue(app.config.Section("notifications").Key("expiry_html").MustString(filepath.Join(app.localPath, "expired.html")))
+ app.config.Section("notifications").Key("expiry_text").SetValue(app.config.Section("notifications").Key("expiry_text").MustString(filepath.Join(app.localPath, "expired.txt")))
- app.config.Section("notifications").Key("created_html").SetValue(app.config.Section("notifications").Key("created_html").MustString(filepath.Join(app.local_path, "created.html")))
- app.config.Section("notifications").Key("created_text").SetValue(app.config.Section("notifications").Key("created_text").MustString(filepath.Join(app.local_path, "created.txt")))
+ app.config.Section("notifications").Key("created_html").SetValue(app.config.Section("notifications").Key("created_html").MustString(filepath.Join(app.localPath, "created.html")))
+ app.config.Section("notifications").Key("created_text").SetValue(app.config.Section("notifications").Key("created_text").MustString(filepath.Join(app.localPath, "created.txt")))
- app.config.Section("deletion").Key("email_html").SetValue(app.config.Section("deletion").Key("email_html").MustString(filepath.Join(app.local_path, "deleted.html")))
- app.config.Section("deletion").Key("email_text").SetValue(app.config.Section("deletion").Key("email_text").MustString(filepath.Join(app.local_path, "deleted.txt")))
+ app.config.Section("deletion").Key("email_html").SetValue(app.config.Section("deletion").Key("email_html").MustString(filepath.Join(app.localPath, "deleted.html")))
+ app.config.Section("deletion").Key("email_text").SetValue(app.config.Section("deletion").Key("email_text").MustString(filepath.Join(app.localPath, "deleted.txt")))
app.config.Section("jellyfin").Key("version").SetValue(VERSION)
app.config.Section("jellyfin").Key("device").SetValue("jfa-go")
diff --git a/config/config-base.json b/config/config-base.json
index 67508a0..21e95a2 100644
--- a/config/config-base.json
+++ b/config/config-base.json
@@ -179,6 +179,14 @@
"type": "bool",
"value": false,
"description": "Use the Bootstrap 5 Alpha. Looks better and removes the need for jQuery, so the page should load faster."
+ },
+ "url_base": {
+ "name": "URL Base",
+ "required": false,
+ "requires_restart": true,
+ "type": "text",
+ "value": "",
+ "description": "URL base for when running jfa-go with a reverse proxy in a subfolder."
}
},
"password_validation": {
diff --git a/daemon.go b/daemon.go
index a8827e1..51299dc 100644
--- a/daemon.go
+++ b/daemon.go
@@ -4,7 +4,7 @@ import "time"
// https://bbengfort.github.io/snippets/2016/06/26/background-work-goroutines-timer.html THANKS
-type Repeater struct {
+type repeater struct {
Stopped bool
ShutdownChannel chan string
Interval time.Duration
@@ -12,8 +12,8 @@ type Repeater struct {
app *appContext
}
-func NewRepeater(interval time.Duration, app *appContext) *Repeater {
- return &Repeater{
+func newRepeater(interval time.Duration, app *appContext) *repeater {
+ return &repeater{
Stopped: false,
ShutdownChannel: make(chan string),
Interval: interval,
@@ -22,7 +22,7 @@ func NewRepeater(interval time.Duration, app *appContext) *Repeater {
}
}
-func (rt *Repeater) Run() {
+func (rt *repeater) run() {
rt.app.info.Println("Invite daemon started")
for {
select {
@@ -42,7 +42,7 @@ func (rt *Repeater) Run() {
}
}
-func (rt *Repeater) Shutdown() {
+func (rt *repeater) shutdown() {
rt.Stopped = true
rt.ShutdownChannel <- "Down"
<-rt.ShutdownChannel
diff --git a/data/templates/admin.html b/data/templates/admin.html
index d40b4f6..3e8b5fb 100644
--- a/data/templates/admin.html
+++ b/data/templates/admin.html
@@ -5,11 +5,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -30,6 +30,7 @@
}
return "";
}
+ window.URLBase = "{{ .urlBase }}";
{{ if .bs5 }}
window.bsVersion = 5;
{{ else }}
diff --git a/data/templates/form-base.html b/data/templates/form-base.html
index 1c8bbf0..5f1aa62 100644
--- a/data/templates/form-base.html
+++ b/data/templates/form-base.html
@@ -4,6 +4,7 @@
window.usernameEnabled = {{ .settings.username }};
window.validationStrings = JSON.parse({{ .lang.validationStrings }});
window.invalidPassword = "{{ .lang.reEnterPasswordInvalid }}";
+ window.URLBase = "{{ .urlBase }}";
{{ end }}
diff --git a/data/templates/form.html b/data/templates/form.html
index cd16cc6..6982736 100644
--- a/data/templates/form.html
+++ b/data/templates/form.html
@@ -4,11 +4,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/email.go b/email.go
index 45c7b16..10bd6c0 100644
--- a/email.go
+++ b/email.go
@@ -252,7 +252,7 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite
return email, nil
}
-func (emailer *Emailer) constructReset(pwr Pwr, app *appContext) (*Email, error) {
+func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext) (*Email, error) {
email := &Email{
subject: app.config.Section("password_resets").Key("subject").MustString("Password reset - Jellyfin"),
}
diff --git a/main.go b/main.go
index f53c1ad..78da16a 100644
--- a/main.go
+++ b/main.go
@@ -34,7 +34,7 @@ import (
"gopkg.in/ini.v1"
)
-// Username is JWT!
+// User is used for auth purposes.
type User struct {
UserID string `json:"id"`
Username string `json:"username"`
@@ -44,11 +44,11 @@ type User struct {
type appContext struct {
// defaults *Config
config *ini.File
- config_path string
- configBase_path string
+ configPath string
+ configBasePath string
configBase map[string]interface{}
- data_path string
- local_path string
+ dataPath string
+ localPath string
cssFile string
bsVersion int
jellyfinLogin bool
@@ -68,8 +68,10 @@ type appContext struct {
version string
quit chan os.Signal
lang Languages
+ URLBase string
}
+// Languages stores the names and filenames of language files, and the index of that which is currently selected.
type Languages struct {
langFiles []os.FileInfo // Language filenames
langOptions []string // Language names
@@ -78,10 +80,10 @@ type Languages struct {
func (app *appContext) loadHTML(router *gin.Engine) {
customPath := app.config.Section("files").Key("html_templates").MustString("")
- templatePath := filepath.Join(app.local_path, "templates")
+ templatePath := filepath.Join(app.localPath, "templates")
htmlFiles, err := ioutil.ReadDir(templatePath)
if err != nil {
- app.err.Fatalf("Couldn't access template directory: \"%s\"", filepath.Join(app.local_path, "templates"))
+ app.err.Fatalf("Couldn't access template directory: \"%s\"", filepath.Join(app.localPath, "templates"))
return
}
loadFiles := make([]string, len(htmlFiles))
@@ -97,7 +99,7 @@ func (app *appContext) loadHTML(router *gin.Engine) {
router.LoadHTMLFiles(loadFiles...)
}
-func GenerateSecret(length int) (string, error) {
+func generateSecret(length int) (string, error) {
bytes := make([]byte, length)
_, err := rand.Read(bytes)
if err != nil {
@@ -173,7 +175,7 @@ func test(app *appContext) {
fmt.Scanln(&username)
user, status, err := app.jf.UserByName(username, false)
fmt.Printf("UserByName (%s): code %d err %s", username, status, err)
- out, err := json.MarshalIndent(user, "", " ")
+ out, _ := json.MarshalIndent(user, "", " ")
fmt.Print(string(out))
}
@@ -187,17 +189,17 @@ func start(asDaemon, firstCall bool) {
local_path is the internal 'data' directory.
*/
userConfigDir, _ := os.UserConfigDir()
- app.data_path = filepath.Join(userConfigDir, "jfa-go")
- app.config_path = filepath.Join(app.data_path, "config.ini")
+ app.dataPath = filepath.Join(userConfigDir, "jfa-go")
+ app.configPath = filepath.Join(app.dataPath, "config.ini")
executable, _ := os.Executable()
- app.local_path = filepath.Join(filepath.Dir(executable), "data")
+ app.localPath = filepath.Join(filepath.Dir(executable), "data")
app.info = log.New(os.Stdout, "[INFO] ", log.Ltime)
app.err = log.New(os.Stdout, "[ERROR] ", log.Ltime|log.Lshortfile)
if firstCall {
- DATA = flag.String("data", app.data_path, "alternate path to data directory.")
- CONFIG = flag.String("config", app.config_path, "alternate path to config file.")
+ DATA = flag.String("data", app.dataPath, "alternate path to data directory.")
+ CONFIG = flag.String("config", app.configPath, "alternate path to config file.")
HOST = flag.String("host", "", "alternate address to host web ui on.")
PORT = flag.Int("port", 0, "alternate port to host web ui on.")
DEBUG = flag.Bool("debug", false, "Enables debug logging and exposes pprof.")
@@ -219,35 +221,35 @@ func start(asDaemon, firstCall bool) {
*DEBUG = true
}
// attempt to apply command line flags correctly
- if app.config_path == *CONFIG && app.data_path != *DATA {
- app.data_path = *DATA
- app.config_path = filepath.Join(app.data_path, "config.ini")
- } else if app.config_path != *CONFIG && app.data_path == *DATA {
- app.config_path = *CONFIG
+ if app.configPath == *CONFIG && app.dataPath != *DATA {
+ app.dataPath = *DATA
+ app.configPath = filepath.Join(app.dataPath, "config.ini")
+ } else if app.configPath != *CONFIG && app.dataPath == *DATA {
+ app.configPath = *CONFIG
} else {
- app.config_path = *CONFIG
- app.data_path = *DATA
+ app.configPath = *CONFIG
+ app.dataPath = *DATA
}
// env variables are necessary because syscall.Exec for self-restarts doesn't doesn't work with arguments for some reason.
if v := os.Getenv("JFA_CONFIGPATH"); v != "" {
- app.config_path = v
+ app.configPath = v
}
if v := os.Getenv("JFA_DATAPATH"); v != "" {
- app.data_path = v
+ app.dataPath = v
}
- os.Setenv("JFA_CONFIGPATH", app.config_path)
- os.Setenv("JFA_DATAPATH", app.data_path)
+ os.Setenv("JFA_CONFIGPATH", app.configPath)
+ os.Setenv("JFA_DATAPATH", app.dataPath)
var firstRun bool
- if _, err := os.Stat(app.data_path); os.IsNotExist(err) {
- os.Mkdir(app.data_path, 0700)
+ if _, err := os.Stat(app.dataPath); os.IsNotExist(err) {
+ os.Mkdir(app.dataPath, 0700)
}
- if _, err := os.Stat(app.config_path); os.IsNotExist(err) {
+ if _, err := os.Stat(app.configPath); os.IsNotExist(err) {
firstRun = true
- dConfigPath := filepath.Join(app.local_path, "config-default.ini")
+ dConfigPath := filepath.Join(app.localPath, "config-default.ini")
var dConfig *os.File
dConfig, err = os.Open(dConfigPath)
if err != nil {
@@ -255,28 +257,28 @@ func start(asDaemon, firstCall bool) {
}
defer dConfig.Close()
var nConfig *os.File
- nConfig, err := os.Create(app.config_path)
+ nConfig, err := os.Create(app.configPath)
if err != nil {
- app.err.Printf("Couldn't open config file for writing: \"%s\"", app.config_path)
+ app.err.Printf("Couldn't open config file for writing: \"%s\"", app.configPath)
app.err.Fatalf("Error: %s", err)
}
defer nConfig.Close()
_, err = io.Copy(nConfig, dConfig)
if err != nil {
- app.err.Fatalf("Couldn't copy default config. To do this manually, copy\n%s\nto\n%s", dConfigPath, app.config_path)
+ app.err.Fatalf("Couldn't copy default config. To do this manually, copy\n%s\nto\n%s", dConfigPath, app.configPath)
}
- app.info.Printf("Copied default configuration to \"%s\"", app.config_path)
+ app.info.Printf("Copied default configuration to \"%s\"", app.configPath)
}
var debugMode bool
var address string
if app.loadConfig() != nil {
- app.err.Fatalf("Failed to load config file \"%s\"", app.config_path)
+ app.err.Fatalf("Failed to load config file \"%s\"", app.configPath)
}
lang := app.config.Section("ui").Key("language").MustString("en-us")
- app.storage.lang.FormPath = filepath.Join(app.local_path, "lang", "form", lang+".json")
+ app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form", lang+".json")
if _, err := os.Stat(app.storage.lang.FormPath); os.IsNotExist(err) {
- app.storage.lang.FormPath = filepath.Join(app.local_path, "lang", "form", "en-us.json")
+ app.storage.lang.FormPath = filepath.Join(app.localPath, "lang", "form", "en-us.json")
}
app.storage.loadLang()
app.version = app.config.Section("jellyfin").Key("version").String()
@@ -356,7 +358,7 @@ func start(asDaemon, firstCall bool) {
address = fmt.Sprintf("%s:%d", app.host, app.port)
- app.debug.Printf("Loaded config file \"%s\"", app.config_path)
+ app.debug.Printf("Loaded config file \"%s\"", app.configPath)
if app.config.Section("ui").Key("bs5").MustBool(false) {
app.cssFile = "bs5-jf.css"
@@ -411,8 +413,8 @@ func start(asDaemon, firstCall bool) {
}
- app.configBase_path = filepath.Join(app.local_path, "config-base.json")
- configBase, _ := ioutil.ReadFile(app.configBase_path)
+ app.configBasePath = filepath.Join(app.localPath, "config-base.json")
+ configBase, _ := ioutil.ReadFile(app.configBasePath)
json.Unmarshal(configBase, &app.configBase)
themes := map[string]string{
@@ -424,7 +426,7 @@ func start(asDaemon, firstCall bool) {
app.cssFile = val
}
app.debug.Printf("Using css file \"%s\"", app.cssFile)
- secret, err := GenerateSecret(16)
+ secret, err := generateSecret(16)
if err != nil {
app.err.Fatal(err)
}
@@ -481,8 +483,8 @@ func start(asDaemon, firstCall bool) {
os.Exit(0)
}
- inviteDaemon := NewRepeater(time.Duration(60*time.Second), app)
- go inviteDaemon.Run()
+ inviteDaemon := newRepeater(time.Duration(60*time.Second), app)
+ go inviteDaemon.run()
if app.config.Section("password_resets").Key("enabled").MustBool(false) {
go app.StartPWR()
@@ -502,7 +504,7 @@ func start(asDaemon, firstCall bool) {
setGinLogger(router, debugMode)
router.Use(gin.Recovery())
- router.Use(static.Serve("/", static.LocalFile(filepath.Join(app.local_path, "static"), false)))
+ router.Use(static.Serve("/", static.LocalFile(filepath.Join(app.localPath, "static"), false)))
app.loadHTML(router)
router.NoRoute(app.NoRouteHandler)
if debugMode {
@@ -514,7 +516,7 @@ func start(asDaemon, firstCall bool) {
router.GET("/token/login", app.getTokenLogin)
router.GET("/token/refresh", app.getTokenRefresh)
router.POST("/newUser", app.NewUser)
- router.Use(static.Serve("/invite/", static.LocalFile(filepath.Join(app.local_path, "static"), false)))
+ router.Use(static.Serve("/invite/", static.LocalFile(filepath.Join(app.localPath, "static"), false)))
router.GET("/invite/:invCode", app.InviteProxy)
if *SWAGGER {
app.info.Print(aurora.Magenta("\n\nWARNING: Swagger should not be used on a public instance.\n\n"))
diff --git a/pwreset.go b/pwreset.go
index 2ca7e1a..7e91a47 100644
--- a/pwreset.go
+++ b/pwreset.go
@@ -34,7 +34,8 @@ func (app *appContext) StartPWR() {
<-done
}
-type Pwr struct {
+// PasswordReset represents a passwordreset-xyz.json file generated by Jellyfin.
+type PasswordReset struct {
Pin string `json:"Pin"`
Username string `json:"UserName"`
Expiry time.Time `json:"ExpirationDate"`
@@ -48,7 +49,7 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
return
}
if event.Op&fsnotify.Write == fsnotify.Write && strings.Contains(event.Name, "passwordreset") {
- var pwr Pwr
+ var pwr PasswordReset
data, err := ioutil.ReadFile(event.Name)
if err != nil {
return
diff --git a/pwval.go b/pwval.go
index 8699e7e..96dcbc3 100644
--- a/pwval.go
+++ b/pwval.go
@@ -4,6 +4,7 @@ import (
"unicode"
)
+// Validator allows for validation of passwords.
type Validator struct {
minLength, upper, lower, number, special int
criteria ValidatorConf
diff --git a/ts/admin.ts b/ts/admin.ts
index c920173..33eb546 100644
--- a/ts/admin.ts
+++ b/ts/admin.ts
@@ -121,10 +121,12 @@ window.toClipboard = (str: string): void => {
function login(username: string, password: string, modal: boolean, button?: HTMLButtonElement, run?: (arg0: number) => void): void {
const req = new XMLHttpRequest();
req.responseType = 'json';
- let url = "/token/login";
+ let url = window.URLBase;
const refresh = (username == "" && password == "");
if (refresh) {
- url = "/token/refresh";
+ url += "/token/refresh";
+ } else {
+ url += "/token/login";
}
req.open("GET", url, true);
if (!refresh) {
diff --git a/ts/modules/common.ts b/ts/modules/common.ts
index 209cb19..c6b25be 100644
--- a/ts/modules/common.ts
+++ b/ts/modules/common.ts
@@ -46,7 +46,7 @@ export const addAttr = (el: HTMLElement, attr: string): void => el.classList.add
export const _get = (url: string, data: Object, onreadystatechange: () => void): void => {
let req = new XMLHttpRequest();
- req.open("GET", url, true);
+ req.open("GET", window.URLBase + url, true);
req.responseType = 'json';
req.setRequestHeader("Authorization", "Bearer " + window.token);
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
@@ -56,7 +56,7 @@ export const _get = (url: string, data: Object, onreadystatechange: () => void):
export const _post = (url: string, data: Object, onreadystatechange: () => void, response?: boolean): void => {
let req = new XMLHttpRequest();
- req.open("POST", url, true);
+ req.open("POST", window.URLBase + url, true);
if (response) {
req.responseType = 'json';
}
@@ -68,7 +68,7 @@ export const _post = (url: string, data: Object, onreadystatechange: () => void,
export function _delete(url: string, data: Object, onreadystatechange: () => void): void {
let req = new XMLHttpRequest();
- req.open("DELETE", url, true);
+ req.open("DELETE", window.URLBase + url, true);
req.setRequestHeader("Authorization", "Bearer " + window.token);
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
req.onreadystatechange = onreadystatechange;
diff --git a/ts/typings/d.ts b/ts/typings/d.ts
index 7bc6c51..5be1863 100644
--- a/ts/typings/d.ts
+++ b/ts/typings/d.ts
@@ -14,6 +14,7 @@ declare interface Window {
bsVersion: number;
bs5: boolean;
BS: Bootstrap;
+ URLBase: string;
Modals: BSModals;
cssFile: string;
availableProfiles: Array;
diff --git a/views.go b/views.go
index 30814ae..5164fd1 100644
--- a/views.go
+++ b/views.go
@@ -7,12 +7,18 @@ import (
"github.com/gin-gonic/gin"
)
+func gcHTML(gc *gin.Context, code int, file string, templ gin.H) {
+ gc.Header("Cache-Control", "no-cache")
+ gc.HTML(code, file, templ)
+}
+
func (app *appContext) AdminPage(gc *gin.Context) {
bs5 := app.config.Section("ui").Key("bs5").MustBool(false)
emailEnabled, _ := app.config.Section("invite_emails").Key("enabled").Bool()
notificationsEnabled, _ := app.config.Section("notifications").Key("enabled").Bool()
ombiEnabled := app.config.Section("ombi").Key("enabled").MustBool(false)
- gc.HTML(http.StatusOK, "admin.html", gin.H{
+ gcHTML(gc, http.StatusOK, "admin.html", gin.H{
+ "urlBase": app.URLBase,
"bs5": bs5,
"cssFile": app.cssFile,
"contactMessage": "",
@@ -34,7 +40,8 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
if strings.Contains(email, "Failed") {
email = ""
}
- gc.HTML(http.StatusOK, "form-loader.html", gin.H{
+ gcHTML(gc, http.StatusOK, "form-loader.html", gin.H{
+ "urlBase": app.URLBase,
"cssFile": app.cssFile,
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
"helpMessage": app.config.Section("ui").Key("help_message").String(),
@@ -50,7 +57,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
"lang": app.storage.lang.Form["strings"],
})
} else {
- gc.HTML(404, "invalidCode.html", gin.H{
+ gcHTML(gc, 404, "invalidCode.html", gin.H{
"bs5": app.config.Section("ui").Key("bs5").MustBool(false),
"cssFile": app.cssFile,
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
@@ -59,7 +66,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
}
func (app *appContext) NoRouteHandler(gc *gin.Context) {
- gc.HTML(404, "404.html", gin.H{
+ gcHTML(gc, 404, "404.html", gin.H{
"bs5": app.config.Section("ui").Key("bs5").MustBool(false),
"cssFile": app.cssFile,
"contactMessage": app.config.Section("ui").Key("contact_message").String(),