http: add "Use reverse proxy host" option
added "Use reverse-proxy reported "Host" when possible" option, which will prefer using the "Host" or "X-Forwarded-Host" values instead of "External jfa-go URL" in the web app. To do so, app.ExternalDomain/URI are now functions which take *gin.Context (the latter optionally). The protocol for the request is determined from X-Forwarded-Proto(col), so make sure your proxy includes it. The wiki will have been updated to mention the new option.
This commit is contained in:
@@ -266,8 +266,7 @@ func (app *appContext) getTokenLogin(gc *gin.Context) {
|
|||||||
respond(500, "Couldn't generate token", gc)
|
respond(500, "Couldn't generate token", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// host := gc.Request.URL.Hostname()
|
host := app.ExternalDomain(gc)
|
||||||
host := app.ExternalDomain
|
|
||||||
|
|
||||||
// Before you think this is broken: the first "true" arg is for "secure", i.e. only HTTPS!
|
// Before you think this is broken: the first "true" arg is for "secure", i.e. only HTTPS!
|
||||||
gc.SetCookie("refresh", refresh, REFRESH_TOKEN_VALIDITY_SEC, "/", host, true, true)
|
gc.SetCookie("refresh", refresh, REFRESH_TOKEN_VALIDITY_SEC, "/", host, true, true)
|
||||||
@@ -329,7 +328,7 @@ func (app *appContext) getTokenRefresh(gc *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// host := gc.Request.URL.Hostname()
|
// host := gc.Request.URL.Hostname()
|
||||||
host := app.ExternalDomain
|
host := app.ExternalDomain(gc)
|
||||||
gc.SetCookie("refresh", refresh, REFRESH_TOKEN_VALIDITY_SEC, "/", host, true, true)
|
gc.SetCookie("refresh", refresh, REFRESH_TOKEN_VALIDITY_SEC, "/", host, true, true)
|
||||||
gc.JSON(200, getTokenDTO{jwt})
|
gc.JSON(200, getTokenDTO{jwt})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/hrfee/jfa-go/common"
|
"github.com/hrfee/jfa-go/common"
|
||||||
"github.com/hrfee/jfa-go/easyproxy"
|
"github.com/hrfee/jfa-go/easyproxy"
|
||||||
lm "github.com/hrfee/jfa-go/logmessages"
|
lm "github.com/hrfee/jfa-go/logmessages"
|
||||||
@@ -68,6 +69,35 @@ func (app *appContext) MustCorrectURL(section, key, value string) {
|
|||||||
app.config.Section(section).Key(key).SetValue(v)
|
app.config.Section(section).Key(key).SetValue(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ExternalDomain returns the Host for the request, using the fixed app.externalDomain value unless app.UseProxyHost is true.
|
||||||
|
func (app *appContext) ExternalDomain(gc *gin.Context) string {
|
||||||
|
if !app.UseProxyHost || gc.Request.Host == "" {
|
||||||
|
return app.externalDomain
|
||||||
|
}
|
||||||
|
return gc.Request.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExternalURI returns the External URI of jfa-go's root directory (by default, where the admin page is), using the fixed app.externalURI value unless app.UseProxyHost is true and gc is not nil.
|
||||||
|
// When nil is passed, app.externalURI is returned.
|
||||||
|
func (app *appContext) ExternalURI(gc *gin.Context) string {
|
||||||
|
if gc == nil {
|
||||||
|
return app.externalURI
|
||||||
|
}
|
||||||
|
|
||||||
|
var proto string
|
||||||
|
if gc.Request.TLS != nil || gc.Request.Header.Get("X-Forwarded-Proto") == "https" || gc.Request.Header.Get("X-Forwarded-Protocol") == "https" {
|
||||||
|
proto = "https://"
|
||||||
|
} else {
|
||||||
|
proto = "http://"
|
||||||
|
}
|
||||||
|
|
||||||
|
// app.debug.Printf("Request: %+v\n", gc.Request)
|
||||||
|
if app.UseProxyHost && gc.Request.Host != "" {
|
||||||
|
return proto + gc.Request.Host + PAGES.Base
|
||||||
|
}
|
||||||
|
return app.externalURI
|
||||||
|
}
|
||||||
|
|
||||||
func (app *appContext) loadConfig() error {
|
func (app *appContext) loadConfig() error {
|
||||||
var err error
|
var err error
|
||||||
app.config, err = ini.ShadowLoad(app.configPath)
|
app.config, err = ini.ShadowLoad(app.configPath)
|
||||||
@@ -108,16 +138,22 @@ 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.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".db"))))
|
||||||
}
|
}
|
||||||
|
|
||||||
app.ExternalURI = strings.TrimSuffix(strings.TrimSuffix(app.config.Section("ui").Key("jfa_url").MustString(""), "/invite"), "/")
|
// If true, app.ExternalDomain() will return one based on the reported Host (ideally reported in "Host" or "X-Forwarded-Host" by the reverse proxy), falling back to app.externalDomain if not set.
|
||||||
if !strings.HasSuffix(app.ExternalURI, PAGES.Base) {
|
app.UseProxyHost = app.config.Section("ui").Key("use_proxy_host").MustBool(false)
|
||||||
|
app.externalURI = strings.TrimSuffix(strings.TrimSuffix(app.config.Section("ui").Key("jfa_url").MustString(""), "/invite"), "/")
|
||||||
|
if !strings.HasSuffix(app.externalURI, PAGES.Base) {
|
||||||
app.err.Println(lm.NoURLSuffix)
|
app.err.Println(lm.NoURLSuffix)
|
||||||
}
|
}
|
||||||
if app.ExternalURI == "" {
|
if app.externalURI == "" {
|
||||||
app.err.Println(lm.NoExternalHost + lm.LoginWontSave)
|
if app.UseProxyHost {
|
||||||
|
app.err.Println(lm.NoExternalHost + lm.LoginWontSave + lm.SetExternalHostDespiteUseProxyHost)
|
||||||
|
} else {
|
||||||
|
app.err.Println(lm.NoExternalHost + lm.LoginWontSave)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
u, err := url.Parse(app.ExternalURI)
|
u, err := url.Parse(app.externalURI)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
app.ExternalDomain = u.Hostname()
|
app.externalDomain = u.Hostname()
|
||||||
}
|
}
|
||||||
|
|
||||||
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)))
|
||||||
|
|||||||
@@ -215,12 +215,15 @@ sections:
|
|||||||
- setting: jfa_url
|
- setting: jfa_url
|
||||||
name: External jfa-go URL
|
name: External jfa-go URL
|
||||||
required: true
|
required: true
|
||||||
depends_true: enabled
|
|
||||||
type: text
|
type: text
|
||||||
value: http://accounts.jellyf.in:8056
|
value: http://accounts.jellyf.in:8056
|
||||||
description: The URL at which the jfa-go root (usually the admin page) is accessible, including
|
description: The URL at which the jfa-go root (usually the admin page) is accessible, including
|
||||||
the subfolder if you use one. This is necessary because using a reverse proxy
|
the subfolder if you use one. While your reverse proxy should report this anyway, server-side actions like sending invite messages don't receive such wisdom.
|
||||||
means the program has no way of knowing the URL itself.
|
- setting: use_proxy_host
|
||||||
|
name: Use reverse-proxy reported "Host" when possible
|
||||||
|
type: bool
|
||||||
|
value: false
|
||||||
|
description: If enabled, the "Host" reported by your reverse proxy will be used in the web app, rather than the "External jfa-go URL" value. Useful if you regularly access jfa-go from more than one host/domain. Also, make sure your proxy passes X-Forwarded-Proto/X-Forwarded-Protocol.
|
||||||
- setting: url_base
|
- setting: url_base
|
||||||
name: Reverse Proxy subfolder
|
name: Reverse Proxy subfolder
|
||||||
requires_restart: true
|
requires_restart: true
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ 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.ExternalURI
|
inviteLink := app.ExternalURI(nil)
|
||||||
if code == "" { // Personal email change
|
if code == "" { // Personal email change
|
||||||
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
|
||||||
@@ -394,7 +394,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 := fmt.Sprintf("%s%s/%s", app.ExternalURI, PAGES.Form, code)
|
inviteLink := fmt.Sprintf("%s%s/%s", app.ExternalURI(nil), PAGES.Form, 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"),
|
||||||
|
|||||||
@@ -207,15 +207,16 @@ const (
|
|||||||
FailedRestoreDB = "Failed to resotre database from \"%s\": %v"
|
FailedRestoreDB = "Failed to resotre database from \"%s\": %v"
|
||||||
|
|
||||||
// config.go
|
// config.go
|
||||||
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!`
|
NoURLSuffix = `Warning: Given "jfa_url"/"External jfa-go URL" value does not include "url_base" value!`
|
||||||
BadURLBase = `Warning: Given reverse proxy subfolder "%s" may conflict with the applications subpaths.`
|
BadURLBase = `Warning: Given reverse proxy subfolder "%s" may conflict with the applications subpaths.`
|
||||||
RouteCollision = `Route Collision! Given reverse proxy subfolder "%s" or "URL Paths" settings likely conflict with the applications subpaths. Culprit: %v`
|
RouteCollision = `Route Collision! Given reverse proxy subfolder "%s" or "URL Paths" settings likely conflict with the applications subpaths. Culprit: %v`
|
||||||
NoExternalHost = `No "External jfa-go URL" provided, set one in Settings > General.`
|
NoExternalHost = `No "External jfa-go URL" provided, set one in Settings > General.`
|
||||||
LoginWontSave = ` Your login won't save until you do.`
|
LoginWontSave = ` Logins may not save until you do.`
|
||||||
SubpathBlockMessage = `URLs: Root subfolder = "%s", Admin = "%s", My Account = "%s", Invite forms = "%s"`
|
SetExternalHostDespiteUseProxyHost = ` This needs to be set even though use_proxy_host is enabled.`
|
||||||
|
SubpathBlockMessage = `URLs: Root subfolder = "%s", Admin = "%s", My Account = "%s", Invite forms = "%s"`
|
||||||
|
|
||||||
// discord.go
|
// discord.go
|
||||||
StartDaemon = "Started %s daemon"
|
StartDaemon = "Started %s daemon"
|
||||||
|
|||||||
@@ -121,7 +121,8 @@ type appContext struct {
|
|||||||
host string
|
host string
|
||||||
port int
|
port int
|
||||||
version string
|
version string
|
||||||
ExternalURI, ExternalDomain string
|
externalURI, externalDomain string // The latter lower-case as should be accessed through app.ExternalDomain()
|
||||||
|
UseProxyHost bool
|
||||||
updater *Updater
|
updater *Updater
|
||||||
webhooks *WebhookSender
|
webhooks *WebhookSender
|
||||||
newUpdate bool // Whether whatever's in update is new.
|
newUpdate bool // Whether whatever's in update is new.
|
||||||
|
|||||||
+1
-1
@@ -30,7 +30,7 @@ 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.ExternalURI
|
url := app.ExternalURI(nil)
|
||||||
var pinLink string
|
var pinLink string
|
||||||
if url == "" {
|
if url == "" {
|
||||||
return pinLink, errors.New(lm.NoExternalHost)
|
return pinLink, errors.New(lm.NoExternalHost)
|
||||||
|
|||||||
+2
-2
@@ -65,7 +65,7 @@ func (app *appContext) getUserTokenLogin(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// host := gc.Request.URL.Hostname()
|
// host := gc.Request.URL.Hostname()
|
||||||
host := app.ExternalDomain
|
host := app.ExternalDomain(gc)
|
||||||
uri := "/my"
|
uri := "/my"
|
||||||
// FIXME: This seems like a bad idea? I think it's to deal with people having Reverse proxy subfolder/URL base set to /accounts.
|
// 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) {
|
if strings.HasPrefix(gc.Request.RequestURI, PAGES.Base) {
|
||||||
@@ -105,7 +105,7 @@ func (app *appContext) getUserTokenRefresh(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// host := gc.Request.URL.Hostname()
|
// host := gc.Request.URL.Hostname()
|
||||||
host := app.ExternalDomain
|
host := app.ExternalDomain(gc)
|
||||||
gc.SetCookie("user-refresh", refresh, REFRESH_TOKEN_VALIDITY_SEC, "/my", host, true, true)
|
gc.SetCookie("user-refresh", refresh, REFRESH_TOKEN_VALIDITY_SEC, "/my", host, true, true)
|
||||||
gc.JSON(200, getTokenDTO{jwt})
|
gc.JSON(200, getTokenDTO{jwt})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ func (app *appContext) BasePageTemplateValues(gc *gin.Context, page Page, base g
|
|||||||
|
|
||||||
pages := PagePathsDTO{
|
pages := PagePathsDTO{
|
||||||
PagePaths: PAGES,
|
PagePaths: PAGES,
|
||||||
ExternalURI: app.ExternalURI,
|
ExternalURI: app.ExternalURI(gc),
|
||||||
TrueBase: PAGES.Base,
|
TrueBase: PAGES.Base,
|
||||||
}
|
}
|
||||||
pages.Base = app.getURLBase(gc)
|
pages.Base = app.getURLBase(gc)
|
||||||
@@ -742,7 +742,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.ExternalURI + PAGES.MyAccount
|
userPageAddress := app.ExternalURI(gc) + PAGES.MyAccount
|
||||||
|
|
||||||
fromUser := ""
|
fromUser := ""
|
||||||
if invite.ReferrerJellyfinID != "" {
|
if invite.ReferrerJellyfinID != "" {
|
||||||
|
|||||||
Reference in New Issue
Block a user