mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 17:10: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() {
|
func (app *appContext) checkInvites() {
|
||||||
current_time := time.Now()
|
currentTime := time.Now()
|
||||||
app.storage.loadInvites()
|
app.storage.loadInvites()
|
||||||
changed := false
|
changed := false
|
||||||
for code, data := range app.storage.invites {
|
for code, data := range app.storage.invites {
|
||||||
expiry := data.ValidTill
|
expiry := data.ValidTill
|
||||||
if !current_time.After(expiry) {
|
if !currentTime.After(expiry) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
app.debug.Printf("Housekeeping: Deleting old invite %s", code)
|
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 {
|
func (app *appContext) checkInvite(code string, used bool, username string) bool {
|
||||||
current_time := time.Now()
|
currentTime := time.Now()
|
||||||
app.storage.loadInvites()
|
app.storage.loadInvites()
|
||||||
changed := false
|
changed := false
|
||||||
inv, match := app.storage.invites[code]
|
inv, match := app.storage.invites[code]
|
||||||
@ -152,7 +152,7 @@ func (app *appContext) checkInvite(code string, used bool, username string) bool
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
expiry := inv.ValidTill
|
expiry := inv.ValidTill
|
||||||
if current_time.After(expiry) {
|
if currentTime.After(expiry) {
|
||||||
app.debug.Printf("Housekeeping: Deleting old invite %s", code)
|
app.debug.Printf("Housekeeping: Deleting old invite %s", code)
|
||||||
notify := inv.Notify
|
notify := inv.Notify
|
||||||
if app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 {
|
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?
|
// 0 means infinite i guess?
|
||||||
newInv.RemainingUses -= 1
|
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 {
|
if !del {
|
||||||
app.storage.invites[code] = newInv
|
app.storage.invites[code] = newInv
|
||||||
}
|
}
|
||||||
@ -256,15 +256,17 @@ func (app *appContext) NewUserAdmin(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
if len(app.storage.policy) != 0 {
|
if len(app.storage.policy) != 0 {
|
||||||
status, err = app.jf.SetPolicy(id, app.storage.policy)
|
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.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 {
|
if len(app.storage.configuration) != 0 && len(app.storage.displayprefs) != 0 {
|
||||||
status, err = app.jf.SetConfiguration(id, app.storage.configuration)
|
status, err = app.jf.SetConfiguration(id, app.storage.configuration)
|
||||||
if (status == 200 || status == 204) && err == nil {
|
if (status == 200 || status == 204) && err == nil {
|
||||||
status, err = app.jf.SetDisplayPreferences(id, app.storage.displayprefs)
|
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)
|
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 {
|
if len(profile.Policy) != 0 {
|
||||||
app.debug.Printf("Applying policy from profile \"%s\"", invite.Profile)
|
app.debug.Printf("Applying policy from profile \"%s\"", invite.Profile)
|
||||||
status, err = app.jf.SetPolicy(id, profile.Policy)
|
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.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 {
|
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)
|
status, err = app.jf.SetConfiguration(id, profile.Configuration)
|
||||||
if (status == 200 || status == 204) && err == nil {
|
if (status == 200 || status == 204) && err == nil {
|
||||||
status, err = app.jf.SetDisplayPreferences(id, profile.Displayprefs)
|
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.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.debug.Println("Generating new invite")
|
||||||
app.storage.loadInvites()
|
app.storage.loadInvites()
|
||||||
gc.BindJSON(&req)
|
gc.BindJSON(&req)
|
||||||
current_time := time.Now()
|
currentTime := time.Now()
|
||||||
valid_till := current_time.AddDate(0, 0, req.Days)
|
validTill := currentTime.AddDate(0, 0, req.Days)
|
||||||
valid_till = valid_till.Add(time.Hour*time.Duration(req.Hours) + time.Minute*time.Duration(req.Minutes))
|
validTill = validTill.Add(time.Hour*time.Duration(req.Hours) + time.Minute*time.Duration(req.Minutes))
|
||||||
// make sure code doesn't begin with number
|
// make sure code doesn't begin with number
|
||||||
invite_code := shortuuid.New()
|
inviteCode := shortuuid.New()
|
||||||
_, err := strconv.Atoi(string(invite_code[0]))
|
_, err := strconv.Atoi(string(inviteCode[0]))
|
||||||
for err == nil {
|
for err == nil {
|
||||||
invite_code = shortuuid.New()
|
inviteCode = shortuuid.New()
|
||||||
_, err = strconv.Atoi(string(invite_code[0]))
|
_, err = strconv.Atoi(string(inviteCode[0]))
|
||||||
}
|
}
|
||||||
var invite Invite
|
var invite Invite
|
||||||
invite.Created = current_time
|
invite.Created = currentTime
|
||||||
if req.MultipleUses {
|
if req.MultipleUses {
|
||||||
if req.NoLimit {
|
if req.NoLimit {
|
||||||
invite.NoLimit = true
|
invite.NoLimit = true
|
||||||
@ -503,21 +508,21 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
|
|||||||
} else {
|
} else {
|
||||||
invite.RemainingUses = 1
|
invite.RemainingUses = 1
|
||||||
}
|
}
|
||||||
invite.ValidTill = valid_till
|
invite.ValidTill = validTill
|
||||||
if req.Email != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) {
|
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
|
invite.Email = req.Email
|
||||||
msg, err := app.email.constructInvite(invite_code, invite, app)
|
msg, err := app.email.constructInvite(inviteCode, invite, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
invite.Email = fmt.Sprintf("Failed to send to %s", req.Email)
|
invite.Email = fmt.Sprintf("Failed to send to %s", req.Email)
|
||||||
app.err.Printf("%s: Failed to construct invite email", invite_code)
|
app.err.Printf("%s: Failed to construct invite email", inviteCode)
|
||||||
app.debug.Printf("%s: Error: %s", invite_code, err)
|
app.debug.Printf("%s: Error: %s", inviteCode, err)
|
||||||
} else if err := app.email.send(req.Email, msg); err != nil {
|
} else if err := app.email.send(req.Email, msg); err != nil {
|
||||||
invite.Email = fmt.Sprintf("Failed to send to %s", req.Email)
|
invite.Email = fmt.Sprintf("Failed to send to %s", req.Email)
|
||||||
app.err.Printf("%s: %s", invite_code, invite.Email)
|
app.err.Printf("%s: %s", inviteCode, invite.Email)
|
||||||
app.debug.Printf("%s: Error: %s", invite_code, err)
|
app.debug.Printf("%s: Error: %s", inviteCode, err)
|
||||||
} else {
|
} 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 != "" {
|
if req.Profile != "" {
|
||||||
@ -527,7 +532,7 @@ func (app *appContext) GenerateInvite(gc *gin.Context) {
|
|||||||
invite.Profile = "Default"
|
invite.Profile = "Default"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
app.storage.invites[invite_code] = invite
|
app.storage.invites[inviteCode] = invite
|
||||||
app.storage.storeInvites()
|
app.storage.storeInvites()
|
||||||
respondBool(200, true, gc)
|
respondBool(200, true, gc)
|
||||||
}
|
}
|
||||||
@ -972,7 +977,7 @@ func (app *appContext) ModifyEmails(gc *gin.Context) {
|
|||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param userSettingsDTO body userSettingsDTO true "Parameters for applying settings"
|
// @Param userSettingsDTO body userSettingsDTO true "Parameters for applying settings"
|
||||||
// @Success 200 {object} errorListDTO
|
// @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]
|
// @Router /users/settings [post]
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @tags Profiles & Settings
|
// @tags Profiles & Settings
|
||||||
@ -1063,7 +1068,7 @@ func (app *appContext) ApplySettings(gc *gin.Context) {
|
|||||||
func (app *appContext) GetConfig(gc *gin.Context) {
|
func (app *appContext) GetConfig(gc *gin.Context) {
|
||||||
app.info.Println("Config requested")
|
app.info.Println("Config requested")
|
||||||
resp := map[string]interface{}{}
|
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.langFiles, _ = ioutil.ReadDir(langPath)
|
||||||
app.lang.langOptions = make([]string, len(app.lang.langFiles))
|
app.lang.langOptions = make([]string, len(app.lang.langFiles))
|
||||||
chosenLang := app.config.Section("ui").Key("language").MustString("en-us") + ".json"
|
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")
|
app.info.Println("Config modification requested")
|
||||||
var req configDTO
|
var req configDTO
|
||||||
gc.BindJSON(&req)
|
gc.BindJSON(&req)
|
||||||
tempConfig, _ := ini.Load(app.config_path)
|
tempConfig, _ := ini.Load(app.configPath)
|
||||||
for section, settings := range req {
|
for section, settings := range req {
|
||||||
if section != "restart-program" {
|
if section != "restart-program" {
|
||||||
_, err := tempConfig.GetSection(section)
|
_, 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")
|
app.debug.Println("Config saved")
|
||||||
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) {
|
||||||
|
29
config.go
29
config.go
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gopkg.in/ini.v1"
|
"gopkg.in/ini.v1"
|
||||||
)
|
)
|
||||||
@ -36,7 +37,7 @@ func (app *appContext) loadDefaults() (err error) {
|
|||||||
|
|
||||||
func (app *appContext) loadConfig() error {
|
func (app *appContext) loadConfig() error {
|
||||||
var err error
|
var err error
|
||||||
app.config, err = ini.Load(app.config_path)
|
app.config, err = ini.Load(app.configPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -48,32 +49,32 @@ func (app *appContext) loadConfig() error {
|
|||||||
// key.SetValue(filepath.Join(app.data_path, (key.Name() + ".json")))
|
// key.SetValue(filepath.Join(app.data_path, (key.Name() + ".json")))
|
||||||
// }
|
// }
|
||||||
if key.Name() != "html_templates" {
|
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"} {
|
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template"} {
|
||||||
// if app.config.Section("files").Key(key).MustString("") == "" {
|
// if app.config.Section("files").Key(key).MustString("") == "" {
|
||||||
// key.SetValue(filepath.Join(app.data_path, (key.Name() + ".json")))
|
// 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("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_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.local_path, "email.txt")))
|
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_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.local_path, "invite-email.txt")))
|
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_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.local_path, "expired.txt")))
|
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_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.local_path, "created.txt")))
|
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_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.local_path, "deleted.txt")))
|
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("version").SetValue(VERSION)
|
||||||
app.config.Section("jellyfin").Key("device").SetValue("jfa-go")
|
app.config.Section("jellyfin").Key("device").SetValue("jfa-go")
|
||||||
|
@ -179,6 +179,14 @@
|
|||||||
"type": "bool",
|
"type": "bool",
|
||||||
"value": false,
|
"value": false,
|
||||||
"description": "Use the Bootstrap 5 Alpha. Looks better and removes the need for jQuery, so the page should load faster."
|
"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": {
|
"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
|
// https://bbengfort.github.io/snippets/2016/06/26/background-work-goroutines-timer.html THANKS
|
||||||
|
|
||||||
type Repeater struct {
|
type repeater struct {
|
||||||
Stopped bool
|
Stopped bool
|
||||||
ShutdownChannel chan string
|
ShutdownChannel chan string
|
||||||
Interval time.Duration
|
Interval time.Duration
|
||||||
@ -12,8 +12,8 @@ type Repeater struct {
|
|||||||
app *appContext
|
app *appContext
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRepeater(interval time.Duration, app *appContext) *Repeater {
|
func newRepeater(interval time.Duration, app *appContext) *repeater {
|
||||||
return &Repeater{
|
return &repeater{
|
||||||
Stopped: false,
|
Stopped: false,
|
||||||
ShutdownChannel: make(chan string),
|
ShutdownChannel: make(chan string),
|
||||||
Interval: interval,
|
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")
|
rt.app.info.Println("Invite daemon started")
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
@ -42,7 +42,7 @@ func (rt *Repeater) Run() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rt *Repeater) Shutdown() {
|
func (rt *repeater) shutdown() {
|
||||||
rt.Stopped = true
|
rt.Stopped = true
|
||||||
rt.ShutdownChannel <- "Down"
|
rt.ShutdownChannel <- "Down"
|
||||||
<-rt.ShutdownChannel
|
<-rt.ShutdownChannel
|
||||||
|
@ -5,11 +5,11 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<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="apple-touch-icon" sizes="180x180" href="{{ .urlBase }}/apple-touch-icon.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="{{ .urlBase }}/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="{{ .urlBase }}/favicon-16x16.png">
|
||||||
<link rel="manifest" href="/site.webmanifest">
|
<link rel="manifest" href="{{ .urlBase }}/site.webmanifest">
|
||||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
|
<link rel="mask-icon" href="{{ .urlBase }}/safari-pinned-tab.svg" color="#5bbad5">
|
||||||
<meta name="msapplication-TileColor" content="#603cba">
|
<meta name="msapplication-TileColor" content="#603cba">
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
<!-- Bootstrap CSS -->
|
<!-- Bootstrap CSS -->
|
||||||
@ -30,6 +30,7 @@
|
|||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
window.URLBase = "{{ .urlBase }}";
|
||||||
{{ if .bs5 }}
|
{{ if .bs5 }}
|
||||||
window.bsVersion = 5;
|
window.bsVersion = 5;
|
||||||
{{ else }}
|
{{ else }}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
window.usernameEnabled = {{ .settings.username }};
|
window.usernameEnabled = {{ .settings.username }};
|
||||||
window.validationStrings = JSON.parse({{ .lang.validationStrings }});
|
window.validationStrings = JSON.parse({{ .lang.validationStrings }});
|
||||||
window.invalidPassword = "{{ .lang.reEnterPasswordInvalid }}";
|
window.invalidPassword = "{{ .lang.reEnterPasswordInvalid }}";
|
||||||
|
window.URLBase = "{{ .urlBase }}";
|
||||||
</script>
|
</script>
|
||||||
<script src="form.js" type="module"></script>
|
<script src="form.js" type="module"></script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<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="apple-touch-icon" sizes="180x180" href="{{ .urlBase }}/apple-touch-icon.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="{{ .urlBase }}/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="{{ .urlBase }}/favicon-16x16.png">
|
||||||
<link rel="manifest" href="/site.webmanifest">
|
<link rel="manifest" href="{{ .urlBase }}/site.webmanifest">
|
||||||
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">
|
<link rel="mask-icon" href="{{ .urlBase }}/safari-pinned-tab.svg" color="#5bbad5">
|
||||||
<meta name="msapplication-TileColor" content="#603cba">
|
<meta name="msapplication-TileColor" content="#603cba">
|
||||||
<meta name="theme-color" content="#ffffff">
|
<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
|
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{
|
email := &Email{
|
||||||
subject: app.config.Section("password_resets").Key("subject").MustString("Password reset - Jellyfin"),
|
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"
|
"gopkg.in/ini.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Username is JWT!
|
// User is used for auth purposes.
|
||||||
type User struct {
|
type User struct {
|
||||||
UserID string `json:"id"`
|
UserID string `json:"id"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
@ -44,11 +44,11 @@ type User struct {
|
|||||||
type appContext struct {
|
type appContext struct {
|
||||||
// defaults *Config
|
// defaults *Config
|
||||||
config *ini.File
|
config *ini.File
|
||||||
config_path string
|
configPath string
|
||||||
configBase_path string
|
configBasePath string
|
||||||
configBase map[string]interface{}
|
configBase map[string]interface{}
|
||||||
data_path string
|
dataPath string
|
||||||
local_path string
|
localPath string
|
||||||
cssFile string
|
cssFile string
|
||||||
bsVersion int
|
bsVersion int
|
||||||
jellyfinLogin bool
|
jellyfinLogin bool
|
||||||
@ -68,8 +68,10 @@ type appContext struct {
|
|||||||
version string
|
version string
|
||||||
quit chan os.Signal
|
quit chan os.Signal
|
||||||
lang Languages
|
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 {
|
type Languages struct {
|
||||||
langFiles []os.FileInfo // Language filenames
|
langFiles []os.FileInfo // Language filenames
|
||||||
langOptions []string // Language names
|
langOptions []string // Language names
|
||||||
@ -78,10 +80,10 @@ type Languages struct {
|
|||||||
|
|
||||||
func (app *appContext) loadHTML(router *gin.Engine) {
|
func (app *appContext) loadHTML(router *gin.Engine) {
|
||||||
customPath := app.config.Section("files").Key("html_templates").MustString("")
|
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)
|
htmlFiles, err := ioutil.ReadDir(templatePath)
|
||||||
if err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
loadFiles := make([]string, len(htmlFiles))
|
loadFiles := make([]string, len(htmlFiles))
|
||||||
@ -97,7 +99,7 @@ func (app *appContext) loadHTML(router *gin.Engine) {
|
|||||||
router.LoadHTMLFiles(loadFiles...)
|
router.LoadHTMLFiles(loadFiles...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateSecret(length int) (string, error) {
|
func generateSecret(length int) (string, error) {
|
||||||
bytes := make([]byte, length)
|
bytes := make([]byte, length)
|
||||||
_, err := rand.Read(bytes)
|
_, err := rand.Read(bytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -173,7 +175,7 @@ func test(app *appContext) {
|
|||||||
fmt.Scanln(&username)
|
fmt.Scanln(&username)
|
||||||
user, status, err := app.jf.UserByName(username, false)
|
user, status, err := app.jf.UserByName(username, false)
|
||||||
fmt.Printf("UserByName (%s): code %d err %s", username, status, err)
|
fmt.Printf("UserByName (%s): code %d err %s", username, status, err)
|
||||||
out, err := json.MarshalIndent(user, "", " ")
|
out, _ := json.MarshalIndent(user, "", " ")
|
||||||
fmt.Print(string(out))
|
fmt.Print(string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,17 +189,17 @@ func start(asDaemon, firstCall bool) {
|
|||||||
local_path is the internal 'data' directory.
|
local_path is the internal 'data' directory.
|
||||||
*/
|
*/
|
||||||
userConfigDir, _ := os.UserConfigDir()
|
userConfigDir, _ := os.UserConfigDir()
|
||||||
app.data_path = filepath.Join(userConfigDir, "jfa-go")
|
app.dataPath = filepath.Join(userConfigDir, "jfa-go")
|
||||||
app.config_path = filepath.Join(app.data_path, "config.ini")
|
app.configPath = filepath.Join(app.dataPath, "config.ini")
|
||||||
executable, _ := os.Executable()
|
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.info = log.New(os.Stdout, "[INFO] ", log.Ltime)
|
||||||
app.err = log.New(os.Stdout, "[ERROR] ", log.Ltime|log.Lshortfile)
|
app.err = log.New(os.Stdout, "[ERROR] ", log.Ltime|log.Lshortfile)
|
||||||
|
|
||||||
if firstCall {
|
if firstCall {
|
||||||
DATA = flag.String("data", app.data_path, "alternate path to data directory.")
|
DATA = flag.String("data", app.dataPath, "alternate path to data directory.")
|
||||||
CONFIG = flag.String("config", app.config_path, "alternate path to config file.")
|
CONFIG = flag.String("config", app.configPath, "alternate path to config file.")
|
||||||
HOST = flag.String("host", "", "alternate address to host web ui on.")
|
HOST = flag.String("host", "", "alternate address to host web ui on.")
|
||||||
PORT = flag.Int("port", 0, "alternate port 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.")
|
DEBUG = flag.Bool("debug", false, "Enables debug logging and exposes pprof.")
|
||||||
@ -219,35 +221,35 @@ func start(asDaemon, firstCall bool) {
|
|||||||
*DEBUG = true
|
*DEBUG = true
|
||||||
}
|
}
|
||||||
// attempt to apply command line flags correctly
|
// attempt to apply command line flags correctly
|
||||||
if app.config_path == *CONFIG && app.data_path != *DATA {
|
if app.configPath == *CONFIG && app.dataPath != *DATA {
|
||||||
app.data_path = *DATA
|
app.dataPath = *DATA
|
||||||
app.config_path = filepath.Join(app.data_path, "config.ini")
|
app.configPath = filepath.Join(app.dataPath, "config.ini")
|
||||||
} else if app.config_path != *CONFIG && app.data_path == *DATA {
|
} else if app.configPath != *CONFIG && app.dataPath == *DATA {
|
||||||
app.config_path = *CONFIG
|
app.configPath = *CONFIG
|
||||||
} else {
|
} else {
|
||||||
app.config_path = *CONFIG
|
app.configPath = *CONFIG
|
||||||
app.data_path = *DATA
|
app.dataPath = *DATA
|
||||||
}
|
}
|
||||||
|
|
||||||
// env variables are necessary because syscall.Exec for self-restarts doesn't doesn't work with arguments for some reason.
|
// 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 != "" {
|
if v := os.Getenv("JFA_CONFIGPATH"); v != "" {
|
||||||
app.config_path = v
|
app.configPath = v
|
||||||
}
|
}
|
||||||
if v := os.Getenv("JFA_DATAPATH"); 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_CONFIGPATH", app.configPath)
|
||||||
os.Setenv("JFA_DATAPATH", app.data_path)
|
os.Setenv("JFA_DATAPATH", app.dataPath)
|
||||||
|
|
||||||
var firstRun bool
|
var firstRun bool
|
||||||
if _, err := os.Stat(app.data_path); os.IsNotExist(err) {
|
if _, err := os.Stat(app.dataPath); os.IsNotExist(err) {
|
||||||
os.Mkdir(app.data_path, 0700)
|
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
|
firstRun = true
|
||||||
dConfigPath := filepath.Join(app.local_path, "config-default.ini")
|
dConfigPath := filepath.Join(app.localPath, "config-default.ini")
|
||||||
var dConfig *os.File
|
var dConfig *os.File
|
||||||
dConfig, err = os.Open(dConfigPath)
|
dConfig, err = os.Open(dConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -255,28 +257,28 @@ func start(asDaemon, firstCall bool) {
|
|||||||
}
|
}
|
||||||
defer dConfig.Close()
|
defer dConfig.Close()
|
||||||
var nConfig *os.File
|
var nConfig *os.File
|
||||||
nConfig, err := os.Create(app.config_path)
|
nConfig, err := os.Create(app.configPath)
|
||||||
if err != nil {
|
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)
|
app.err.Fatalf("Error: %s", err)
|
||||||
}
|
}
|
||||||
defer nConfig.Close()
|
defer nConfig.Close()
|
||||||
_, err = io.Copy(nConfig, dConfig)
|
_, err = io.Copy(nConfig, dConfig)
|
||||||
if err != nil {
|
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 debugMode bool
|
||||||
var address string
|
var address string
|
||||||
if app.loadConfig() != nil {
|
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")
|
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) {
|
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.storage.loadLang()
|
||||||
app.version = app.config.Section("jellyfin").Key("version").String()
|
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)
|
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) {
|
if app.config.Section("ui").Key("bs5").MustBool(false) {
|
||||||
app.cssFile = "bs5-jf.css"
|
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")
|
app.configBasePath = filepath.Join(app.localPath, "config-base.json")
|
||||||
configBase, _ := ioutil.ReadFile(app.configBase_path)
|
configBase, _ := ioutil.ReadFile(app.configBasePath)
|
||||||
json.Unmarshal(configBase, &app.configBase)
|
json.Unmarshal(configBase, &app.configBase)
|
||||||
|
|
||||||
themes := map[string]string{
|
themes := map[string]string{
|
||||||
@ -424,7 +426,7 @@ func start(asDaemon, firstCall bool) {
|
|||||||
app.cssFile = val
|
app.cssFile = val
|
||||||
}
|
}
|
||||||
app.debug.Printf("Using css file \"%s\"", app.cssFile)
|
app.debug.Printf("Using css file \"%s\"", app.cssFile)
|
||||||
secret, err := GenerateSecret(16)
|
secret, err := generateSecret(16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.err.Fatal(err)
|
app.err.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -481,8 +483,8 @@ func start(asDaemon, firstCall bool) {
|
|||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
inviteDaemon := NewRepeater(time.Duration(60*time.Second), app)
|
inviteDaemon := newRepeater(time.Duration(60*time.Second), app)
|
||||||
go inviteDaemon.Run()
|
go inviteDaemon.run()
|
||||||
|
|
||||||
if app.config.Section("password_resets").Key("enabled").MustBool(false) {
|
if app.config.Section("password_resets").Key("enabled").MustBool(false) {
|
||||||
go app.StartPWR()
|
go app.StartPWR()
|
||||||
@ -502,7 +504,7 @@ func start(asDaemon, firstCall bool) {
|
|||||||
setGinLogger(router, debugMode)
|
setGinLogger(router, debugMode)
|
||||||
|
|
||||||
router.Use(gin.Recovery())
|
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)
|
app.loadHTML(router)
|
||||||
router.NoRoute(app.NoRouteHandler)
|
router.NoRoute(app.NoRouteHandler)
|
||||||
if debugMode {
|
if debugMode {
|
||||||
@ -514,7 +516,7 @@ func start(asDaemon, firstCall bool) {
|
|||||||
router.GET("/token/login", app.getTokenLogin)
|
router.GET("/token/login", app.getTokenLogin)
|
||||||
router.GET("/token/refresh", app.getTokenRefresh)
|
router.GET("/token/refresh", app.getTokenRefresh)
|
||||||
router.POST("/newUser", app.NewUser)
|
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)
|
router.GET("/invite/:invCode", app.InviteProxy)
|
||||||
if *SWAGGER {
|
if *SWAGGER {
|
||||||
app.info.Print(aurora.Magenta("\n\nWARNING: Swagger should not be used on a public instance.\n\n"))
|
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
|
<-done
|
||||||
}
|
}
|
||||||
|
|
||||||
type Pwr struct {
|
// PasswordReset represents a passwordreset-xyz.json file generated by Jellyfin.
|
||||||
|
type PasswordReset struct {
|
||||||
Pin string `json:"Pin"`
|
Pin string `json:"Pin"`
|
||||||
Username string `json:"UserName"`
|
Username string `json:"UserName"`
|
||||||
Expiry time.Time `json:"ExpirationDate"`
|
Expiry time.Time `json:"ExpirationDate"`
|
||||||
@ -48,7 +49,7 @@ func pwrMonitor(app *appContext, watcher *fsnotify.Watcher) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if event.Op&fsnotify.Write == fsnotify.Write && strings.Contains(event.Name, "passwordreset") {
|
if event.Op&fsnotify.Write == fsnotify.Write && strings.Contains(event.Name, "passwordreset") {
|
||||||
var pwr Pwr
|
var pwr PasswordReset
|
||||||
data, err := ioutil.ReadFile(event.Name)
|
data, err := ioutil.ReadFile(event.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
|
1
pwval.go
1
pwval.go
@ -4,6 +4,7 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Validator allows for validation of passwords.
|
||||||
type Validator struct {
|
type Validator struct {
|
||||||
minLength, upper, lower, number, special int
|
minLength, upper, lower, number, special int
|
||||||
criteria ValidatorConf
|
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 {
|
function login(username: string, password: string, modal: boolean, button?: HTMLButtonElement, run?: (arg0: number) => void): void {
|
||||||
const req = new XMLHttpRequest();
|
const req = new XMLHttpRequest();
|
||||||
req.responseType = 'json';
|
req.responseType = 'json';
|
||||||
let url = "/token/login";
|
let url = window.URLBase;
|
||||||
const refresh = (username == "" && password == "");
|
const refresh = (username == "" && password == "");
|
||||||
if (refresh) {
|
if (refresh) {
|
||||||
url = "/token/refresh";
|
url += "/token/refresh";
|
||||||
|
} else {
|
||||||
|
url += "/token/login";
|
||||||
}
|
}
|
||||||
req.open("GET", url, true);
|
req.open("GET", url, true);
|
||||||
if (!refresh) {
|
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 => {
|
export const _get = (url: string, data: Object, onreadystatechange: () => void): void => {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.open("GET", url, true);
|
req.open("GET", window.URLBase + url, true);
|
||||||
req.responseType = 'json';
|
req.responseType = 'json';
|
||||||
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
||||||
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
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 => {
|
export const _post = (url: string, data: Object, onreadystatechange: () => void, response?: boolean): void => {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.open("POST", url, true);
|
req.open("POST", window.URLBase + url, true);
|
||||||
if (response) {
|
if (response) {
|
||||||
req.responseType = 'json';
|
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 {
|
export function _delete(url: string, data: Object, onreadystatechange: () => void): void {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.open("DELETE", url, true);
|
req.open("DELETE", window.URLBase + url, true);
|
||||||
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
req.setRequestHeader("Authorization", "Bearer " + window.token);
|
||||||
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||||
req.onreadystatechange = onreadystatechange;
|
req.onreadystatechange = onreadystatechange;
|
||||||
|
@ -14,6 +14,7 @@ declare interface Window {
|
|||||||
bsVersion: number;
|
bsVersion: number;
|
||||||
bs5: boolean;
|
bs5: boolean;
|
||||||
BS: Bootstrap;
|
BS: Bootstrap;
|
||||||
|
URLBase: string;
|
||||||
Modals: BSModals;
|
Modals: BSModals;
|
||||||
cssFile: string;
|
cssFile: string;
|
||||||
availableProfiles: Array<any>;
|
availableProfiles: Array<any>;
|
||||||
|
15
views.go
15
views.go
@ -7,12 +7,18 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"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) {
|
func (app *appContext) AdminPage(gc *gin.Context) {
|
||||||
bs5 := app.config.Section("ui").Key("bs5").MustBool(false)
|
bs5 := app.config.Section("ui").Key("bs5").MustBool(false)
|
||||||
emailEnabled, _ := app.config.Section("invite_emails").Key("enabled").Bool()
|
emailEnabled, _ := app.config.Section("invite_emails").Key("enabled").Bool()
|
||||||
notificationsEnabled, _ := app.config.Section("notifications").Key("enabled").Bool()
|
notificationsEnabled, _ := app.config.Section("notifications").Key("enabled").Bool()
|
||||||
ombiEnabled := app.config.Section("ombi").Key("enabled").MustBool(false)
|
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,
|
"bs5": bs5,
|
||||||
"cssFile": app.cssFile,
|
"cssFile": app.cssFile,
|
||||||
"contactMessage": "",
|
"contactMessage": "",
|
||||||
@ -34,7 +40,8 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
if strings.Contains(email, "Failed") {
|
if strings.Contains(email, "Failed") {
|
||||||
email = ""
|
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,
|
"cssFile": app.cssFile,
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
"helpMessage": app.config.Section("ui").Key("help_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"],
|
"lang": app.storage.lang.Form["strings"],
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
gc.HTML(404, "invalidCode.html", gin.H{
|
gcHTML(gc, 404, "invalidCode.html", gin.H{
|
||||||
"bs5": app.config.Section("ui").Key("bs5").MustBool(false),
|
"bs5": app.config.Section("ui").Key("bs5").MustBool(false),
|
||||||
"cssFile": app.cssFile,
|
"cssFile": app.cssFile,
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"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) {
|
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),
|
"bs5": app.config.Section("ui").Key("bs5").MustBool(false),
|
||||||
"cssFile": app.cssFile,
|
"cssFile": app.cssFile,
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
|
Loading…
Reference in New Issue
Block a user