feat: add custom base url (#858)

Co-authored-by: Stephan Höhn <me@steph.ovh>
Co-authored-by: Kyle Mendell <ksm@ofkm.us>
This commit is contained in:
Stephan H.
2025-08-30 20:13:57 +02:00
committed by GitHub
parent 52c560c30d
commit a3979f63e0
3 changed files with 46 additions and 5 deletions

View File

@@ -27,6 +27,7 @@ const (
DbProviderPostgres DbProvider = "postgres" DbProviderPostgres DbProvider = "postgres"
MaxMindGeoLiteCityUrl string = "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz" MaxMindGeoLiteCityUrl string = "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&license_key=%s&suffix=tar.gz"
defaultSqliteConnString string = "data/pocket-id.db" defaultSqliteConnString string = "data/pocket-id.db"
AppUrl string = "http://localhost:1411"
) )
type EnvConfigSchema struct { type EnvConfigSchema struct {
@@ -53,6 +54,7 @@ type EnvConfigSchema struct {
TrustProxy bool `env:"TRUST_PROXY"` TrustProxy bool `env:"TRUST_PROXY"`
AnalyticsDisabled bool `env:"ANALYTICS_DISABLED"` AnalyticsDisabled bool `env:"ANALYTICS_DISABLED"`
AllowDowngrade bool `env:"ALLOW_DOWNGRADE"` AllowDowngrade bool `env:"ALLOW_DOWNGRADE"`
InternalAppURL string `env:"INTERNAL_APP_URL"`
} }
var EnvConfig = defaultConfig() var EnvConfig = defaultConfig()
@@ -74,7 +76,7 @@ func defaultConfig() EnvConfigSchema {
KeysPath: "data/keys", KeysPath: "data/keys",
KeysStorage: "", // "database" or "file" KeysStorage: "", // "database" or "file"
EncryptionKey: nil, EncryptionKey: nil,
AppURL: "http://localhost:1411", AppURL: AppUrl,
Port: "1411", Port: "1411",
Host: "0.0.0.0", Host: "0.0.0.0",
UnixSocket: "", UnixSocket: "",
@@ -89,6 +91,7 @@ func defaultConfig() EnvConfigSchema {
TrustProxy: false, TrustProxy: false,
AnalyticsDisabled: false, AnalyticsDisabled: false,
AllowDowngrade: false, AllowDowngrade: false,
InternalAppURL: "",
} }
} }
@@ -133,6 +136,19 @@ func parseEnvConfig() error {
return errors.New("APP_URL must not contain a path") return errors.New("APP_URL must not contain a path")
} }
// Derive INTERNAL_APP_URL from APP_URL if not set; validate only when provided
if EnvConfig.InternalAppURL == "" {
EnvConfig.InternalAppURL = EnvConfig.AppURL
} else {
parsedInternalAppUrl, err := url.Parse(EnvConfig.InternalAppURL)
if err != nil {
return errors.New("INTERNAL_APP_URL is not a valid URL")
}
if parsedInternalAppUrl.Path != "" {
return errors.New("INTERNAL_APP_URL must not contain a path")
}
}
switch EnvConfig.KeysStorage { switch EnvConfig.KeysStorage {
// KeysStorage defaults to "file" if empty // KeysStorage defaults to "file" if empty
case "": case "":

View File

@@ -91,6 +91,28 @@ func TestParseEnvConfig(t *testing.T) {
assert.ErrorContains(t, err, "APP_URL must not contain a path") assert.ErrorContains(t, err, "APP_URL must not contain a path")
}) })
t.Run("should fail with invalid INTERNAL_APP_URL", func(t *testing.T) {
EnvConfig = defaultConfig()
t.Setenv("DB_PROVIDER", "sqlite")
t.Setenv("DB_CONNECTION_STRING", "file:test.db")
t.Setenv("INTERNAL_APP_URL", "€://not-a-valid-url")
err := parseEnvConfig()
require.Error(t, err)
assert.ErrorContains(t, err, "INTERNAL_APP_URL is not a valid URL")
})
t.Run("should fail when INTERNAL_APP_URL contains path", func(t *testing.T) {
EnvConfig = defaultConfig()
t.Setenv("DB_PROVIDER", "sqlite")
t.Setenv("DB_CONNECTION_STRING", "file:test.db")
t.Setenv("INTERNAL_APP_URL", "http://localhost:3000/path")
err := parseEnvConfig()
require.Error(t, err)
assert.ErrorContains(t, err, "INTERNAL_APP_URL must not contain a path")
})
t.Run("should default KEYS_STORAGE to 'file' when empty", func(t *testing.T) { t.Run("should default KEYS_STORAGE to 'file' when empty", func(t *testing.T) {
EnvConfig = defaultConfig() EnvConfig = defaultConfig()
t.Setenv("DB_PROVIDER", "sqlite") t.Setenv("DB_PROVIDER", "sqlite")

View File

@@ -67,6 +67,9 @@ func (wkc *WellKnownController) openIDConfigurationHandler(c *gin.Context) {
func (wkc *WellKnownController) computeOIDCConfiguration() ([]byte, error) { func (wkc *WellKnownController) computeOIDCConfiguration() ([]byte, error) {
appUrl := common.EnvConfig.AppURL appUrl := common.EnvConfig.AppURL
internalAppUrl := common.EnvConfig.InternalAppURL
alg, err := wkc.jwtService.GetKeyAlg() alg, err := wkc.jwtService.GetKeyAlg()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get key algorithm: %w", err) return nil, fmt.Errorf("failed to get key algorithm: %w", err)
@@ -74,12 +77,12 @@ func (wkc *WellKnownController) computeOIDCConfiguration() ([]byte, error) {
config := map[string]any{ config := map[string]any{
"issuer": appUrl, "issuer": appUrl,
"authorization_endpoint": appUrl + "/authorize", "authorization_endpoint": appUrl + "/authorize",
"token_endpoint": appUrl + "/api/oidc/token", "token_endpoint": internalAppUrl + "/api/oidc/token",
"userinfo_endpoint": appUrl + "/api/oidc/userinfo", "userinfo_endpoint": internalAppUrl + "/api/oidc/userinfo",
"end_session_endpoint": appUrl + "/api/oidc/end-session", "end_session_endpoint": appUrl + "/api/oidc/end-session",
"introspection_endpoint": appUrl + "/api/oidc/introspect", "introspection_endpoint": internalAppUrl + "/api/oidc/introspect",
"device_authorization_endpoint": appUrl + "/api/oidc/device/authorize", "device_authorization_endpoint": appUrl + "/api/oidc/device/authorize",
"jwks_uri": appUrl + "/.well-known/jwks.json", "jwks_uri": internalAppUrl + "/.well-known/jwks.json",
"grant_types_supported": []string{service.GrantTypeAuthorizationCode, service.GrantTypeRefreshToken, service.GrantTypeDeviceCode}, "grant_types_supported": []string{service.GrantTypeAuthorizationCode, service.GrantTypeRefreshToken, service.GrantTypeDeviceCode},
"scopes_supported": []string{"openid", "profile", "email", "groups"}, "scopes_supported": []string{"openid", "profile", "email", "groups"},
"claims_supported": []string{"sub", "given_name", "family_name", "name", "email", "email_verified", "preferred_username", "picture", "groups"}, "claims_supported": []string{"sub", "given_name", "family_name", "name", "email", "email_verified", "preferred_username", "picture", "groups"},