fix(oauth2server): accept all loopback redirect forms
Hardcoding the three exact strings localhost / 127.0.0.1 / ::1 rejected legitimate loopback redirects like 127.0.0.2:1234 (anywhere in 127.0.0.0/8) or [0:0:0:0:0:0:0:1]:1234 (expanded IPv6 loopback). Use net.IP.IsLoopback() to cover the full loopback ranges, and match "localhost" case-insensitively. 0.0.0.0 stays rejected as it is not a loopback address. https://claude.ai/code/session_01LsTDrCJ7trE6WQ4FYf78UB
This commit is contained in:
parent
c6bda7a2dd
commit
aa1956e1aa
|
|
@ -17,6 +17,7 @@
|
|||
package oauth2server
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
|
@ -24,8 +25,10 @@ import (
|
|||
// ValidateRedirectURI checks that the redirect_uri is either a Vikunja native
|
||||
// app scheme (e.g. vikunja-flutter://callback) or a loopback http URL as
|
||||
// recommended by RFC 8252 for native apps that cannot register a custom
|
||||
// scheme. Dangerous schemes like javascript:, data:, https://, or non-loopback
|
||||
// http:// targets are rejected.
|
||||
// scheme. Any address in 127.0.0.0/8, the IPv6 loopback (::1, in any
|
||||
// notation), and the literal hostname "localhost" are accepted; dangerous
|
||||
// schemes like javascript:, data:, https://, or non-loopback http:// targets
|
||||
// are rejected.
|
||||
func ValidateRedirectURI(redirectURI string) bool {
|
||||
u, err := url.Parse(redirectURI)
|
||||
if err != nil || u.Scheme == "" {
|
||||
|
|
@ -38,7 +41,12 @@ func ValidateRedirectURI(redirectURI string) bool {
|
|||
|
||||
if u.Scheme == "http" {
|
||||
host := u.Hostname()
|
||||
return host == "localhost" || host == "127.0.0.1" || host == "::1"
|
||||
if strings.EqualFold(host, "localhost") {
|
||||
return true
|
||||
}
|
||||
if ip := net.ParseIP(host); ip != nil && ip.IsLoopback() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
|
|
|
|||
|
|
@ -38,9 +38,21 @@ func TestValidateRedirectURI(t *testing.T) {
|
|||
t.Run("accepts http 127.0.0.1", func(t *testing.T) {
|
||||
assert.True(t, ValidateRedirectURI("http://127.0.0.1:8080/callback"))
|
||||
})
|
||||
t.Run("accepts other 127.0.0.0/8 loopback addresses", func(t *testing.T) {
|
||||
assert.True(t, ValidateRedirectURI("http://127.0.0.2:1234/callback"))
|
||||
})
|
||||
t.Run("accepts http ipv6 loopback", func(t *testing.T) {
|
||||
assert.True(t, ValidateRedirectURI("http://[::1]:8080/callback"))
|
||||
})
|
||||
t.Run("accepts expanded ipv6 loopback", func(t *testing.T) {
|
||||
assert.True(t, ValidateRedirectURI("http://[0:0:0:0:0:0:0:1]:1234/callback"))
|
||||
})
|
||||
t.Run("accepts localhost case-insensitively", func(t *testing.T) {
|
||||
assert.True(t, ValidateRedirectURI("http://LocalHost:8080/callback"))
|
||||
})
|
||||
t.Run("rejects 0.0.0.0", func(t *testing.T) {
|
||||
assert.False(t, ValidateRedirectURI("http://0.0.0.0:8080/callback"))
|
||||
})
|
||||
t.Run("rejects https scheme", func(t *testing.T) {
|
||||
assert.False(t, ValidateRedirectURI("https://evil.com/callback"))
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue