mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 09:00:10 +00:00
add URL base option for subfolder proxies
also cleaned up the naming of some things.
This commit is contained in:
parent
e35d0579c8
commit
9dbf60e3df
65
api.go
65
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) {
|
||||
|
29
config.go
29
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")
|
||||
|
@ -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": {
|
||||
|
10
daemon.go
10
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
|
||||
|
@ -5,11 +5,11 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ .urlBase }}/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ .urlBase }}/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ .urlBase }}/favicon-16x16.png">
|
||||
<link rel="manifest" href="{{ .urlBase }}/site.webmanifest">
|
||||
<link rel="mask-icon" href="{{ .urlBase }}/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta name="msapplication-TileColor" content="#603cba">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<!-- Bootstrap CSS -->
|
||||
@ -30,6 +30,7 @@
|
||||
}
|
||||
return "";
|
||||
}
|
||||
window.URLBase = "{{ .urlBase }}";
|
||||
{{ if .bs5 }}
|
||||
window.bsVersion = 5;
|
||||
{{ else }}
|
||||
|
@ -4,6 +4,7 @@
|
||||
window.usernameEnabled = {{ .settings.username }};
|
||||
window.validationStrings = JSON.parse({{ .lang.validationStrings }});
|
||||
window.invalidPassword = "{{ .lang.reEnterPasswordInvalid }}";
|
||||
window.URLBase = "{{ .urlBase }}";
|
||||
</script>
|
||||
<script src="form.js" type="module"></script>
|
||||
{{ end }}
|
||||
|
@ -4,11 +4,11 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
||||
<link rel="manifest" href="/site.webmanifest">
|
||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ .urlBase }}/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ .urlBase }}/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ .urlBase }}/favicon-16x16.png">
|
||||
<link rel="manifest" href="{{ .urlBase }}/site.webmanifest">
|
||||
<link rel="mask-icon" href="{{ .urlBase }}/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta name="msapplication-TileColor" content="#603cba">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
|
||||
|
2
email.go
2
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"),
|
||||
}
|
||||
|
90
main.go
90
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"))
|
||||
|
@ -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
|
||||
|
1
pwval.go
1
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
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -14,6 +14,7 @@ declare interface Window {
|
||||
bsVersion: number;
|
||||
bs5: boolean;
|
||||
BS: Bootstrap;
|
||||
URLBase: string;
|
||||
Modals: BSModals;
|
||||
cssFile: string;
|
||||
availableProfiles: Array<any>;
|
||||
|
15
views.go
15
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(),
|
||||
|
Loading…
Reference in New Issue
Block a user