NetworkFromRedirectURI only handled cloud.<net>.<tld> and a substring match on bootno.de. The four live brand surfaces — web3.hanzo.ai, web3.lux.network, web3.zoo.ngo, web3.pars.id, plus the apex hosts lux.cloud and zoo.cloud — fell through to "" and would have exchanged the OAuth code against the fallback IAM client id instead of the shared lux-web3 app. Add an apexNetworks table for bare brand TLDs (bootno.de, lux.cloud, zoo.cloud) and extend the subdomain rule to accept the web3 entrypoint alongside cloud. Both prefixes map to the same per-network IAM redirect; they are alternate brand surfaces over one IAM app. Tests assert every live hostname resolves to lux-web3 and that unknown hosts still fall back. No IAM logic duplicated — this only derives the client id from the redirect host. Co-authored-by: zeekay <z@zeekay.io>
116 lines
3.7 KiB
Go
116 lines
3.7 KiB
Go
package auth
|
|
|
|
import "testing"
|
|
|
|
func TestNetworkFromRedirectURI(t *testing.T) {
|
|
cases := map[string]string{
|
|
"https://cloud.lux.network/auth/callback": "lux",
|
|
"https://cloud.hanzo.ai/auth/callback": "hanzo",
|
|
"https://cloud.zoo.network/auth/callback": "zoo",
|
|
"https://cloud.pars.network/auth/callback": "pars",
|
|
// web3.<network>.<tld> — the live production brand surfaces.
|
|
"https://web3.hanzo.ai/auth/callback": "hanzo",
|
|
"https://web3.lux.network/auth/callback": "lux",
|
|
"https://web3.zoo.ngo/auth/callback": "zoo",
|
|
"https://web3.pars.id/auth/callback": "pars",
|
|
// Apex brand surfaces (the brand is the registrable domain).
|
|
"https://bootno.de/login": "lux",
|
|
"https://lux.cloud/auth/callback": "lux",
|
|
"https://zoo.cloud/auth/callback": "zoo",
|
|
"https://example.com/cb": "",
|
|
"not a url at all": "",
|
|
"": "",
|
|
}
|
|
for uri, want := range cases {
|
|
if got := NetworkFromRedirectURI(uri); got != want {
|
|
t.Errorf("NetworkFromRedirectURI(%q) = %q, want %q", uri, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestClientIDForRedirect(t *testing.T) {
|
|
if got := ClientIDForRedirect("https://cloud.zoo.network/cb", "fallback"); got != "lux-web3" {
|
|
t.Errorf("zoo network must map to lux-web3, got %q", got)
|
|
}
|
|
// All four live brand surfaces share the single lux-web3 IAM app.
|
|
for _, uri := range []string{
|
|
"https://web3.hanzo.ai/auth/callback",
|
|
"https://bootno.de/auth/callback",
|
|
"https://lux.cloud/auth/callback",
|
|
"https://zoo.cloud/auth/callback",
|
|
} {
|
|
if got := ClientIDForRedirect(uri, "fallback"); got != "lux-web3" {
|
|
t.Errorf("%s must map to lux-web3, got %q", uri, got)
|
|
}
|
|
}
|
|
if got := ClientIDForRedirect("https://unknown.example/cb", "fallback"); got != "fallback" {
|
|
t.Errorf("unknown redirect must fall back, got %q", got)
|
|
}
|
|
}
|
|
|
|
func TestClassifyCredential(t *testing.T) {
|
|
cases := []struct {
|
|
cred string
|
|
isBearer bool
|
|
want KeyType
|
|
}{
|
|
{"bn_abc123", false, KeyBootnode},
|
|
{"pk-abc", false, KeyIAMPublishable},
|
|
{"sk-abc", false, KeyIAMSecret},
|
|
{"hk-abc", false, KeyIAMService},
|
|
{"aaa.bbb.ccc", false, KeyJWT},
|
|
{"opaque", true, KeyJWT}, // bearer header => treat as JWT
|
|
{"opaque", false, KeyUnknown},
|
|
}
|
|
for _, c := range cases {
|
|
if got := ClassifyCredential(c.cred, c.isBearer); got != c.want {
|
|
t.Errorf("ClassifyCredential(%q, %v) = %d, want %d", c.cred, c.isBearer, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGenerateAndVerifyKey(t *testing.T) {
|
|
salt := "test-salt"
|
|
raw, hash, prefix, err := GenerateKey(salt)
|
|
if err != nil {
|
|
t.Fatalf("GenerateKey: %v", err)
|
|
}
|
|
if len(raw) < len(BootnodeKeyPrefix)+16 {
|
|
t.Fatalf("raw key too short: %q", raw)
|
|
}
|
|
if raw[:3] != BootnodeKeyPrefix {
|
|
t.Fatalf("raw key missing bn_ prefix: %q", raw)
|
|
}
|
|
if len(prefix) != keyPrefixLen || prefix != raw[:keyPrefixLen] {
|
|
t.Fatalf("prefix mismatch: %q", prefix)
|
|
}
|
|
if !VerifyKey(raw, salt, hash) {
|
|
t.Fatal("VerifyKey must accept the freshly-generated key")
|
|
}
|
|
// Tamper: a single different character must not verify.
|
|
if VerifyKey(raw+"x", salt, hash) {
|
|
t.Fatal("VerifyKey must reject a tampered key")
|
|
}
|
|
if VerifyKey(raw, "wrong-salt", hash) {
|
|
t.Fatal("VerifyKey must reject a key checked with the wrong salt")
|
|
}
|
|
}
|
|
|
|
func TestHashKeyDeterministic(t *testing.T) {
|
|
// The hash is a lookup index, so it MUST be deterministic.
|
|
a := HashKey("bn_xyz", "salt")
|
|
b := HashKey("bn_xyz", "salt")
|
|
if a != b {
|
|
t.Fatal("HashKey must be deterministic for the same input")
|
|
}
|
|
if HashKey("bn_xyz", "salt") == HashKey("bn_xyz", "other") {
|
|
t.Fatal("different salts must yield different hashes")
|
|
}
|
|
// Two distinct keys must not collide.
|
|
r1, h1, _, _ := GenerateKey("s")
|
|
r2, h2, _, _ := GenerateKey("s")
|
|
if r1 == r2 || h1 == h2 {
|
|
t.Fatal("distinct generated keys must not collide")
|
|
}
|
|
}
|