From c52ba2162ee7def64c13e2d7e8f672c7c2734b3a Mon Sep 17 00:00:00 2001 From: Harvey Tindall Date: Tue, 13 May 2025 21:10:40 +0100 Subject: [PATCH] config: start adding path parameters to change the urls of the admin page, the my account page and of invites. Seems to work, but need to check all the code over and test. --- api-messages.go | 10 +- api-userpage.go | 8 +- config.go | 25 +++- config/config-base.yaml | 27 ++++ email.go | 4 +- html/404.html | 3 +- html/admin.html | 16 +-- html/crash.html | 1 + html/create-success.html | 1 - html/form-base.html | 4 - html/form.html | 1 - html/header.html | 29 ++++- html/invalidCode.html | 1 - html/login-modal.html | 2 +- html/password-reset.html | 3 +- html/setup.html | 1 - html/user.html | 12 +- main.go | 64 +++++----- models.go | 15 +++ router.go | 34 ++--- setup.go | 2 + ts/admin.ts | 8 +- ts/form.ts | 12 +- ts/modules/account-linking.ts | 2 +- ts/modules/accounts.ts | 2 + ts/modules/activity.ts | 12 +- ts/modules/common.ts | 14 ++- ts/modules/discord.ts | 2 + ts/modules/invites.ts | 4 +- ts/modules/login.ts | 4 +- ts/modules/modal.ts | 2 +- ts/modules/pages.ts | 1 + ts/modules/profiles.ts | 2 + ts/modules/search.ts | 2 + ts/modules/settings.ts | 2 + ts/modules/tabs.ts | 4 +- ts/modules/update.ts | 2 + ts/pwr-pin.ts | 2 + ts/setup.ts | 3 +- ts/typings/d.ts | 16 ++- ts/user.ts | 13 +- user-auth.go | 3 +- views.go | 227 ++++++++++++++++------------------ 43 files changed, 336 insertions(+), 266 deletions(-) diff --git a/api-messages.go b/api-messages.go index cb4f67f..e3297b5 100644 --- a/api-messages.go +++ b/api-messages.go @@ -414,7 +414,7 @@ func (app *appContext) TelegramVerified(gc *gin.Context) { respondBool(200, ok, gc) } -// @Summary Returns true/false on whether or not a telegram PIN was verified. Requires invite code. +// @Summary Returns true/false on whether or not a telegram PIN was verified. Requires invite code. NOTE: "/invite" might have been changed in Settings > URL Paths. // @Produce json // @Success 200 {object} boolResponse // @Success 401 {object} boolResponse @@ -438,7 +438,7 @@ func (app *appContext) TelegramVerifiedInvite(gc *gin.Context) { respondBool(200, ok, gc) } -// @Summary Returns true/false on whether or not a discord PIN was verified. Requires invite code. +// @Summary Returns true/false on whether or not a discord PIN was verified. Requires invite code. NOTE: "/invite" might have been changed in Settings > URL Paths. // @Produce json // @Success 200 {object} boolResponse // @Failure 401 {object} boolResponse @@ -462,7 +462,7 @@ func (app *appContext) DiscordVerifiedInvite(gc *gin.Context) { respondBool(200, ok, gc) } -// @Summary Returns a 10-minute, one-use Discord server invite +// @Summary Returns a 10-minute, one-use Discord server invite. NOTE: "/invite" might have been changed in Settings > URL Paths. // @Produce json // @Success 200 {object} DiscordInviteDTO // @Failure 400 {object} boolResponse @@ -489,7 +489,7 @@ func (app *appContext) DiscordServerInvite(gc *gin.Context) { gc.JSON(200, DiscordInviteDTO{invURL, iconURL}) } -// @Summary Generate and send a new PIN to a specified Matrix user. +// @Summary Generate and send a new PIN to a specified Matrix user. NOTE: "/invite" might have been changed in Settings > URL Paths. // @Produce json // @Success 200 {object} boolResponse // @Failure 400 {object} stringResponse @@ -528,7 +528,7 @@ func (app *appContext) MatrixSendPIN(gc *gin.Context) { respondBool(200, true, gc) } -// @Summary Check whether a matrix PIN is valid, and mark the token as verified if so. Requires invite code. +// @Summary Check whether a matrix PIN is valid, and mark the token as verified if so. Requires invite code. NOTE: "/invite" might have been changed in Settings > URL Paths. // @Produce json // @Success 200 {object} boolResponse // @Failure 401 {object} boolResponse diff --git a/api-userpage.go b/api-userpage.go index a7d9b77..f608fbb 100644 --- a/api-userpage.go +++ b/api-userpage.go @@ -164,9 +164,7 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) { var target ConfirmationTarget var id string fail := func() { - gcHTML(gc, 404, "404.html", gin.H{ - "cssClass": app.cssClass, - "cssVersion": cssVersion, + app.gcHTML(gc, 404, "404.html", OtherPage, gin.H{ "contactMessage": app.config.Section("ui").Key("contact_message").String(), }) } @@ -201,7 +199,7 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) { // Perform an Action if target == NoOp { - gc.Redirect(http.StatusSeeOther, "/my/account") + gc.Redirect(http.StatusSeeOther, PAGES.MyAccount) return } else if target == UserEmailChange { app.modifyEmail(id, claims["email"].(string)) @@ -216,7 +214,7 @@ func (app *appContext) confirmMyAction(gc *gin.Context, key string) { }, gc, true) app.info.Printf(lm.UserEmailAdjusted, gc.GetString("jfId")) - gc.Redirect(http.StatusSeeOther, "/my/account") + gc.Redirect(http.StatusSeeOther, PAGES.MyAccount) return } } diff --git a/config.go b/config.go index ff8a29e..4cb2bb7 100644 --- a/config.go +++ b/config.go @@ -22,6 +22,9 @@ var telegramEnabled = false var discordEnabled = false var matrixEnabled = false +// URL subpaths. Ignore the "Current" field. +var PAGES = PagePaths{} + func (app *appContext) GetPath(sect, key string) (fs.FS, string) { val := app.config.Section(sect).Key(key).MustString("") if strings.HasPrefix(val, "jfa-go:") { @@ -35,6 +38,13 @@ func (app *appContext) MustSetValue(section, key, val string) { app.config.Section(section).Key(key).SetValue(app.config.Section(section).Key(key).MustString(val)) } +func (app *appContext) MustSetURLPath(section, key, val string) { + if !strings.HasPrefix(val, "/") { + val = "/" + val + } + app.MustSetValue(section, key, val) +} + func (app *appContext) loadConfig() error { var err error app.config, err = ini.ShadowLoad(app.configPath) @@ -42,6 +52,13 @@ func (app *appContext) loadConfig() error { return err } + app.MustSetURLPath("url_paths", "admin", "/") + app.MustSetURLPath("url_paths", "user_page", "/my/account") + app.MustSetURLPath("url_paths", "form", "/invite") + PAGES.Admin = app.config.Section("url_paths").Key("admin").MustString("/") + PAGES.MyAccount = app.config.Section("url_paths").Key("user_page").MustString("/my/account") + PAGES.Form = app.config.Section("url_paths").Key("form").MustString("/invite") + app.MustSetValue("jellyfin", "public_server", app.config.Section("jellyfin").Key("server").String()) app.MustSetValue("ui", "redirect_url", app.config.Section("jellyfin").Key("public_server").String()) @@ -58,12 +75,12 @@ func (app *appContext) loadConfig() error { app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".db")))) } - app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/") - if app.URLBase == "/invite" || app.URLBase == "/accounts" || app.URLBase == "/settings" || app.URLBase == "/activity" { - app.err.Printf(lm.BadURLBase, app.URLBase) + PAGES.Base = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/") + if PAGES.Base == "/invite" || PAGES.Base == "/accounts" || PAGES.Base == "/settings" || PAGES.Base == "/activity" { + app.err.Printf(lm.BadURLBase, PAGES.Base) } app.ExternalURI = strings.TrimSuffix(strings.TrimSuffix(app.config.Section("ui").Key("jfa_url").MustString(""), "/invite"), "/") - if !strings.HasSuffix(app.ExternalURI, app.URLBase) { + if !strings.HasSuffix(app.ExternalURI, PAGES.Base) { app.err.Println(lm.NoURLSuffix) } if app.ExternalURI == "" { diff --git a/config/config-base.yaml b/config/config-base.yaml index d429010..2a17342 100644 --- a/config/config-base.yaml +++ b/config/config-base.yaml @@ -232,6 +232,33 @@ sections: - ["opaque", "Opaque"] value: clear description: Appearance of the Admin login screen. +- section: url_paths + meta: + name: URL Paths + description: Settings for changing where different pages are accessed. + advanced: true + settings: + - setting: admin + name: Admin page subpath + type: text + required: true + requires_restart: true + value: "/" + description: URL subpath the admin page should be at. + - setting: user_page + name: "\"My Account\" subpath" + type: text + required: true + requires_restart: true + value: "/my/account" + description: URL subpath the "My Account" page should be at. + - setting: form + name: Invite subpath + type: text + required: true + requires_restart: true + value: "/invite" + description: URL subpath invites should be on. - section: advanced meta: name: Advanced diff --git a/email.go b/email.go index 82da3f2..5201a33 100644 --- a/email.go +++ b/email.go @@ -329,7 +329,7 @@ func (emailer *Emailer) confirmationValues(code, username, key string, app *appC if code == "" { // Personal email change inviteLink = fmt.Sprintf("%s/my/confirm/%s", inviteLink, url.PathEscape(key)) } else { // Invite email confirmation - inviteLink = fmt.Sprintf("%s/invite/%s?key=%s", inviteLink, code, url.PathEscape(key)) + inviteLink = fmt.Sprintf("%s%s/%s?key=%s", inviteLink, PAGES.Form, code, url.PathEscape(key)) } template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": username}) template["confirmationURL"] = inviteLink @@ -393,7 +393,7 @@ func (emailer *Emailer) inviteValues(code string, invite Invite, app *appContext expiry := invite.ValidTill d, t, expiresIn := emailer.formatExpiry(expiry, false, app.datePattern, app.timePattern) message := app.config.Section("messages").Key("message").String() - inviteLink := fmt.Sprintf("%s/invite/%s", app.ExternalURI, code) + inviteLink := fmt.Sprintf("%s%s/%s", app.ExternalURI, PAGES.Form, code) template := map[string]interface{}{ "hello": emailer.lang.InviteEmail.get("hello"), "youHaveBeenInvited": emailer.lang.InviteEmail.get("youHaveBeenInvited"), diff --git a/html/404.html b/html/404.html index 22b0cbb..429ecba 100644 --- a/html/404.html +++ b/html/404.html @@ -1,9 +1,8 @@ - - {{ template "header.html" . }} 404 - jfa-go + {{ template "header.html" . }}
diff --git a/html/admin.html b/html/admin.html index bb10c4c..25630bd 100644 --- a/html/admin.html +++ b/html/admin.html @@ -1,16 +1,7 @@ - Admin - jfa-go @@ -47,7 +37,7 @@
- + diff --git a/html/crash.html b/html/crash.html index 44e4b43..e01329b 100644 --- a/html/crash.html +++ b/html/crash.html @@ -1,6 +1,7 @@ + {{ template "header.html" . }} Crash report diff --git a/html/create-success.html b/html/create-success.html index 19b7660..105424f 100644 --- a/html/create-success.html +++ b/html/create-success.html @@ -1,7 +1,6 @@ - {{ template "header.html" . }} {{ .strings.successHeader }} - jfa-go diff --git a/html/form-base.html b/html/form-base.html index f25bb2e..77e6a36 100644 --- a/html/form-base.html +++ b/html/form-base.html @@ -3,7 +3,6 @@ window.usernameEnabled = {{ .username }}; window.validationStrings = JSON.parse({{ .validationStrings }}); window.invalidPassword = "{{ .strings.reEnterPasswordInvalid }}"; - window.URLBase = "{{ .urlBase }}"; window.code = "{{ .code }}"; window.language = "{{ .langName }}"; window.messages = JSON.parse({{ .notifications }}); @@ -14,16 +13,13 @@ window.userExpiryHours = {{ .userExpiryHours }}; window.userExpiryMinutes = {{ .userExpiryMinutes }}; window.userExpiryMessage = {{ .userExpiryMessage }}; - window.telegramEnabled = {{ .telegramEnabled }}; window.telegramRequired = {{ .telegramRequired }}; window.telegramPIN = "{{ .telegramPIN }}"; window.emailRequired = {{ .emailRequired }}; - window.discordEnabled = {{ .discordEnabled }}; window.discordRequired = {{ .discordRequired }}; window.discordPIN = "{{ .discordPIN }}"; window.discordInviteLink = {{ .discordInviteLink }}; window.discordServerName = "{{ .discordServerName }}"; - window.matrixEnabled = {{ .matrixEnabled }}; window.matrixRequired = {{ .matrixRequired }}; window.matrixUserID = "{{ .matrixUser }}"; window.captcha = {{ .captcha }}; diff --git a/html/form.html b/html/form.html index 5199cad..336d21d 100644 --- a/html/form.html +++ b/html/form.html @@ -1,7 +1,6 @@ - {{ template "header.html" . }} {{ if .passwordReset }} {{ .strings.passwordReset }} diff --git a/html/header.html b/html/header.html index 6af2da8..b857990 100644 --- a/html/header.html +++ b/html/header.html @@ -1,13 +1,32 @@ + - - - - - + + + + + + diff --git a/html/invalidCode.html b/html/invalidCode.html index bc8de0f..611577b 100644 --- a/html/invalidCode.html +++ b/html/invalidCode.html @@ -1,7 +1,6 @@ - {{ template "header.html" . }} Invalid Code - jfa-go diff --git a/html/login-modal.html b/html/login-modal.html index 408e5c7..dde3998 100644 --- a/html/login-modal.html +++ b/html/login-modal.html @@ -15,7 +15,7 @@ {{ $hasTwoCards = 1 }}
{{ .strings.loginNotAdmin }} - {{ .strings.myAccount }} + {{ .strings.myAccount }}
{{ end }} {{ end }} diff --git a/html/password-reset.html b/html/password-reset.html index ebd75fd..4ffd558 100644 --- a/html/password-reset.html +++ b/html/password-reset.html @@ -1,7 +1,6 @@ - {{ template "header.html" . }} {{ .strings.passwordReset }} - jfa-go @@ -40,6 +39,6 @@ {{ .contactMessage }} - + diff --git a/html/setup.html b/html/setup.html index 0d53716..ffed526 100644 --- a/html/setup.html +++ b/html/setup.html @@ -1,7 +1,6 @@ - {{ template "header.html" . }} {{ .lang.Strings.pageTitle }} diff --git a/html/user.html b/html/user.html index 7e7484b..8de812b 100644 --- a/html/user.html +++ b/html/user.html @@ -1,31 +1,21 @@ - {{ template "header.html" . }} {{ .strings.myAccount }} @@ -156,7 +146,7 @@ {{ end }} - + diff --git a/main.go b/main.go index d2f881c..7d1de5a 100644 --- a/main.go +++ b/main.go @@ -103,37 +103,37 @@ type appContext struct { adminUsers []User invalidTokens []string // Keeping jf name because I can't think of a better one - jf *mediabrowser.MediaBrowser - authJf *mediabrowser.MediaBrowser - ombi *OmbiWrapper - js *JellyseerrWrapper - thirdPartyServices []ThirdPartyService - datePattern string - timePattern string - storage Storage - validator Validator - email *Emailer - telegram *TelegramDaemon - discord *DiscordDaemon - matrix *MatrixDaemon - contactMethods []ContactMethodLinker - info, debug, err *logger.Logger - host string - port int - version string - URLBase, ExternalURI, ExternalDomain string - updater *Updater - webhooks *WebhookSender - newUpdate bool // Whether whatever's in update is new. - tag Tag - update Update - proxyEnabled bool - proxyTransport *http.Transport - proxyConfig easyproxy.ProxyConfig - internalPWRs map[string]InternalPWR - pwrCaptchas map[string]Captcha - ConfirmationKeys map[string]map[string]ConfirmationKey // Map of invite code to jwt to request - confirmationKeysLock sync.Mutex + jf *mediabrowser.MediaBrowser + authJf *mediabrowser.MediaBrowser + ombi *OmbiWrapper + js *JellyseerrWrapper + thirdPartyServices []ThirdPartyService + datePattern string + timePattern string + storage Storage + validator Validator + email *Emailer + telegram *TelegramDaemon + discord *DiscordDaemon + matrix *MatrixDaemon + contactMethods []ContactMethodLinker + info, debug, err *logger.Logger + host string + port int + version string + ExternalURI, ExternalDomain string + updater *Updater + webhooks *WebhookSender + newUpdate bool // Whether whatever's in update is new. + tag Tag + update Update + proxyEnabled bool + proxyTransport *http.Transport + proxyConfig easyproxy.ProxyConfig + internalPWRs map[string]InternalPWR + pwrCaptchas map[string]Captcha + ConfirmationKeys map[string]map[string]ConfirmationKey // Map of invite code to jwt to request + confirmationKeysLock sync.Mutex } func generateSecret(length int) (string, error) { @@ -147,7 +147,7 @@ func generateSecret(length int) (string, error) { func test(app *appContext) { fmt.Printf("\n\n----\n\n") - settings := map[string]interface{}{ + settings := map[string]any{ "server": app.jf.Server, "server version": app.jf.ServerInfo.Version, "server name": app.jf.ServerInfo.Name, diff --git a/models.go b/models.go index 97e669b..686effd 100644 --- a/models.go +++ b/models.go @@ -467,3 +467,18 @@ type ContactMethodKey struct { PIN string User ContactMethodUser } + +type PagePaths struct { + // The base subfolder the app is hosted on. + Base string `json:"Base"` + // Those for other pages + Admin string `json:"Admin"` + MyAccount string `json:"MyAccount"` + Form string `json:"Form"` +} + +type PagePathsDTO struct { + PagePaths + // The subdirectory this bit of the app is hosted on (e.g. admin is usually on "/", myacc is usually on "/my/account") + Current string `json:"Current"` +} diff --git a/router.go b/router.go index 9edc32c..e88f49f 100644 --- a/router.go +++ b/router.go @@ -108,8 +108,8 @@ func (app *appContext) loadRouter(address string, debug bool) *gin.Engine { } func (app *appContext) loadRoutes(router *gin.Engine) { - routePrefixes := []string{app.URLBase} - if app.URLBase != "" { + routePrefixes := []string{PAGES.Base} + if PAGES.Base != "" { routePrefixes = append(routePrefixes, "") } @@ -118,7 +118,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) { for _, p := range routePrefixes { router.GET(p+"/lang/:page", app.GetLanguages) router.Use(static.Serve(p+"/", app.webFS)) - router.GET(p+"/", app.AdminPage) + router.GET(p+PAGES.Admin, app.AdminPage) if app.config.Section("password_resets").Key("link_reset").MustBool(false) { router.GET(p+"/reset", app.ResetPassword) @@ -127,39 +127,39 @@ func (app *appContext) loadRoutes(router *gin.Engine) { } } - router.GET(p+"/accounts", app.AdminPage) - router.GET(p+"/settings", app.AdminPage) - router.GET(p+"/activity", app.AdminPage) - router.GET(p+"/accounts/user/:userID", app.AdminPage) - router.GET(p+"/invites/:code", app.AdminPage) + router.GET(p+PAGES.Admin+"/accounts", app.AdminPage) + router.GET(p+PAGES.Admin+"/settings", app.AdminPage) + router.GET(p+PAGES.Admin+"/activity", app.AdminPage) + router.GET(p+PAGES.Admin+"/accounts/user/:userID", app.AdminPage) + router.GET(p+PAGES.Admin+"/invites/:code", app.AdminPage) router.GET(p+"/lang/:page/:file", app.ServeLang) router.GET(p+"/token/login", app.getTokenLogin) router.GET(p+"/token/refresh", app.getTokenRefresh) router.POST(p+"/user/invite", app.NewUserFromInvite) - router.Use(static.Serve(p+"/invite/", app.webFS)) - router.GET(p+"/invite/:invCode", app.InviteProxy) + router.Use(static.Serve(p+PAGES.Form, app.webFS)) + router.GET(p+PAGES.Form+"/:invCode", app.InviteProxy) if app.config.Section("captcha").Key("enabled").MustBool(false) { router.GET(p+"/captcha/gen/:invCode", app.GenCaptcha) router.GET(p+"/captcha/img/:invCode/:captchaID", app.GetCaptcha) router.POST(p+"/captcha/verify/:invCode/:captchaID/:text", app.VerifyCaptcha) } if telegramEnabled { - router.GET(p+"/invite/:invCode/telegram/verified/:pin", app.TelegramVerifiedInvite) + router.GET(p+PAGES.Form+"/:invCode/telegram/verified/:pin", app.TelegramVerifiedInvite) } if discordEnabled { - router.GET(p+"/invite/:invCode/discord/verified/:pin", app.DiscordVerifiedInvite) + router.GET(p+PAGES.Form+"/:invCode/discord/verified/:pin", app.DiscordVerifiedInvite) if app.config.Section("discord").Key("provide_invite").MustBool(false) { - router.GET(p+"/invite/:invCode/discord/invite", app.DiscordServerInvite) + router.GET(p+PAGES.Form+"/:invCode/discord/invite", app.DiscordServerInvite) } } if matrixEnabled { - router.GET(p+"/invite/:invCode/matrix/verified/:userID/:pin", app.MatrixCheckPIN) - router.POST(p+"/invite/:invCode/matrix/user", app.MatrixSendPIN) + router.GET(p+PAGES.Form+"/:invCode/matrix/verified/:userID/:pin", app.MatrixCheckPIN) + router.POST(p+PAGES.Form+"/:invCode/matrix/user", app.MatrixSendPIN) router.POST(p+"/users/matrix", app.MatrixConnect) } if userPageEnabled { - router.GET(p+"/my/account", app.MyUserPage) - router.GET(p+"/my/account/password/reset", app.MyUserPage) + router.GET(p+PAGES.MyAccount, app.MyUserPage) + router.GET(p+PAGES.MyAccount+"/password/reset", app.MyUserPage) router.GET(p+"/my/token/login", app.getUserTokenLogin) router.GET(p+"/my/token/refresh", app.getUserTokenRefresh) router.GET(p+"/my/confirm/:jwt", app.ConfirmMyAction) diff --git a/setup.go b/setup.go index bebba74..5d2f1ab 100644 --- a/setup.go +++ b/setup.go @@ -39,8 +39,10 @@ func (app *appContext) ServeSetup(gc *gin.Context) { respond(500, "Failed to fetch default values", gc) return } + pages := PagePathsDTO{PagePaths: PAGES} gc.HTML(200, "setup.html", gin.H{ "cssVersion": cssVersion, + "pages": pages, "lang": app.storage.lang.Setup[lang], "strings": app.storage.lang.Setup[lang].Strings, "emailLang": app.storage.lang.Email[emailLang], diff --git a/ts/admin.ts b/ts/admin.ts index 814fa37..090a8e4 100644 --- a/ts/admin.ts +++ b/ts/admin.ts @@ -11,6 +11,8 @@ import { _get, _post, notificationBox, whichAnimationEvent, bindManualDropdowns import { Updater } from "./modules/update.js"; import { Login } from "./modules/login.js"; +declare var window: GlobalWindow; + const theme = new ThemeManager(document.getElementById("button-theme")); window.lang = new lang(window.langFile as LangFile); @@ -165,12 +167,12 @@ const defaultTab = tabs[0]; window.tabs = new Tabs(); for (let tab of tabs) { - window.tabs.addTab(tab.id, tab.url, null, tab.reloader); + window.tabs.addTab(tab.id, window.pages.Admin + "/" + tab.url, null, tab.reloader); } let matchedTab = false -for (let tab of tabs) { - if (window.location.pathname.startsWith(window.URLBase + "/" + tab.url)) { +for (const tab of tabs) { + if (window.location.pathname.startsWith(window.pages.Base + window.pages.Current + "/" + tab.url)) { window.tabs.switch(tab.url, true); matchedTab = true; } diff --git a/ts/form.ts b/ts/form.ts index 6552040..7ec413c 100644 --- a/ts/form.ts +++ b/ts/form.ts @@ -6,7 +6,7 @@ import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator. import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js"; import { Captcha, GreCAPTCHA } from "./modules/captcha.js"; -interface formWindow extends Window { +interface formWindow extends GlobalWindow { invalidPassword: string; successModal: Modal; telegramModal: Modal; @@ -59,7 +59,7 @@ if (window.telegramEnabled) { modal: window.telegramModal as Modal, pin: window.telegramPIN, pinURL: "", - verifiedURL: "/invite/" + window.code + "/telegram/verified/", + verifiedURL: window.pages.Form + "/" + window.code + "/telegram/verified/", invalidCodeError: window.messages["errorInvalidPIN"], accountLinkedError: window.messages["errorAccountLinked"], successError: window.messages["verified"], @@ -89,9 +89,9 @@ if (window.discordEnabled) { const discordConf: ServiceConfiguration = { modal: window.discordModal as Modal, pin: window.discordPIN, - inviteURL: window.discordInviteLink ? ("/invite/" + window.code + "/discord/invite") : "", + inviteURL: window.discordInviteLink ? (window.pages.Form + "/" + window.code + "/discord/invite") : "", pinURL: "", - verifiedURL: "/invite/" + window.code + "/discord/verified/", + verifiedURL: window.pages.Form + "/" + window.code + "/discord/verified/", invalidCodeError: window.messages["errorInvalidPIN"], accountLinkedError: window.messages["errorAccountLinked"], successError: window.messages["verified"], @@ -121,8 +121,8 @@ if (window.matrixEnabled) { const matrixConf: MatrixConfiguration = { modal: window.matrixModal as Modal, - sendMessageURL: "/invite/" + window.code + "/matrix/user", - verifiedURL: "/invite/" + window.code + "/matrix/verified/", + sendMessageURL: window.pages.Form + "/" + window.code + "/matrix/user", + verifiedURL: window.pages.Form + "/" + window.code + "/matrix/verified/", invalidCodeError: window.messages["errorInvalidPIN"], accountLinkedError: window.messages["errorAccountLinked"], unknownError: window.messages["errorUnknown"], diff --git a/ts/modules/account-linking.ts b/ts/modules/account-linking.ts index 301c3cb..effc511 100644 --- a/ts/modules/account-linking.ts +++ b/ts/modules/account-linking.ts @@ -1,7 +1,7 @@ import { Modal } from "../modules/modal.js"; import { _get, _post, toggleLoader, addLoader, removeLoader } from "../modules/common.js"; -interface formWindow extends Window { +interface formWindow extends GlobalWindow { invalidPassword: string; successModal: Modal; telegramModal: Modal; diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts index ab88af0..c338bfc 100644 --- a/ts/modules/accounts.ts +++ b/ts/modules/accounts.ts @@ -6,6 +6,8 @@ import { DiscordUser, newDiscordSearch } from "../modules/discord.js"; import { Search, SearchConfiguration, QueryType, SearchableItem } from "../modules/search.js"; import { HiddenInputField } from "./ui.js"; +declare var window: GlobalWindow; + const dateParser = require("any-date-parser"); interface User { diff --git a/ts/modules/activity.ts b/ts/modules/activity.ts index d54972f..1049390 100644 --- a/ts/modules/activity.ts +++ b/ts/modules/activity.ts @@ -3,6 +3,8 @@ import { Search, SearchConfiguration, QueryType, SearchableItem } from "../modul import { accountURLEvent } from "../modules/accounts.js"; import { inviteURLEvent } from "../modules/invites.js"; +declare var window: GlobalWindow; + export interface activity { id: string; type: string; @@ -52,8 +54,8 @@ export class Activity implements activity, SearchableItem { link = link.split(split)[0]; } if (link.slice(-1) != "/") { link += "/"; } - // FIXME: I should probably just be using window.URLBase, but incase thats not right, i'll put this warning here - if (link != window.URLBase) console.error(`URL Bases don't match: "${link}" != "${window.URLBase}"`); + // FIXME: I should probably just be using window.pages.Base, but incase thats not right, i'll put this warning here + if (link != window.pages.Base) console.error(`URL Bases don't match: "${link}" != "${window.pages.Base}"`); return link; })(); */ @@ -66,17 +68,17 @@ export class Activity implements activity, SearchableItem { } _genUserLink = (): string => { - return `${this._genUserText()}`; + return `${this._genUserText()}`; } _genSrcUserLink = (): string => { - return `${this._genSrcUserText()}`; + return `${this._genSrcUserText()}`; } private _renderInvText = (): string => { return `${this.value || this.invite_code || "???"}`; } private _genInvLink = (): string => { - return `${this._renderInvText()}`; + return `${this._renderInvText()}`; } diff --git a/ts/modules/common.ts b/ts/modules/common.ts index d87498a..40b5ddd 100644 --- a/ts/modules/common.ts +++ b/ts/modules/common.ts @@ -1,4 +1,4 @@ -declare var window: Window; +declare var window: GlobalWindow; export function toDateString(date: Date): string { const locale = window.language || (window as any).navigator.userLanguage || window.navigator.language; @@ -23,7 +23,7 @@ export function toDateString(date: Date): string { export const _get = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => { let req = new XMLHttpRequest(); - if (window.URLBase) { url = window.URLBase + url; } + if (window.pages) { url = window.pages.Base + url; } req.open("GET", url, true); req.responseType = 'json'; req.setRequestHeader("Authorization", "Bearer " + window.token); @@ -42,7 +42,7 @@ export const _get = (url: string, data: Object, onreadystatechange: (req: XMLHtt export const _download = (url: string, fname: string): void => { let req = new XMLHttpRequest(); - if (window.URLBase) { url = window.URLBase + url; } + if (window.pages) { url = window.pages.Base + url; } req.open("GET", url, true); req.responseType = 'blob'; req.setRequestHeader("Authorization", "Bearer " + window.token); @@ -58,7 +58,7 @@ export const _download = (url: string, fname: string): void => { export const _upload = (url: string, formData: FormData): void => { let req = new XMLHttpRequest(); - if (window.URLBase) { url = window.URLBase + url; } + if (window.pages) { url = window.pages.Base + url; } req.open("POST", url, true); req.setRequestHeader("Authorization", "Bearer " + window.token); // req.setRequestHeader('Content-Type', 'multipart/form-data'); @@ -67,7 +67,8 @@ export const _upload = (url: string, formData: FormData): void => { export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean, statusHandler?: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void => { let req = new XMLHttpRequest(); - req.open("POST", window.URLBase + url, true); + if (window.pages) { url = window.pages.Base + url; } + req.open("POST", url, true); if (response) { req.responseType = 'json'; } @@ -88,7 +89,8 @@ export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHt export function _delete(url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, noConnectionError: boolean = false): void { let req = new XMLHttpRequest(); - req.open("DELETE", window.URLBase + url, true); + if (window.pages) { url = window.pages.Base + url; } + req.open("DELETE", url, true); req.setRequestHeader("Authorization", "Bearer " + window.token); req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); req.onreadystatechange = () => { diff --git a/ts/modules/discord.ts b/ts/modules/discord.ts index 89e8a8c..f773ccf 100644 --- a/ts/modules/discord.ts +++ b/ts/modules/discord.ts @@ -1,5 +1,7 @@ import {addLoader, removeLoader, _get} from "../modules/common.js"; +declare var window: GlobalWindow; + export interface DiscordUser { name: string; avatar_url: string; diff --git a/ts/modules/invites.ts b/ts/modules/invites.ts index c9b759c..4c34a90 100644 --- a/ts/modules/invites.ts +++ b/ts/modules/invites.ts @@ -2,6 +2,8 @@ import { _get, _post, _delete, toClipboard, toggleLoader, toDateString } from ". import { DiscordUser, newDiscordSearch } from "../modules/discord.js"; import { reloadProfileNames } from "../modules/profiles.js"; +declare var window: GlobalWindow; + class DOMInvite implements Invite { updateNotify = (checkbox: HTMLInputElement) => { let state: { [code: string]: { [type: string]: boolean } } = {}; @@ -66,7 +68,7 @@ class DOMInvite implements Invite { codeLink = codeLink.split(split)[0]; } if (codeLink.slice(-1) != "/") { codeLink += "/"; } - this._codeLink = codeLink + "invite/" + code; + this._codeLink = codeLink + window.pages.Form + "/" + code; const linkEl = this._codeArea.querySelector("a") as HTMLAnchorElement; if (this.label == "") { linkEl.textContent = code.replace(/-/g, '-'); diff --git a/ts/modules/login.ts b/ts/modules/login.ts index 71cd590..11d09bd 100644 --- a/ts/modules/login.ts +++ b/ts/modules/login.ts @@ -1,6 +1,8 @@ import { Modal } from "../modules/modal.js"; import { toggleLoader, _post, unicodeB64Encode } from "../modules/common.js"; +declare var window: GlobalWindow; + export class Login { loggedIn: boolean = false; private _modal: Modal; @@ -14,7 +16,7 @@ export class Login { constructor(modal: Modal, endpoint: string, appearance: string) { this._endpoint = endpoint; - this._url = window.URLBase + endpoint; + this._url = window.pages.Base + endpoint; if (this._url[this._url.length-1] != '/') this._url += "/"; this._modal = modal; diff --git a/ts/modules/modal.ts b/ts/modules/modal.ts index 77cbb4e..92e3435 100644 --- a/ts/modules/modal.ts +++ b/ts/modules/modal.ts @@ -1,4 +1,4 @@ -declare var window: Window; +declare var window: GlobalWindow; export class Modal implements Modal { modal: HTMLElement; diff --git a/ts/modules/pages.ts b/ts/modules/pages.ts index 686f771..edf21f3 100644 --- a/ts/modules/pages.ts +++ b/ts/modules/pages.ts @@ -73,6 +73,7 @@ export class PageManager { } loadPage (p: Page) { + console.log("loading page with", p.name || this.defaultName, p.title, p.url + window.location.search); window.history.pushState(p.name || this.defaultName, p.title, p.url + window.location.search); } diff --git a/ts/modules/profiles.ts b/ts/modules/profiles.ts index e58fb27..51d6eb3 100644 --- a/ts/modules/profiles.ts +++ b/ts/modules/profiles.ts @@ -1,5 +1,7 @@ import { _get, _post, _delete, toggleLoader } from "../modules/common.js"; +declare var window: GlobalWindow; + export const profileLoadEvent = new CustomEvent("profileLoadEvent"); export const reloadProfileNames = (then?: () => void) => _get("/profiles/names", null, (req: XMLHttpRequest) => { if (req.readyState != 4) return; diff --git a/ts/modules/search.ts b/ts/modules/search.ts index dd01dbe..fa8d1e1 100644 --- a/ts/modules/search.ts +++ b/ts/modules/search.ts @@ -1,5 +1,7 @@ const dateParser = require("any-date-parser"); +declare var window: GlobalWindow; + export interface QueryType { name: string; description?: string; diff --git a/ts/modules/settings.ts b/ts/modules/settings.ts index c9d1459..a1a43f8 100644 --- a/ts/modules/settings.ts +++ b/ts/modules/settings.ts @@ -2,6 +2,8 @@ import { _get, _post, _delete, _download, _upload, toggleLoader, addLoader, remo import { Marked } from "@ts-stack/markdown"; import { stripMarkdown } from "../modules/stripmd.js"; +declare var window: GlobalWindow; + const toBool = (s: string): boolean => { let b = Boolean(s); if (s == "false") b = false; diff --git a/ts/modules/tabs.ts b/ts/modules/tabs.ts index 628ed7e..3f7d78b 100644 --- a/ts/modules/tabs.ts +++ b/ts/modules/tabs.ts @@ -1,5 +1,7 @@ import { PageManager, Page } from "../modules/pages.js"; +declare var window: GlobalWindow; + export interface Tab { page: Page; tabEl: HTMLDivElement; @@ -38,7 +40,7 @@ export class Tabs implements Tabs { tab.page = { name: tabID, title: document.title, /*FIXME: Get actual names from translations*/ - url: window.URLBase + "/" + url, + url: url, show: () => { tab.buttonEl.classList.add("active", "~urge"); tab.tabEl.classList.remove("unfocused"); diff --git a/ts/modules/update.ts b/ts/modules/update.ts index ba2c946..c39fc63 100644 --- a/ts/modules/update.ts +++ b/ts/modules/update.ts @@ -1,6 +1,8 @@ import { _get, _post, toggleLoader, toDateString } from "../modules/common.js"; import { Marked, Renderer } from "@ts-stack/markdown"; +declare var window: GlobalWindow; + interface updateDTO { new: boolean; update: Update; diff --git a/ts/pwr-pin.ts b/ts/pwr-pin.ts index 8774967..a274a58 100644 --- a/ts/pwr-pin.ts +++ b/ts/pwr-pin.ts @@ -1,5 +1,7 @@ import { toClipboard, notificationBox } from "./modules/common.js"; +declare var window: GlobalWindow; + const pin = document.getElementById("pin") as HTMLSpanElement; if (pin) { diff --git a/ts/setup.ts b/ts/setup.ts index 747ed83..d987060 100644 --- a/ts/setup.ts +++ b/ts/setup.ts @@ -3,12 +3,11 @@ import { lang, LangFile, loadLangSelector } from "./modules/lang.js"; import { ThemeManager } from "./modules/theme.js"; import { PageManager } from "./modules/pages.js"; -interface sWindow extends Window { +interface sWindow extends GlobalWindow { messages: {}; } declare var window: sWindow; -window.URLBase = ""; const theme = new ThemeManager(document.getElementById("button-theme")); diff --git a/ts/typings/d.ts b/ts/typings/d.ts index a53a3fb..b19a1c7 100644 --- a/ts/typings/d.ts +++ b/ts/typings/d.ts @@ -12,8 +12,19 @@ interface ArrayConstructor { from(arrayLike: any, mapFn?, thisArg?): Array; } -declare interface Window { - URLBase: string; +declare interface PagePaths { + // The base subfolder the app is hosted on. + Base: string; + // The subdirectory this bit of the app is hosted on (e.g. admin is usually on "/", myacc is usually on "/my/account") + Current: string; + // Those for other pages + Admin: string; + MyAccount: string; + Form: string; +} + +declare interface GlobalWindow extends Window { + pages: PagePaths; modals: Modals; cssFile: string; availableProfiles: string[]; @@ -25,6 +36,7 @@ declare interface Window { matrixEnabled: boolean; ombiEnabled: boolean; jellyseerrEnabled: boolean; + pwrEnabled: boolean; usernameEnabled: boolean; linkResetEnabled: boolean; token: string; diff --git a/ts/user.ts b/ts/user.ts index b72b22b..cceb19e 100644 --- a/ts/user.ts +++ b/ts/user.ts @@ -7,7 +7,7 @@ import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js"; import { PageManager } from "./modules/pages.js"; -interface userWindow extends Window { +interface userWindow extends GlobalWindow { jellyfinID: string; username: string; emailRequired: boolean; @@ -18,14 +18,13 @@ interface userWindow extends Window { discordInviteLink: boolean; matrixUserID: string; discordSendPINMessage: string; - pwrEnabled: string; referralsEnabled: boolean; } -const basePath = window.location.pathname.replace("/password/reset", ""); - declare var window: userWindow; +const basePath = window.location.pathname.replace("/password/reset", ""); + const theme = new ThemeManager(document.getElementById("button-theme")); window.lang = new lang(window.langFile as LangFile); @@ -38,7 +37,7 @@ window.token = ""; window.modals = {} as Modals; -let pages = new PageManager({ +const pages = new PageManager({ hideOthersOnPageShow: true, defaultName: "", defaultTitle: document.title, @@ -311,7 +310,7 @@ class ReferralCard { path = path.split(split)[0]; } if (path.slice(-1) != "/") { path += "/"; } - path = path + "invite/" + this._code; + path = path + window.pages.Form + "/" + this._code; u.pathname = path; u.hash = ""; @@ -661,7 +660,7 @@ document.addEventListener("details-reload", () => { expiryCard.expiry = details.expiry; const adminBackButton = document.getElementById("admin-back-button") as HTMLAnchorElement; - adminBackButton.href = window.location.href.replace("my/account", ""); + adminBackButton.href = window.location.href.replace(window.pages.MyAccount, window.pages.Admin); let messageCard = document.getElementById("card-message"); if (details.accounts_admin) { diff --git a/user-auth.go b/user-auth.go index 5af2320..c80e213 100644 --- a/user-auth.go +++ b/user-auth.go @@ -67,7 +67,8 @@ func (app *appContext) getUserTokenLogin(gc *gin.Context) { // host := gc.Request.URL.Hostname() host := app.ExternalDomain uri := "/my" - if strings.HasPrefix(gc.Request.RequestURI, app.URLBase) { + // FIXME: This seems like a bad idea? I think it's to deal with people having Reverse proxy subfolder/URL base set to /accounts. + if strings.HasPrefix(gc.Request.RequestURI, PAGES.Base) { uri = "/accounts/my" } gc.SetCookie("user-refresh", refresh, REFRESH_TOKEN_VALIDITY_SEC, uri, host, true, true) diff --git a/views.go b/views.go index 7542606..be574b4 100644 --- a/views.go +++ b/views.go @@ -32,7 +32,7 @@ func (app *appContext) loadCSSHeader() string { l := len(css) h := "" for i, f := range css { - h += "<" + app.URLBase + "/css/" + f + ">; rel=preload; as=style" + h += "<" + PAGES.Base + "/css/" + f + ">; rel=preload; as=style" if l > 1 && i != (l-1) { h += ", " } @@ -41,18 +41,19 @@ func (app *appContext) loadCSSHeader() string { } func (app *appContext) getURLBase(gc *gin.Context) string { - if strings.HasPrefix(gc.Request.URL.String(), app.URLBase) { + if strings.HasPrefix(gc.Request.URL.String(), PAGES.Base) { // Hack to fix the common URL base /accounts - if app.URLBase == "/accounts" && strings.HasPrefix(gc.Request.URL.String(), "/accounts/user/") { + if PAGES.Base == "/accounts" && strings.HasPrefix(gc.Request.URL.String(), "/accounts/user/") { return "" } - return app.URLBase + return PAGES.Base } return "" } -func gcHTML(gc *gin.Context, code int, file string, templ gin.H) { +func (app *appContext) gcHTML(gc *gin.Context, code int, file string, page Page, templ gin.H) { gc.Header("Cache-Control", "no-cache") + app.BasePageTemplateValues(gc, page, templ) gc.HTML(code, file, templ) } @@ -61,16 +62,14 @@ func (app *appContext) pushResources(gc *gin.Context, page Page) { switch page { case AdminPage: toPush = []string{"/js/admin.js", "/js/theme.js", "/js/lang.js", "/js/modal.js", "/js/tabs.js", "/js/invites.js", "/js/accounts.js", "/js/settings.js", "/js/profiles.js", "/js/common.js"} - break case UserPage: toPush = []string{"/js/user.js", "/js/theme.js", "/js/lang.js", "/js/modal.js", "/js/common.js"} - break default: toPush = []string{} } if pusher := gc.Writer.Pusher(); pusher != nil { for _, f := range toPush { - if err := pusher.Push(app.URLBase+f, nil); err != nil { + if err := pusher.Push(PAGES.Base+f, nil); err != nil { app.debug.Printf(lm.FailedServerPush, err) } } @@ -78,6 +77,48 @@ func (app *appContext) pushResources(gc *gin.Context, page Page) { gc.Header("Link", cssHeader) } +// Returns a gin.H with general values (url base, css version, etc.) +func (app *appContext) BasePageTemplateValues(gc *gin.Context, page Page, base gin.H) { + set := func(k string, v any) { + if _, ok := base[k]; !ok { + base[k] = v + } + } + + pages := PagePathsDTO{ + PagePaths: PAGES, + } + pages.Base = app.getURLBase(gc) + switch page { + case AdminPage: + pages.Current = PAGES.Admin + case FormPage: + pages.Current = PAGES.Form + case UserPage: + pages.Current = PAGES.MyAccount + default: + pages.Current = "/" + } + set("pages", pages) + ombiEnabled := app.config.Section("ombi").Key("enabled").MustBool(false) + jellyseerrEnabled := app.config.Section("jellyseerr").Key("enabled").MustBool(false) + notificationsEnabled, _ := app.config.Section("notifications").Key("enabled").Bool() + set("notifications", notificationsEnabled) + set("cssClass", app.cssClass) + set("cssVersion", cssVersion) + set("emailEnabled", emailEnabled) + set("telegramEnabled", telegramEnabled) + set("discordEnabled", discordEnabled) + set("matrixEnabled", matrixEnabled) + set("ombiEnabled", ombiEnabled) + set("jellyseerrEnabled", jellyseerrEnabled) + // QUIRK: The login modal html template uses this' existence to check if the modal is for the admin or user page. + if page != AdminPage { + set("pwrEnabled", app.config.Section("password_resets").Key("enabled").MustBool(false)) + } + set("referralsEnabled", app.config.Section("user_page").Key("enabled").MustBool(false) && app.config.Section("user_page").Key("referrals").MustBool(false)) +} + type Page int const ( @@ -132,10 +173,6 @@ func (app *appContext) getLang(gc *gin.Context, page Page, chosen string) string func (app *appContext) AdminPage(gc *gin.Context) { app.pushResources(gc, AdminPage) lang := app.getLang(gc, AdminPage, app.storage.lang.chosenAdminLang) - 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) - jellyseerrEnabled := app.config.Section("jellyseerr").Key("enabled").MustBool(false) jfAdminOnly := app.config.Section("ui").Key("admin_only").MustBool(true) jfAllowAll := app.config.Section("ui").Key("allow_all").MustBool(false) var license string @@ -157,62 +194,36 @@ func (app *appContext) AdminPage(gc *gin.Context) { builtBy = "???" } - gcHTML(gc, http.StatusOK, "admin.html", gin.H{ - "urlBase": app.getURLBase(gc), - "cssClass": app.cssClass, - "cssVersion": cssVersion, - "contactMessage": "", - "emailEnabled": emailEnabled, - "telegramEnabled": telegramEnabled, - "discordEnabled": discordEnabled, - "matrixEnabled": matrixEnabled, - "ombiEnabled": ombiEnabled, - "jellyseerrEnabled": jellyseerrEnabled, - "linkResetEnabled": app.config.Section("password_resets").Key("link_reset").MustBool(false), - "notifications": notificationsEnabled, - "version": version, - "commit": commit, - "buildTime": buildTime, - "builtBy": builtBy, - "buildTags": buildTags, - "username": !app.config.Section("email").Key("no_username").MustBool(false), - "strings": app.storage.lang.Admin[lang].Strings, - "quantityStrings": app.storage.lang.Admin[lang].QuantityStrings, - "language": app.storage.lang.Admin[lang].JSON, - "langName": lang, - "license": license, - "jellyfinLogin": app.jellyfinLogin, - "jfAdminOnly": jfAdminOnly, - "jfAllowAll": jfAllowAll, - "userPageEnabled": app.config.Section("user_page").Key("enabled").MustBool(false), - "showUserPageLink": app.config.Section("user_page").Key("show_link").MustBool(true), - "referralsEnabled": app.config.Section("user_page").Key("enabled").MustBool(false) && app.config.Section("user_page").Key("referrals").MustBool(false), - "loginAppearance": app.config.Section("ui").Key("login_appearance").MustString("clear"), + app.gcHTML(gc, http.StatusOK, "admin.html", AdminPage, gin.H{ + "contactMessage": "", + "linkResetEnabled": app.config.Section("password_resets").Key("link_reset").MustBool(false), + "version": version, + "commit": commit, + "buildTime": buildTime, + "builtBy": builtBy, + "buildTags": buildTags, + "username": !app.config.Section("email").Key("no_username").MustBool(false), + "strings": app.storage.lang.Admin[lang].Strings, + "quantityStrings": app.storage.lang.Admin[lang].QuantityStrings, + "language": app.storage.lang.Admin[lang].JSON, + "langName": lang, + "license": license, + "jellyfinLogin": app.jellyfinLogin, + "jfAdminOnly": jfAdminOnly, + "jfAllowAll": jfAllowAll, + "userPageEnabled": app.config.Section("user_page").Key("enabled").MustBool(false), + "showUserPageLink": app.config.Section("user_page").Key("show_link").MustBool(true), + "loginAppearance": app.config.Section("ui").Key("login_appearance").MustString("clear"), }) } func (app *appContext) MyUserPage(gc *gin.Context) { app.pushResources(gc, UserPage) lang := app.getLang(gc, UserPage, app.storage.lang.chosenUserLang) - 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) - jellyseerrEnabled := app.config.Section("jellyseerr").Key("enabled").MustBool(false) data := gin.H{ - "urlBase": app.getURLBase(gc), - "cssClass": app.cssClass, - "cssVersion": cssVersion, "contactMessage": app.config.Section("ui").Key("contact_message").String(), - "emailEnabled": emailEnabled, "emailRequired": app.config.Section("email").Key("required").MustBool(false), - "telegramEnabled": telegramEnabled, - "discordEnabled": discordEnabled, - "matrixEnabled": matrixEnabled, - "ombiEnabled": ombiEnabled, - "jellyseerrEnabled": jellyseerrEnabled, - "pwrEnabled": app.config.Section("password_resets").Key("enabled").MustBool(false), "linkResetEnabled": app.config.Section("password_resets").Key("link_reset").MustBool(false), - "notifications": notificationsEnabled, "username": !app.config.Section("email").Key("no_username").MustBool(false), "strings": app.storage.lang.User[lang].Strings, "validationStrings": app.storage.lang.User[lang].validationStringsJSON, @@ -220,7 +231,6 @@ func (app *appContext) MyUserPage(gc *gin.Context) { "langName": lang, "jfLink": app.config.Section("ui").Key("redirect_url").String(), "requirements": app.validator.getCriteria(), - "referralsEnabled": app.config.Section("user_page").Key("enabled").MustBool(false) && app.config.Section("user_page").Key("referrals").MustBool(false), } if telegramEnabled { data["telegramUsername"] = app.telegram.username @@ -264,7 +274,7 @@ func (app *appContext) MyUserPage(gc *gin.Context) { data[name+"MessageContent"] = template.HTML(markdown.ToHTML([]byte(msg.Content), nil, markdownRenderer)) } - gcHTML(gc, http.StatusOK, "user.html", data) + app.gcHTML(gc, http.StatusOK, "user.html", UserPage, data) } func (app *appContext) ResetPassword(gc *gin.Context) { @@ -278,14 +288,9 @@ func (app *appContext) ResetPassword(gc *gin.Context) { app.pushResources(gc, PWRPage) lang := app.getLang(gc, PWRPage, app.storage.lang.chosenPWRLang) data := gin.H{ - "urlBase": app.getURLBase(gc), - "cssClass": app.cssClass, - "cssVersion": cssVersion, "contactMessage": app.config.Section("ui").Key("contact_message").String(), "strings": app.storage.lang.PasswordReset[lang].Strings, "success": false, - "ombiEnabled": app.config.Section("ombi").Key("enabled").MustBool(false), - "jellyseerrEnabled": app.config.Section("jellyseerr").Key("enabled").MustBool(false), "customSuccessCard": false, } pwr, isInternal := app.internalPWRs[pin] @@ -299,6 +304,7 @@ func (app *appContext) ResetPassword(gc *gin.Context) { data["requirements"] = app.validator.getCriteria() data["strings"] = app.storage.lang.PasswordReset[lang].Strings data["validationStrings"] = app.storage.lang.User[lang].validationStringsJSON + // ewwwww, reusing an existing field, FIXME! data["notifications"] = app.storage.lang.User[lang].notificationsJSON data["langName"] = lang data["passwordReset"] = true @@ -309,10 +315,10 @@ func (app *appContext) ResetPassword(gc *gin.Context) { data["reCAPTCHA"] = app.config.Section("captcha").Key("recaptcha").MustBool(false) data["reCAPTCHASiteKey"] = app.config.Section("captcha").Key("recaptcha_site_key").MustString("") data["pwrPIN"] = pin - gcHTML(gc, http.StatusOK, "form-loader.html", data) + app.gcHTML(gc, http.StatusOK, "form-loader.html", PWRPage, data) return } - defer gcHTML(gc, http.StatusOK, "password-reset.html", data) + defer app.gcHTML(gc, http.StatusOK, "password-reset.html", PWRPage, data) // If it's a bot, pretend to be a success so the preview is nice. if isBot { app.debug.Println(lm.IgnoreBotPWR) @@ -413,10 +419,7 @@ func (app *appContext) GetCaptcha(gc *gin.Context) { if !isPWR { inv, ok = app.storage.GetInvitesKey(code) if !ok { - gcHTML(gc, 404, "invalidCode.html", gin.H{ - "urlBase": app.getURLBase(gc), - "cssClass": app.cssClass, - "cssVersion": cssVersion, + app.gcHTML(gc, 404, "invalidCode.html", OtherPage, gin.H{ "contactMessage": app.config.Section("ui").Key("contact_message").String(), }) } @@ -453,10 +456,7 @@ func (app *appContext) GenCaptcha(gc *gin.Context) { } if !ok { - gcHTML(gc, 404, "invalidCode.html", gin.H{ - "urlBase": app.getURLBase(gc), - "cssClass": app.cssClass, - "cssVersion": cssVersion, + app.gcHTML(gc, 404, "invalidCode.html", OtherPage, gin.H{ "contactMessage": app.config.Section("ui").Key("contact_message").String(), }) } @@ -587,10 +587,7 @@ func (app *appContext) VerifyCaptcha(gc *gin.Context) { if !isPWR { inv, ok = app.storage.GetInvitesKey(code) if !ok { - gcHTML(gc, 404, "invalidCode.html", gin.H{ - "urlBase": app.getURLBase(gc), - "cssClass": app.cssClass, - "cssVersion": cssVersion, + app.gcHTML(gc, 404, "invalidCode.html", OtherPage, gin.H{ "contactMessage": app.config.Section("ui").Key("contact_message").String(), }) return @@ -617,10 +614,7 @@ func (app *appContext) VerifyCaptcha(gc *gin.Context) { func (app *appContext) NewUserFromConfirmationKey(invite Invite, key string, lang string, gc *gin.Context) { fail := func() { - gcHTML(gc, 404, "404.html", gin.H{ - "urlBase": app.getURLBase(gc), - "cssClass": app.cssClass, - "cssVersion": cssVersion, + app.gcHTML(gc, 404, "404.html", OtherPage, gin.H{ "contactMessage": app.config.Section("ui").Key("contact_message").String(), }) } @@ -691,10 +685,7 @@ func (app *appContext) NewUserFromConfirmationKey(invite Invite, key string, lan if app.config.Section("ui").Key("auto_redirect").MustBool(false) { gc.Redirect(301, jfLink) } else { - gcHTML(gc, http.StatusOK, "create-success.html", gin.H{ - "urlBase": app.getURLBase(gc), - "cssClass": app.cssClass, - "cssVersion": cssVersion, + app.gcHTML(gc, http.StatusOK, "create-success.html", OtherPage, gin.H{ "strings": app.storage.lang.User[lang].Strings, "successMessage": app.config.Section("ui").Key("success_message").String(), "contactMessage": app.config.Section("ui").Key("contact_message").String(), @@ -725,10 +716,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) { // if app.checkInvite(code, false, "") { invite, ok := app.storage.GetInvitesKey(gc.Param("invCode")) if !ok { - gcHTML(gc, 404, "invalidCode.html", gin.H{ - "urlBase": app.getURLBase(gc), - "cssClass": app.cssClass, - "cssVersion": cssVersion, + app.gcHTML(gc, 404, "invalidCode.html", FormPage, gin.H{ "contactMessage": app.config.Section("ui").Key("contact_message").String(), }) return @@ -747,7 +735,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) { discord := discordEnabled && app.config.Section("discord").Key("show_on_reg").MustBool(true) matrix := matrixEnabled && app.config.Section("matrix").Key("show_on_reg").MustBool(true) - userPageAddress := fmt.Sprintf("%s/my/account", app.ExternalURI) + userPageAddress := app.ExternalURI + PAGES.MyAccount fromUser := "" if invite.ReferrerJellyfinID != "" { @@ -758,9 +746,6 @@ func (app *appContext) InviteProxy(gc *gin.Context) { } data := gin.H{ - "urlBase": app.getURLBase(gc), - "cssClass": app.cssClass, - "cssVersion": cssVersion, "contactMessage": app.config.Section("ui").Key("contact_message").String(), "helpMessage": app.config.Section("ui").Key("help_message").String(), "successMessage": app.config.Section("ui").Key("success_message").String(), @@ -772,28 +757,29 @@ func (app *appContext) InviteProxy(gc *gin.Context) { "username": !app.config.Section("email").Key("no_username").MustBool(false), "strings": app.storage.lang.User[lang].Strings, "validationStrings": app.storage.lang.User[lang].validationStringsJSON, - "notifications": app.storage.lang.User[lang].notificationsJSON, - "code": invite.Code, - "confirmation": app.config.Section("email_confirmation").Key("enabled").MustBool(false), - "userExpiry": invite.UserExpiry, - "userExpiryMonths": invite.UserMonths, - "userExpiryDays": invite.UserDays, - "userExpiryHours": invite.UserHours, - "userExpiryMinutes": invite.UserMinutes, - "userExpiryMessage": app.storage.lang.User[lang].Strings.get("yourAccountIsValidUntil"), - "langName": lang, - "passwordReset": false, - "customSuccessCard": false, - "telegramEnabled": telegram, - "discordEnabled": discord, - "matrixEnabled": matrix, - "emailRequired": app.config.Section("email").Key("required").MustBool(false), - "captcha": app.config.Section("captcha").Key("enabled").MustBool(false), - "reCAPTCHA": app.config.Section("captcha").Key("recaptcha").MustBool(false), - "reCAPTCHASiteKey": app.config.Section("captcha").Key("recaptcha_site_key").MustString(""), - "userPageEnabled": app.config.Section("user_page").Key("enabled").MustBool(false), - "userPageAddress": userPageAddress, - "fromUser": fromUser, + // ewwwww, reusing an existing field, FIXME! + "notifications": app.storage.lang.User[lang].notificationsJSON, + "code": invite.Code, + "confirmation": app.config.Section("email_confirmation").Key("enabled").MustBool(false), + "userExpiry": invite.UserExpiry, + "userExpiryMonths": invite.UserMonths, + "userExpiryDays": invite.UserDays, + "userExpiryHours": invite.UserHours, + "userExpiryMinutes": invite.UserMinutes, + "userExpiryMessage": app.storage.lang.User[lang].Strings.get("yourAccountIsValidUntil"), + "langName": lang, + "passwordReset": false, + "customSuccessCard": false, + "telegramEnabled": telegram, + "discordEnabled": discord, + "matrixEnabled": matrix, + "emailRequired": app.config.Section("email").Key("required").MustBool(false), + "captcha": app.config.Section("captcha").Key("enabled").MustBool(false), + "reCAPTCHA": app.config.Section("captcha").Key("recaptcha").MustBool(false), + "reCAPTCHASiteKey": app.config.Section("captcha").Key("recaptcha_site_key").MustString(""), + "userPageEnabled": app.config.Section("user_page").Key("enabled").MustBool(false), + "userPageAddress": userPageAddress, + "fromUser": fromUser, } if telegram { data["telegramPIN"] = app.telegram.NewAuthToken() @@ -837,15 +823,12 @@ func (app *appContext) InviteProxy(gc *gin.Context) { // pin := "" // for _, token := range app.discord.tokens { // if - gcHTML(gc, http.StatusOK, "form-loader.html", data) + app.gcHTML(gc, http.StatusOK, "form-loader.html", OtherPage, data) } func (app *appContext) NoRouteHandler(gc *gin.Context) { app.pushResources(gc, OtherPage) - gcHTML(gc, 404, "404.html", gin.H{ - "urlBase": app.getURLBase(gc), - "cssClass": app.cssClass, - "cssVersion": cssVersion, + app.gcHTML(gc, 404, "404.html", OtherPage, gin.H{ "contactMessage": app.config.Section("ui").Key("contact_message").String(), }) }