1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2024-12-22 17:10:10 +00:00

config: migrate "url_base" dupes to "jfa_url"

URL Base now refers to JUST the subfolder portion, i.e. `/accounts` if
you access jfa-go at `http://jellyf.in/accounts`. General > "jfa_url"/"External
jfa-go URL" now refers to the WHOLE URL you access jfa-go at, i.e.
`http://jellyf.in/accounts`. The settings in "invite emails" and
"password resets" have been removed, and a value chosen from the two
applied to "jfa_url". Migration also makes a config backup. Adds a
"deprecated" flag to config-base, which just tells the UI to not show
it (for now). Also added some warnings related to the URL base /
External URL.
This commit is contained in:
Harvey Tindall 2024-08-12 18:53:46 +01:00
parent 0e7245e6b9
commit e71d492495
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
14 changed files with 127 additions and 75 deletions

9
api.go
View File

@ -322,15 +322,6 @@ func (app *appContext) GetConfig(gc *gin.Context) {
resp.Sections["discord"].Settings["language"] = tl resp.Sections["discord"].Settings["language"] = tl
resp.Sections["matrix"].Settings["language"] = tl resp.Sections["matrix"].Settings["language"] = tl
// if setting := resp.Sections["invite_emails"].Settings["url_base"]; setting.Value == "" {
// setting.Value = strings.TrimSuffix(resp.Sections["password_resets"].Settings["url_base"].Value.(string), "/invite")
// resp.Sections["invite_emails"].Settings["url_base"] = setting
// }
// if setting := resp.Sections["password_resets"].Settings["url_base"]; setting.Value == "" {
// setting.Value = strings.TrimSuffix(resp.Sections["invite_emails"].Settings["url_base"].Value.(string), "/invite")
// resp.Sections["password_resets"].Settings["url_base"] = setting
// }
gc.JSON(200, resp) gc.JSON(200, resp)
} }

View File

@ -55,7 +55,16 @@ func (app *appContext) loadConfig() error {
for _, key := range []string{"matrix_sql"} { for _, key := range []string{"matrix_sql"} {
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".db")))) 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(""), "/") 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)
}
app.ExternalHost = strings.TrimSuffix(strings.TrimSuffix(app.config.Section("ui").Key("jfa_url").MustString(""), "/invite"), "/")
if !strings.HasSuffix(app.ExternalHost, app.URLBase) {
app.err.Println(lm.NoURLSuffix)
}
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.MustSetValue("password_resets", "email_html", "jfa-go:"+"email.html") app.MustSetValue("password_resets", "email_html", "jfa-go:"+"email.html")
@ -128,12 +137,6 @@ func (app *appContext) loadConfig() error {
LOGIP = app.config.Section("advanced").Key("log_ips").MustBool(false) LOGIP = app.config.Section("advanced").Key("log_ips").MustBool(false)
LOGIPU = app.config.Section("advanced").Key("log_ips_users").MustBool(false) LOGIPU = app.config.Section("advanced").Key("log_ips_users").MustBool(false)
// These two settings are pretty much the same
url1 := app.config.Section("invite_emails").Key("url_base").String()
url2 := app.config.Section("password_resets").Key("url_base").String()
app.MustSetValue("password_resets", "url_base", strings.TrimSuffix(url1, "/invite"))
app.MustSetValue("invite_emails", "url_base", url2)
pwrMethods := []string{"allow_pwr_username", "allow_pwr_email", "allow_pwr_contact_method"} pwrMethods := []string{"allow_pwr_username", "allow_pwr_email", "allow_pwr_contact_method"}
allDisabled := true allDisabled := true
for _, v := range pwrMethods { for _, v := range pwrMethods {

View File

@ -257,6 +257,15 @@
"value": "", "value": "",
"description": "URL base for when running jfa-go with a reverse proxy in a subfolder. include preceding /, e.g \"/accounts\"." "description": "URL base for when running jfa-go with a reverse proxy in a subfolder. include preceding /, e.g \"/accounts\"."
}, },
"jfa_url": {
"name": "External jfa-go URL",
"required": true,
"requires_restart": false,
"depends_true": "enabled",
"type": "text",
"value": "http://accounts.jellyf.in:8056",
"description": "The URL at which the jfa-go root (admin page) is accessible, including the subfolder if you use one. This is necessary because using a reverse proxy means the program has no way of knowing the URL itself."
},
"redirect_url": { "redirect_url": {
"name": "Form success redirect URL", "name": "Form success redirect URL",
"required": false, "required": false,
@ -1375,13 +1384,22 @@
"description": "Instead of automatically setting the user's password to the PIN, allow them to set a new password through the reset link." "description": "Instead of automatically setting the user's password to the PIN, allow them to set a new password through the reset link."
}, },
"url_base": { "url_base": {
"name": "URL Base", "name": "External jfa-go URL",
"required": true, "required": true,
"requires_restart": false, "requires_restart": false,
"depends_true": "link_reset", "depends_true": "link_reset",
"type": "text", "type": "text",
"value": "http://accounts.jellyf.in:8056", "value": "http://accounts.jellyf.in:8056",
"description": "Base URL for jfa-go. You can leave this if you have one set in \"Invite Emails\". This is necessary because using a reverse proxy means the program has no way of knowing the URL itself." "description": "The URL at which the jfa-go admin page is accessible, including the subfolder if you use one. You can leave this if you have one set in \"Invite Emails\". This is necessary because using a reverse proxy means the program has no way of knowing the URL itself.",
"deprecated": true
},
"jfa_url": {
"name": "Generating Reset Links:",
"type": "note",
"value": "",
"depends_true": "link_reset",
"required": "false",
"description": "Set the \"External jfa-go URL\" in General so that links to jfa-go can be made."
}, },
"language": { "language": {
"name": "Default reset link language", "name": "Default reset link language",
@ -1471,13 +1489,22 @@
"description": "Subject of invite emails." "description": "Subject of invite emails."
}, },
"url_base": { "url_base": {
"name": "URL Base", "name": "External jfa-go URL",
"required": true, "required": true,
"requires_restart": false, "requires_restart": false,
"depends_true": "enabled", "depends_true": "enabled",
"type": "text", "type": "text",
"value": "http://accounts.jellyf.in:8056", "value": "http://accounts.jellyf.in:8056",
"description": "Base URL for jfa-go. This is necessary because using a reverse proxy means the program has no way of knowing the URL itself." "description": "The URL at which the jfa-go root (admin page) is accessible, including the subfolder if you use one. You can leave this if you have one set in \"Invite Emails\". This is necessary because using a reverse proxy means the program has no way of knowing the URL itself.",
"deprecated": true
},
"jfa_url": {
"name": "Generating Links:",
"type": "note",
"value": "",
"depends_true": "enabled",
"required": "false",
"description": "Set the \"External jfa-go URL\" in General so that links to jfa-go can be made."
} }
} }
}, },

View File

@ -325,17 +325,11 @@ func (emailer *Emailer) confirmationValues(code, username, key string, app *appC
} }
} else { } else {
message := app.config.Section("messages").Key("message").String() message := app.config.Section("messages").Key("message").String()
inviteLink := app.config.Section("invite_emails").Key("url_base").String() inviteLink := app.ExternalHost
if code == "" { // Personal email change if code == "" { // Personal email change
if strings.HasSuffix(inviteLink, "/invite") {
inviteLink = strings.TrimSuffix(inviteLink, "/invite")
}
inviteLink = fmt.Sprintf("%s/my/confirm/%s", inviteLink, url.PathEscape(key)) inviteLink = fmt.Sprintf("%s/my/confirm/%s", inviteLink, url.PathEscape(key))
} else { // Invite email confirmation } else { // Invite email confirmation
if !strings.HasSuffix(inviteLink, "/invite") { inviteLink = fmt.Sprintf("%s/invite/%s?key=%s", inviteLink, code, url.PathEscape(key))
inviteLink += "/invite"
}
inviteLink = fmt.Sprintf("%s/%s?key=%s", inviteLink, code, url.PathEscape(key))
} }
template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": username}) template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": username})
template["confirmationURL"] = inviteLink template["confirmationURL"] = inviteLink
@ -399,11 +393,7 @@ func (emailer *Emailer) inviteValues(code string, invite Invite, app *appContext
expiry := invite.ValidTill expiry := invite.ValidTill
d, t, expiresIn := emailer.formatExpiry(expiry, false, app.datePattern, app.timePattern) d, t, expiresIn := emailer.formatExpiry(expiry, false, app.datePattern, app.timePattern)
message := app.config.Section("messages").Key("message").String() message := app.config.Section("messages").Key("message").String()
inviteLink := app.config.Section("invite_emails").Key("url_base").String() inviteLink := fmt.Sprintf("%s/invite/%s", app.ExternalHost, code)
if !strings.HasSuffix(inviteLink, "/invite") {
inviteLink += "/invite"
}
inviteLink = fmt.Sprintf("%s/%s", inviteLink, code)
template := map[string]interface{}{ template := map[string]interface{}{
"hello": emailer.lang.InviteEmail.get("hello"), "hello": emailer.lang.InviteEmail.get("hello"),
"youHaveBeenInvited": emailer.lang.InviteEmail.get("youHaveBeenInvited"), "youHaveBeenInvited": emailer.lang.InviteEmail.get("youHaveBeenInvited"),

View File

@ -110,9 +110,14 @@
</label> </label>
<label class="label"> <label class="label">
<span class="mt-4">{{ .lang.General.urlBase }} ({{ .lang.Strings.optional }})</span> <span class="mt-4">{{ .lang.General.urlBase }} ({{ .lang.Strings.optional }})</span>
<input type="url" class="input ~neutral @low mt-4" id="ui-url_base"> <input type="text" class="input ~neutral @low mt-4" id="ui-url_base" placeholder="/mysubfolder">
<p class="support mb-2 mt-1">{{ .lang.General.urlBaseNotice }}</p> <p class="support mb-2 mt-1">{{ .lang.General.urlBaseNotice }}</p>
</label> </label>
<label class="label">
<span class="mt-4">{{ .lang.General.externalURL }}</span>
<input type="text" class="input ~neutral @low mt-4" id="ui-jfa_url" placeholder="https://jellyf.in/mysubfolder">
<p class="support mb-2 mt-1">{{ .lang.General.externalURLNotice }}</p>
</label>
<label class="label"> <label class="label">
<span>{{ .lang.Strings.theme }}</span> <span>{{ .lang.Strings.theme }}</span>
<div class="select ~neutral @low mt-4 mb-2"> <div class="select ~neutral @low mt-4 mb-2">
@ -423,10 +428,6 @@
<label class="row switch pb-4"> <label class="row switch pb-4">
<input type="checkbox" class="mr-2" id="invite_emails-enabled"><span>{{ .lang.Strings.enabled }}</span> <input type="checkbox" class="mr-2" id="invite_emails-enabled"><span>{{ .lang.Strings.enabled }}</span>
</label> </label>
<label class="label">
<span class="mt-4">{{ .lang.Strings.URL }}</span>
<input type="url" class="input ~neutral @low mt-4 mb-2" id="invite_emails-url_base" placeholder="https://accounts.jellyf.in/invite">
</label>
<label class="label"> <label class="label">
<span class="mt-4">{{ .lang.Strings.emailSubject }}</span> <span class="mt-4">{{ .lang.Strings.emailSubject }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="invite_emails-subject" placeholder="{{ .emailLang.InviteEmail.title }}"> <input type="text" class="input ~neutral @low mt-4 mb-2" id="invite_emails-subject" placeholder="{{ .emailLang.InviteEmail.title }}">

View File

@ -48,6 +48,8 @@
"listenAddress": "Listen Address", "listenAddress": "Listen Address",
"urlBase": "URL Base", "urlBase": "URL Base",
"urlBaseNotice": "Only needed if using a reverse proxy on a subfolder (e.g 'jellyf.in/accounts').", "urlBaseNotice": "Only needed if using a reverse proxy on a subfolder (e.g 'jellyf.in/accounts').",
"externalURL": "External jfa-go URL",
"externalURLNotice": "The URL you'll be accessing jfa-go from. Used to generate links for things like password resets. Make sure to include the above URL base if you set one.",
"lightTheme": "Light", "lightTheme": "Light",
"darkTheme": "Dark", "darkTheme": "Dark",
"useHTTPS": "Use HTTPS", "useHTTPS": "Use HTTPS",

View File

@ -207,6 +207,9 @@ const (
EnableAllPWRMethods = "No PWR method preferences set in [user_page], all will be enabled" EnableAllPWRMethods = "No PWR method preferences set in [user_page], all will be enabled"
InitProxy = "Initialized proxy @ \"%s\"" InitProxy = "Initialized proxy @ \"%s\""
FailedInitProxy = "Failed to initialize proxy @ \"%s\": %v\nStartup will pause for a bit to grab your attention." FailedInitProxy = "Failed to initialize proxy @ \"%s\": %v\nStartup will pause for a bit to grab your attention."
NoURLSuffix = `Warning: Given "jfa_url"/"External jfa-go URL" value does not include "url_base" value!`
BadURLBase = `Warning: Given URL Base "%s" may conflict with the applications subpaths.`
NoExternalHost = `No "External jfa-go URL" provided, set one in Settings > General.`
// discord.go // discord.go
StartDaemon = "Started %s daemon" StartDaemon = "Started %s daemon"

60
main.go
View File

@ -101,36 +101,36 @@ type appContext struct {
adminUsers []User adminUsers []User
invalidTokens []string invalidTokens []string
// Keeping jf name because I can't think of a better one // Keeping jf name because I can't think of a better one
jf *mediabrowser.MediaBrowser jf *mediabrowser.MediaBrowser
authJf *mediabrowser.MediaBrowser authJf *mediabrowser.MediaBrowser
ombi *OmbiWrapper ombi *OmbiWrapper
js *JellyseerrWrapper js *JellyseerrWrapper
thirdPartyServices []ThirdPartyService thirdPartyServices []ThirdPartyService
datePattern string datePattern string
timePattern string timePattern string
storage Storage storage Storage
validator Validator validator Validator
email *Emailer email *Emailer
telegram *TelegramDaemon telegram *TelegramDaemon
discord *DiscordDaemon discord *DiscordDaemon
matrix *MatrixDaemon matrix *MatrixDaemon
contactMethods []ContactMethodLinker contactMethods []ContactMethodLinker
info, debug, err *logger.Logger info, debug, err *logger.Logger
host string host string
port int port int
version string version string
URLBase string URLBase, ExternalHost string
updater *Updater updater *Updater
newUpdate bool // Whether whatever's in update is new. newUpdate bool // Whether whatever's in update is new.
tag Tag tag Tag
update Update update Update
proxyEnabled bool proxyEnabled bool
proxyTransport *http.Transport proxyTransport *http.Transport
proxyConfig easyproxy.ProxyConfig proxyConfig easyproxy.ProxyConfig
internalPWRs map[string]InternalPWR internalPWRs map[string]InternalPWR
pwrCaptchas map[string]Captcha pwrCaptchas map[string]Captcha
ConfirmationKeys map[string]map[string]newUserDTO // Map of invite code to jwt to request ConfirmationKeys map[string]map[string]newUserDTO // Map of invite code to jwt to request
confirmationKeysLock sync.Mutex confirmationKeysLock sync.Mutex
} }
func generateSecret(length int) (string, error) { func generateSecret(length int) (string, error) {

View File

@ -14,6 +14,7 @@ import (
func runMigrations(app *appContext) { func runMigrations(app *appContext) {
migrateProfiles(app) migrateProfiles(app)
migrateBootstrap(app) migrateBootstrap(app)
migrateExternalURL(app)
migrateEmailStorage(app) migrateEmailStorage(app)
migrateNotificationMethods(app) migrateNotificationMethods(app)
linkExistingOmbiDiscordTelegram(app) linkExistingOmbiDiscordTelegram(app)
@ -463,3 +464,37 @@ func intialiseCustomContent(app *appContext) {
// } // }
// } // }
// } // }
// Migrate poorly-named and duplicate "url_base" settings to the single "external jfa-go URL" setting.
func migrateExternalURL(app *appContext) {
tempConfig, _ := ini.Load(app.configPath)
err := tempConfig.SaveTo(app.configPath + "_" + commit + ".bak")
if err != nil {
app.err.Fatalf("Failed to backup config: %v", err)
return
}
url1 := app.config.Section("password_resets").Key("url_base").String()
url2 := app.config.Section("invite_emails").Key("url_base").String()
if tempConfig.Section("ui").Key("jfa_url").String() != "" || (url1 == "" && url2 == "") {
return
}
preferred := url1
// the PWR setting (url1) is preferred, as it has always been defined as the URL root, while
// the invite email setting (url2) once asked for "/invite" at the end.
if url1 == "" {
preferred = strings.TrimSuffix(url2, "/invite")
}
fmt.Println(warning("The duplicate URL Base settings in \"Invite emails\" and \"Password Resets\" have been merged into General > External jfa-go URL. A backup config has been made."))
tempConfig.Section("ui").Key("jfa_url").SetValue(preferred)
app.config.Section("password_resets").DeleteKey("url_base")
app.config.Section("invite_emails").DeleteKey("url_base")
err = tempConfig.SaveTo(app.configPath)
if err != nil {
app.err.Fatalf("Failed to save new config: %v", err)
return
}
}

View File

@ -229,6 +229,7 @@ type setting struct {
DependsTrue string `json:"depends_true,omitempty"` // If specified, this field is enabled when the specified bool setting is enabled. DependsTrue string `json:"depends_true,omitempty"` // If specified, this field is enabled when the specified bool setting is enabled.
DependsFalse string `json:"depends_false,omitempty"` // If specified, opposite behaviour of DependsTrue. DependsFalse string `json:"depends_false,omitempty"` // If specified, opposite behaviour of DependsTrue.
Style string `json:"style,omitempty"` Style string `json:"style,omitempty"`
Deprecated bool `json:"deprecated,omitempty"`
} }
type section struct { type section struct {

View File

@ -2,6 +2,7 @@ package main
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"os" "os"
"strings" "strings"
@ -29,10 +30,10 @@ func (app *appContext) GenInternalReset(userID string) (InternalPWR, error) {
// GenResetLink generates and returns a password reset link. // GenResetLink generates and returns a password reset link.
func (app *appContext) GenResetLink(pin string) (string, error) { func (app *appContext) GenResetLink(pin string) (string, error) {
url := app.config.Section("password_resets").Key("url_base").String() url := app.ExternalHost
var pinLink string var pinLink string
if url == "" { if url == "" {
return pinLink, fmt.Errorf("disabled as no URL Base provided. Set in Settings > Password Resets.") return pinLink, errors.New(lm.NoExternalHost)
} }
// Strip /invite from end of this URL, ik it's ugly. // Strip /invite from end of this URL, ik it's ugly.
pinLink = fmt.Sprintf("%s/reset?pin=%s", url, pin) pinLink = fmt.Sprintf("%s/reset?pin=%s", url, pin)

View File

@ -33,6 +33,7 @@ interface Setting {
depends_true?: string; depends_true?: string;
depends_false?: string; depends_false?: string;
wiki_link?: string; wiki_link?: string;
deprecated?: boolean;
asElement: () => HTMLElement; asElement: () => HTMLElement;
update: (s: Setting) => void; update: (s: Setting) => void;
@ -579,6 +580,7 @@ class sectionPanel {
if (name in this._settings) { if (name in this._settings) {
this._settings[name].update(setting); this._settings[name].update(setting);
} else { } else {
if (setting.deprecated) continue;
switch (setting.type) { switch (setting.type) {
case "text": case "text":
setting = new DOMText(setting, this._sectionName, name); setting = new DOMText(setting, this._sectionName, name);

View File

@ -249,6 +249,7 @@ const settings = {
"host": new Input(get("ui-host")), "host": new Input(get("ui-host")),
"port": new Input(get("ui-port")), "port": new Input(get("ui-port")),
"url_base": new Input(get("ui-url_base")), "url_base": new Input(get("ui-url_base")),
"jfa_url": new Input(get("ui-jfa_url")),
"theme": new Select(get("ui-theme")), "theme": new Select(get("ui-theme")),
"language-form": new LangSelect("form", get("ui-language-form")), "language-form": new LangSelect("form", get("ui-language-form")),
"language-admin": new LangSelect("admin", get("ui-language-admin")), "language-admin": new LangSelect("admin", get("ui-language-admin")),
@ -304,7 +305,6 @@ const settings = {
"invite_emails": { "invite_emails": {
"enabled": new Checkbox(get("invite_emails-enabled"), "", false, "invite_emails", "enabled"), "enabled": new Checkbox(get("invite_emails-enabled"), "", false, "invite_emails", "enabled"),
"subject": new Input(get("invite_emails-subject"), "", "", "enabled", true, "invite_emails"), "subject": new Input(get("invite_emails-subject"), "", "", "enabled", true, "invite_emails"),
"url_base": new Input(get("invite_emails-url_base"), "", "", "enabled", true, "invite_emails")
}, },
"mailgun": { "mailgun": {
"api_url": new Input(get("mailgun-api_url")), "api_url": new Input(get("mailgun-api_url")),

View File

@ -740,11 +740,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
discord := discordEnabled && app.config.Section("discord").Key("show_on_reg").MustBool(true) discord := discordEnabled && app.config.Section("discord").Key("show_on_reg").MustBool(true)
matrix := matrixEnabled && app.config.Section("matrix").Key("show_on_reg").MustBool(true) matrix := matrixEnabled && app.config.Section("matrix").Key("show_on_reg").MustBool(true)
userPageAddress := app.config.Section("invite_emails").Key("url_base").String() userPageAddress := fmt.Sprintf("%s/my/account", app.ExternalHost)
if userPageAddress == "" {
userPageAddress = app.config.Section("password_resets").Key("url_base").String()
}
userPageAddress += "/my/account"
fromUser := "" fromUser := ""
if invite.ReferrerJellyfinID != "" { if invite.ReferrerJellyfinID != "" {