fix: allow self-hosted Bitrix24 domains for portal registration

Split bitrixDomainRegex into bitrixCloudDomainRegex (cloud SaaS domains)
and selfHostedDomainRegex (custom FQDNs). Users can now register portals
with self-hosted Bitrix24 instances like bx.mycompany.com instead of
being restricted to *.bitrix24.{com,eu,...} domains.
This commit is contained in:
Thành Nguyễn committed 2026-06-11 17:21:05 +07:00
1 parent 45f789563d
commit ee26bc90bf
2 files changed
+59 -7

No files matched your search

+7 -3
View File
@@ -71,7 +71,11 @@ type bitrixPortalView struct {
var (
// Bitrix24 cloud portal hosts. Matches *.bitrix24.{com,eu,ru,de,fr,jp,in,kz,ua,by}
// plus self-hosted *.bitrix.info. Subdomain regex matches DNS label rules.
bitrixDomainRegex = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.(bitrix24\.(com|eu|ru|de|fr|jp|in|kz|ua|by)|bitrix\.info)$`)
bitrixCloudDomainRegex = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.(bitrix24\.(com|eu|ru|de|fr|jp|in|kz|ua|by)|bitrix\.info)$`)
// Valid hostname regex for self-hosted Bitrix24 instances (custom domains).
// Accepts any valid FQDN or hostname with optional port (e.g. bx.example.com, portal.internal:8443).
selfHostedDomainRegex = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*(\:\d+)?$`)
// Portal name: lowercase slug used in install state token + channel config
// reference. Underscore allowed for legacy CLI-created portals.
@@ -142,8 +146,8 @@ func (m *BitrixPortalsMethods) handleCreate(ctx context.Context, client *gateway
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgInvalidRequest, "name: lowercase letters, digits, hyphen, underscore (2-64 chars)")))
return
}
if !bitrixDomainRegex.MatchString(domain) {
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgInvalidRequest, "domain: must be *.bitrix24.{com,eu,ru,…} or *.bitrix.info")))
if !bitrixCloudDomainRegex.MatchString(domain) && !selfHostedDomainRegex.MatchString(domain) {
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgInvalidRequest, "domain: must be a valid hostname (e.g. *.bitrix24.com, *.bitrix.info, or your self-hosted domain)")))
return
}
if clientID == "" || clientSecret == "" {
@@ -369,7 +369,7 @@ func TestBitrixPortals_Create_InvalidDomain(t *testing.T) {
client, ch := gateway.NewCapturingTestClient(permissions.RoleAdmin, tid, "u", 4)
m.handleCreate(store.WithTenantID(context.Background(), tid), client, buildBitrixReq(t, protocol.MethodBitrixPortalsCreate, map[string]string{
"name": "p",
"domain": "not-a-bitrix-domain.com",
"domain": "-invalid.com",
"client_id": "x",
"client_secret": "y",
}))
@@ -380,6 +380,29 @@ func TestBitrixPortals_Create_InvalidDomain(t *testing.T) {
}
}
func TestBitrixPortals_Create_SelfHostedDomain(t *testing.T) {
tid := uuid.MustParse("22222222-2222-2222-2222-222222222222")
pStore := newStubBitrixPortalStore()
m := NewBitrixPortalsMethods(pStore, newStubChannelInstanceStore(), gatewayURLFn("https://goclaw.tamgiac.com"))
client, ch := gateway.NewCapturingTestClient(permissions.RoleAdmin, tid, "admin", 4)
m.handleCreate(store.WithTenantID(context.Background(), tid), client, buildBitrixReq(t, protocol.MethodBitrixPortalsCreate, map[string]string{
"name": "myportal",
"domain": "bx.mycompany.com",
"client_id": "local.abc",
"client_secret": "secret123",
}))
resp := readResponse(t, ch)
if resp.Error != nil {
t.Fatalf("create with self-hosted domain failed: %+v", resp.Error)
}
result := resp.Payload.(map[string]any)
if result["domain"] != "bx.mycompany.com" {
t.Errorf("domain = %q, want bx.mycompany.com", result["domain"])
}
}
func TestBitrixPortals_Create_InvalidName(t *testing.T) {
tid := uuid.New()
m := NewBitrixPortalsMethods(newStubBitrixPortalStore(), newStubChannelInstanceStore(), gatewayURLFn("https://gw.example.com"))
@@ -543,19 +566,44 @@ func TestBitrixDomainRegex(t *testing.T) {
}
bad := []string{
"tamgiac.bitrix24",
"tamgiac.example.com",
"tamgiac.bitrix24.xx",
"-bad.bitrix24.com",
"UPPER.bitrix24.com", // we lowercase before match
"a.b.bitrix24.com", // multi-level subdomain not allowed
}
for _, d := range good {
if !bitrixDomainRegex.MatchString(d) {
if !bitrixCloudDomainRegex.MatchString(d) {
t.Errorf("should accept %q", d)
}
}
for _, d := range bad {
if bitrixDomainRegex.MatchString(d) {
if bitrixCloudDomainRegex.MatchString(d) {
t.Errorf("should reject %q", d)
}
}
}
func TestSelfHostedDomainRegex(t *testing.T) {
good := []string{
"bx.example.com",
"portal.internal",
"bitrix.mycompany.co.uk",
"portal.example.com:8443",
"bx.corp",
}
bad := []string{
"-bad.example.com",
"UPPER.example.com", // we lowercase before match
"",
"not a domain",
}
for _, d := range good {
if !selfHostedDomainRegex.MatchString(d) {
t.Errorf("should accept %q", d)
}
}
for _, d := range bad {
if selfHostedDomainRegex.MatchString(d) {
t.Errorf("should reject %q", d)
}
}