mirror of
https://github.com/navidrome/navidrome.git
synced 2026-02-24 02:48:29 -05:00
Compare commits
3 Commits
feat/spell
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bb13e5ff1 | ||
|
|
d1c5e6a2f2 | ||
|
|
0c3cc86535 |
2
.github/workflows/pipeline.yml
vendored
2
.github/workflows/pipeline.yml
vendored
@@ -424,7 +424,7 @@ jobs:
|
||||
run: echo 'RELEASE_FLAGS=--skip=publish --snapshot' >> $GITHUB_ENV
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
uses: goreleaser/goreleaser-action@v7
|
||||
with:
|
||||
version: '~> v2'
|
||||
args: "release --clean -f release/goreleaser.yml ${{ env.RELEASE_FLAGS }}"
|
||||
|
||||
@@ -250,6 +250,7 @@ type pluginsOptions struct {
|
||||
type extAuthOptions struct {
|
||||
TrustedSources string
|
||||
UserHeader string
|
||||
LogoutURL string
|
||||
}
|
||||
|
||||
type searchOptions struct {
|
||||
@@ -345,6 +346,7 @@ func Load(noConfigDump bool) {
|
||||
validateBackupSchedule,
|
||||
validatePlaylistsPath,
|
||||
validatePurgeMissingOption,
|
||||
validateURL("ExtAuth.LogoutURL", Server.ExtAuth.LogoutURL),
|
||||
)
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
@@ -548,6 +550,33 @@ func validateSchedule(schedule, field string) (string, error) {
|
||||
return schedule, err
|
||||
}
|
||||
|
||||
// validateURL checks if the provided URL is valid and has either http or https scheme.
|
||||
// It returns a function that can be used as a hook to validate URLs in the config.
|
||||
func validateURL(optionName, optionURL string) func() error {
|
||||
return func() error {
|
||||
if optionURL == "" {
|
||||
return nil
|
||||
}
|
||||
u, err := url.Parse(optionURL)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Invalid %s: it could not be parsed", optionName), "url", optionURL, "err", err)
|
||||
return err
|
||||
}
|
||||
if u.Scheme != "http" && u.Scheme != "https" {
|
||||
err := fmt.Errorf("invalid scheme for %s: '%s'. Only 'http' and 'https' are allowed", optionName, u.Scheme)
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
// Require an absolute URL with a non-empty host and no opaque component.
|
||||
if u.Host == "" || u.Opaque != "" {
|
||||
err := fmt.Errorf("invalid %s: '%s'. A full http(s) URL with a non-empty host is required", optionName, optionURL)
|
||||
log.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeSearchBackend(value string) string {
|
||||
v := strings.ToLower(strings.TrimSpace(value))
|
||||
switch v {
|
||||
@@ -641,6 +670,7 @@ func setViperDefaults() {
|
||||
viper.SetDefault("passwordencryptionkey", "")
|
||||
viper.SetDefault("extauth.userheader", "Remote-User")
|
||||
viper.SetDefault("extauth.trustedsources", "")
|
||||
viper.SetDefault("extauth.logouturl", "")
|
||||
viper.SetDefault("prometheus.enabled", false)
|
||||
viper.SetDefault("prometheus.metricspath", consts.PrometheusDefaultPath)
|
||||
viper.SetDefault("prometheus.password", "")
|
||||
|
||||
@@ -52,6 +52,48 @@ var _ = Describe("Configuration", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ValidateURL", func() {
|
||||
It("accepts a valid http URL", func() {
|
||||
fn := conf.ValidateURL("TestOption", "http://example.com/path")
|
||||
Expect(fn()).To(Succeed())
|
||||
})
|
||||
|
||||
It("accepts a valid https URL", func() {
|
||||
fn := conf.ValidateURL("TestOption", "https://example.com/path")
|
||||
Expect(fn()).To(Succeed())
|
||||
})
|
||||
|
||||
It("rejects a URL with no scheme", func() {
|
||||
fn := conf.ValidateURL("TestOption", "example.com/path")
|
||||
Expect(fn()).To(MatchError(ContainSubstring("invalid scheme")))
|
||||
})
|
||||
|
||||
It("rejects a URL with an unsupported scheme", func() {
|
||||
fn := conf.ValidateURL("TestOption", "javascript://example.com/path")
|
||||
Expect(fn()).To(MatchError(ContainSubstring("invalid scheme")))
|
||||
})
|
||||
|
||||
It("accepts an empty URL (optional config)", func() {
|
||||
fn := conf.ValidateURL("TestOption", "")
|
||||
Expect(fn()).To(Succeed())
|
||||
})
|
||||
|
||||
It("includes the option name in the error message", func() {
|
||||
fn := conf.ValidateURL("MyOption", "ftp://example.com")
|
||||
Expect(fn()).To(MatchError(ContainSubstring("MyOption")))
|
||||
})
|
||||
|
||||
It("rejects a URL that cannot be parsed", func() {
|
||||
fn := conf.ValidateURL("TestOption", "://invalid")
|
||||
Expect(fn()).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("rejects a URL without a host", func() {
|
||||
fn := conf.ValidateURL("TestOption", "http:///path")
|
||||
Expect(fn()).To(MatchError(ContainSubstring("non-empty host is required")))
|
||||
})
|
||||
})
|
||||
|
||||
DescribeTable("NormalizeSearchBackend",
|
||||
func(input, expected string) {
|
||||
Expect(conf.NormalizeSearchBackend(input)).To(Equal(expected))
|
||||
|
||||
@@ -8,4 +8,6 @@ var SetViperDefaults = setViperDefaults
|
||||
|
||||
var ParseLanguages = parseLanguages
|
||||
|
||||
var ValidateURL = validateURL
|
||||
|
||||
var NormalizeSearchBackend = normalizeSearchBackend
|
||||
|
||||
@@ -76,6 +76,7 @@ func serveIndex(ds model.DataStore, fs fs.FS, shareInfo *model.Share) http.Handl
|
||||
"separator": string(os.PathSeparator),
|
||||
"enableInspect": conf.Server.Inspect.Enabled,
|
||||
"pluginsEnabled": conf.Server.Plugins.Enabled,
|
||||
"extAuthLogoutURL": conf.Server.ExtAuth.LogoutURL,
|
||||
}
|
||||
if strings.HasPrefix(conf.Server.UILoginBackgroundURL, "/") {
|
||||
appConfig["loginBackgroundURL"] = path.Join(conf.Server.BasePath, conf.Server.UILoginBackgroundURL)
|
||||
|
||||
@@ -104,6 +104,7 @@ var _ = Describe("serveIndex", func() {
|
||||
Entry("enableUserEditing", func() { conf.Server.EnableUserEditing = false }, "enableUserEditing", false),
|
||||
Entry("enableSharing", func() { conf.Server.EnableSharing = true }, "enableSharing", true),
|
||||
Entry("devNewEventStream", func() { conf.Server.DevNewEventStream = true }, "devNewEventStream", true),
|
||||
Entry("extAuthLogoutURL", func() { conf.Server.ExtAuth.LogoutURL = "https://auth.example.com/logout" }, "extAuthLogoutURL", "https://auth.example.com/logout"),
|
||||
)
|
||||
|
||||
DescribeTable("sets other UI configuration values",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<subsonic-response xmlns="http://subsonic.org/restapi" status="ok" version="1.16.1" type="navidrome" serverVersion="v0.55.0" openSubsonic="true">
|
||||
<playlists>
|
||||
<playlist id="111" name="aaa" comment="comment" songCount="2" duration="120" public="true" owner="admin" created="2023-02-20T14:45:00Z" changed="2023-02-20T14:45:00Z" coverArt="pl-123123123123" readonly="true" validUntil="2023-02-20T14:45:00Z"></playlist>
|
||||
<playlist id="333" name="ccc" songCount="0" duration="0" created="0001-01-01T00:00:00Z" changed="0001-01-01T00:00:00Z"></playlist>
|
||||
<playlist id="222" name="bbb" songCount="0" duration="0" created="0001-01-01T00:00:00Z" changed="0001-01-01T00:00:00Z"></playlist>
|
||||
<playlist id="333" name="ccc" songCount="0" duration="0" public="false" created="0001-01-01T00:00:00Z" changed="0001-01-01T00:00:00Z"></playlist>
|
||||
<playlist id="222" name="bbb" songCount="0" duration="0" public="false" created="0001-01-01T00:00:00Z" changed="0001-01-01T00:00:00Z"></playlist>
|
||||
</playlists>
|
||||
</subsonic-response>
|
||||
|
||||
@@ -303,7 +303,7 @@ type Playlist struct {
|
||||
Comment string `xml:"comment,attr,omitempty" json:"comment,omitempty"`
|
||||
SongCount int32 `xml:"songCount,attr" json:"songCount"`
|
||||
Duration int32 `xml:"duration,attr" json:"duration"`
|
||||
Public bool `xml:"public,attr,omitempty" json:"public,omitempty"`
|
||||
Public bool `xml:"public,attr" json:"public,omitempty"`
|
||||
Owner string `xml:"owner,attr,omitempty" json:"owner,omitempty"`
|
||||
Created time.Time `xml:"created,attr" json:"created"`
|
||||
Changed time.Time `xml:"changed,attr" json:"changed"`
|
||||
|
||||
@@ -66,6 +66,10 @@ const authProvider = {
|
||||
|
||||
logout: () => {
|
||||
removeItems()
|
||||
if (config.extAuthLogoutURL) {
|
||||
window.location.href = config.extAuthLogoutURL
|
||||
return Promise.resolve(false)
|
||||
}
|
||||
return Promise.resolve()
|
||||
},
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ const UserMenu = (props) => {
|
||||
})
|
||||
: null,
|
||||
)}
|
||||
{!config.auth && logout}
|
||||
{(!config.auth || !!config.extAuthLogoutURL) && logout}
|
||||
</MenuList>
|
||||
</Popover>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user