domain is defined

This commit is contained in:
adlerhurst
2025-07-16 09:26:46 +02:00
parent 8d020e56bb
commit 483b4daba4
12 changed files with 610 additions and 10 deletions

View File

@@ -0,0 +1,16 @@
package migration
import (
_ "embed"
)
var (
//go:embed 003_domains_table/up.sql
up003DomainsTable string
//go:embed 003_domains_table/down.sql
down003DomainsTable string
)
func init() {
registerSQLMigration(3, up003DomainsTable, down003DomainsTable)
}

View File

@@ -0,0 +1,4 @@
DROP TABLE IF EXISTS zitadel.instance_domains;
DROP TABLE IF EXISTS zitadel.org_domains;
DROP TYPE IF EXISTS zitadel.domain_validation_type;
DROP FUNCTION IF EXISTS zitadel.check_verified_org_domain();

View File

@@ -0,0 +1,128 @@
CREATE TYPE zitadel.domain_validation_type AS ENUM (
'http'
, 'dns'
);
CREATE TABLE zitadel.instance_domains(
instance_id TEXT NOT NULL
, domain TEXT NOT NULL CHECK (LENGTH(domain) BETWEEN 1 AND 255)
, is_verified BOOLEAN NOT NULL DEFAULT FALSE
, is_primary BOOLEAN NOT NULL DEFAULT FALSE
, is_generated BOOLEAN NOT NULL DEFAULT FALSE
, validation_type zitadel.domain_validation_type
, created_at TIMESTAMP DEFAULT NOW()
, updated_at TIMESTAMP DEFAULT NOW()
, PRIMARY KEY (domain)
, FOREIGN KEY (instance_id) REFERENCES zitadel.instances(id) ON DELETE CASCADE
);
CREATE INDEX idx_instance_domain_instance ON zitadel.instance_domains(instance_id);
CREATE TABLE zitadel.org_domains(
instance_id TEXT NOT NULL
, org_id TEXT NOT NULL
, domain TEXT NOT NULL CHECK (LENGTH(domain) BETWEEN 1 AND 255)
, is_verified BOOLEAN NOT NULL DEFAULT FALSE
, is_primary BOOLEAN NOT NULL DEFAULT FALSE
, validation_type zitadel.domain_validation_type
, created_at TIMESTAMP DEFAULT NOW()
, updated_at TIMESTAMP DEFAULT NOW()
, PRIMARY KEY (instance_id, org_id, domain)
, FOREIGN KEY (instance_id, org_id) REFERENCES zitadel.organizations(instance_id, id) ON DELETE CASCADE
, UNIQUE (instance_id, org_id, domain)
);
CREATE INDEX idx_org_domain ON zitadel.org_domains(instance_id, domain);
-- Trigger to update the updated_at timestamp on instance_domains
CREATE TRIGGER trg_set_updated_at_instance_domains
BEFORE UPDATE ON zitadel.instance_domains
FOR EACH ROW
WHEN (OLD.updated_at IS NOT DISTINCT FROM NEW.updated_at)
EXECUTE FUNCTION zitadel.set_updated_at();
-- Trigger to update the updated_at timestamp on org_domains
CREATE TRIGGER trg_set_updated_at_org_domains
BEFORE UPDATE ON zitadel.org_domains
FOR EACH ROW
WHEN (OLD.updated_at IS NOT DISTINCT FROM NEW.updated_at)
EXECUTE FUNCTION zitadel.set_updated_at();
-- Function to check for already verified org domains
CREATE OR REPLACE FUNCTION zitadel.check_verified_org_domain()
RETURNS TRIGGER AS $$
BEGIN
-- Check if there's already a verified domain within this instance (excluding the current record being updated)
IF EXISTS (
SELECT 1
FROM zitadel.org_domains
WHERE instance_id = NEW.instance_id
AND domain = NEW.domain
AND is_verified = TRUE
AND (TG_OP = 'INSERT' OR (org_id != NEW.org_id))
) THEN
RAISE EXCEPTION 'org domain is already taken';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger to enforce verified domain constraint on org_domains
CREATE TRIGGER trg_check_verified_org_domain
BEFORE INSERT OR UPDATE ON zitadel.org_domains
FOR EACH ROW
WHEN (NEW.is_verified IS TRUE)
EXECUTE FUNCTION zitadel.check_verified_org_domain();
-- Function to ensure only one primary domain per instance in instance_domains
CREATE OR REPLACE FUNCTION zitadel.ensure_single_primary_instance_domain()
RETURNS TRIGGER AS $$
BEGIN
-- If setting this domain as primary, update all other domains in the same instance to non-primary
UPDATE zitadel.instance_domains
SET is_primary = FALSE, updated_at = NOW()
WHERE instance_id = NEW.instance_id
AND domain != NEW.domain
AND is_primary = TRUE;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger to enforce single primary domain constraint on instance_domains
CREATE TRIGGER trg_ensure_single_primary_instance_domain
BEFORE INSERT OR UPDATE ON zitadel.instance_domains
FOR EACH ROW
WHEN (NEW.is_primary IS TRUE)
EXECUTE FUNCTION zitadel.ensure_single_primary_instance_domain();
-- Function to ensure only one primary domain per organization in org_domains
CREATE OR REPLACE FUNCTION zitadel.ensure_single_primary_org_domain()
RETURNS TRIGGER AS $$
BEGIN
-- If setting this domain as primary, update all other domains in the same organization to non-primary
UPDATE zitadel.org_domains
SET is_primary = FALSE, updated_at = NOW()
WHERE instance_id = NEW.instance_id
AND org_id = NEW.org_id
AND domain != NEW.domain
AND is_primary = TRUE;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Trigger to enforce single primary domain constraint on org_domains
CREATE TRIGGER trg_ensure_single_primary_org_domain
BEFORE INSERT OR UPDATE ON zitadel.org_domains
FOR EACH ROW
WHEN (NEW.is_primary IS TRUE)
EXECUTE FUNCTION zitadel.ensure_single_primary_org_domain();

View File

@@ -15,6 +15,8 @@ var _ domain.InstanceRepository = (*instance)(nil)
type instance struct {
repository
shouldJoinDomains bool
domainRepo domain.InstanceDomainRepository
}
func InstanceRepository(client database.QueryExecutor) domain.InstanceRepository {
@@ -244,3 +246,22 @@ func scanInstances(ctx context.Context, querier database.Querier, builder *datab
return instances, nil
}
// -------------------------------------------------------------
// sub repositories
// -------------------------------------------------------------
// Domains implements [domain.InstanceRepository].
func (i *instance) Domains() domain.InstanceDomainRepository {
i.shouldJoinDomains = true
if i.domainRepo != nil {
return i.domainRepo
}
i.domainRepo = &instanceDomain{
repository: i.repository,
instance: i,
}
return i.domainRepo
}

View File

@@ -0,0 +1,128 @@
package repository
import (
"context"
"github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/backend/v3/storage/database"
)
var _ domain.InstanceDomainRepository = (*instanceDomain)(nil)
type instanceDomain struct {
repository
*instance
}
// -------------------------------------------------------------
// repository
// -------------------------------------------------------------
// Add implements [domain.InstanceDomainRepository].
func (i *instanceDomain) Add(ctx context.Context, domain *domain.AddInstanceDomain) error {
panic("unimplemented")
}
// Remove implements [domain.InstanceDomainRepository].
func (i *instanceDomain) Remove(ctx context.Context, condition database.Condition) error {
panic("unimplemented")
}
// Update implements [domain.InstanceDomainRepository].
// Subtle: this method shadows the method (instance).Update of instanceDomain.instance.
func (i *instanceDomain) Update(ctx context.Context, condition database.Condition, changes ...database.Change) error {
panic("unimplemented")
}
// -------------------------------------------------------------
// changes
// -------------------------------------------------------------
// SetVerificationType implements [domain.InstanceDomainRepository].
func (i *instanceDomain) SetVerificationType(verificationType domain.DomainVerificationType) database.Change {
panic("unimplemented")
}
// SetPrimary implements [domain.InstanceDomainRepository].
func (i *instanceDomain) SetPrimary() database.Change {
panic("unimplemented")
}
// SetVerified implements [domain.InstanceDomainRepository].
func (i *instanceDomain) SetVerified() database.Change {
panic("unimplemented")
}
// -------------------------------------------------------------
// conditions
// -------------------------------------------------------------
// DomainCondition implements [domain.InstanceDomainRepository].
func (i *instanceDomain) DomainCondition(op database.TextOperation, domain string) database.Condition {
panic("unimplemented")
}
// InstanceIDCondition implements [domain.InstanceDomainRepository].
func (i *instanceDomain) InstanceIDCondition(instanceID string) database.Condition {
panic("unimplemented")
}
// IsPrimaryCondition implements [domain.InstanceDomainRepository].
func (i *instanceDomain) IsPrimaryCondition(isPrimary bool) database.Condition {
panic("unimplemented")
}
// IsVerifiedCondition implements [domain.InstanceDomainRepository].
func (i *instanceDomain) IsVerifiedCondition(isVerified bool) database.Condition {
panic("unimplemented")
}
// -------------------------------------------------------------
// columns
// -------------------------------------------------------------
// CreatedAtColumn implements [domain.InstanceDomainRepository].
// Subtle: this method shadows the method (instance).CreatedAtColumn of instanceDomain.instance.
func (i *instanceDomain) CreatedAtColumn() database.Column {
panic("unimplemented")
}
// DomainColumn implements [domain.InstanceDomainRepository].
func (i *instanceDomain) DomainColumn() database.Column {
panic("unimplemented")
}
// InstanceIDColumn implements [domain.InstanceDomainRepository].
func (i *instanceDomain) InstanceIDColumn() database.Column {
panic("unimplemented")
}
// IsPrimaryColumn implements [domain.InstanceDomainRepository].
func (i *instanceDomain) IsPrimaryColumn() database.Column {
panic("unimplemented")
}
// IsVerifiedColumn implements [domain.InstanceDomainRepository].
func (i *instanceDomain) IsVerifiedColumn() database.Column {
panic("unimplemented")
}
// UpdatedAtColumn implements [domain.InstanceDomainRepository].
// Subtle: this method shadows the method (instance).UpdatedAtColumn of instanceDomain.instance.
func (i *instanceDomain) UpdatedAtColumn() database.Column {
panic("unimplemented")
}
// VerificationTypeColumn implements [domain.InstanceDomainRepository].
func (i *instanceDomain) VerificationTypeColumn() database.Column {
panic("unimplemented")
}
// IsGeneratedColumn implements [domain.InstanceDomainRepository].
func (i *instanceDomain) IsGeneratedColumn() database.Column {
return database.NewColumn("is_generated")
}
// -------------------------------------------------------------
// scanners
// -------------------------------------------------------------

View File

@@ -18,7 +18,9 @@ import (
var _ domain.OrganizationRepository = (*org)(nil)
type org struct {
shouldJoinDomains bool
repository
domainRepo domain.OrganizationDomainRepository
}
func OrganizationRepository(client database.QueryExecutor) domain.OrganizationRepository {
@@ -229,6 +231,10 @@ func (org) DeletedAtColumn() database.Column {
return database.NewColumn("deleted_at")
}
// -------------------------------------------------------------
// scanners
// -------------------------------------------------------------
func scanOrganization(ctx context.Context, querier database.Querier, builder *database.StatementBuilder) (*domain.Organization, error) {
rows, err := querier.Query(ctx, builder.String(), builder.Args()...)
if err != nil {
@@ -264,3 +270,22 @@ func scanOrganizations(ctx context.Context, querier database.Querier, builder *d
}
return organizations, nil
}
// -------------------------------------------------------------
// sub repositories
// -------------------------------------------------------------
func (o *org) Domains() domain.OrganizationDomainRepository {
o.shouldJoinDomains = true
if o.domainRepo != nil {
return o.domainRepo
}
o.domainRepo = &orgDomain{
repository: o.repository,
org: o,
}
return o.domainRepo
}

View File

@@ -4,25 +4,132 @@ import (
"context"
"github.com/zitadel/zitadel/backend/v3/domain"
"github.com/zitadel/zitadel/backend/v3/storage/database"
)
var _ domain.OrganizationDomainRepository = (*orgDomain)(nil)
type orgDomain struct {
repository
*org
}
// AddDomain implements [domain.DomainRepository].
func (o *orgDomain) AddDomain(ctx context.Context, domain string) error {
// -------------------------------------------------------------
// repository
// -------------------------------------------------------------
// Add implements [domain.OrganizationDomainRepository].
func (o *orgDomain) Add(ctx context.Context, domain *domain.AddOrganizationDomain) error {
panic("unimplemented")
}
// RemoveDomain implements [domain.DomainRepository].
func (o *orgDomain) RemoveDomain(ctx context.Context, domain string) error {
// Update implements [domain.OrganizationDomainRepository].
// Subtle: this method shadows the method (*org).Update of orgDomain.org.
func (o *orgDomain) Update(ctx context.Context, condition database.Condition, changes ...database.Change) error {
panic("unimplemented")
}
// SetDomainVerified implements [domain.DomainRepository].
func (o *orgDomain) SetDomainVerified(ctx context.Context, domain string) error {
// Remove implements [domain.OrganizationDomainRepository].
func (o *orgDomain) Remove(ctx context.Context, condition database.Condition) error {
panic("unimplemented")
}
var _ domain.DomainRepository = (*orgDomain)(nil)
// -------------------------------------------------------------
// changes
// -------------------------------------------------------------
// SetPrimary implements [domain.OrganizationDomainRepository].
func (o *orgDomain) SetPrimary() database.Change {
panic("unimplemented")
}
// SetVerificationType implements [domain.OrganizationDomainRepository].
func (o *orgDomain) SetVerificationType(verificationType domain.DomainVerificationType) database.Change {
panic("unimplemented")
}
// SetVerified implements [domain.OrganizationDomainRepository].
func (o *orgDomain) SetVerified() database.Change {
panic("unimplemented")
}
// -------------------------------------------------------------
// conditions
// -------------------------------------------------------------
// DomainCondition implements [domain.OrganizationDomainRepository].
func (o *orgDomain) DomainCondition(op database.TextOperation, domain string) database.Condition {
panic("unimplemented")
}
// InstanceIDCondition implements [domain.OrganizationDomainRepository].
// Subtle: this method shadows the method (*org).InstanceIDCondition of orgDomain.org.
func (o *orgDomain) InstanceIDCondition(instanceID string) database.Condition {
panic("unimplemented")
}
// IsPrimaryCondition implements [domain.OrganizationDomainRepository].
func (o *orgDomain) IsPrimaryCondition(isPrimary bool) database.Condition {
panic("unimplemented")
}
// IsVerifiedCondition implements [domain.OrganizationDomainRepository].
func (o *orgDomain) IsVerifiedCondition(isVerified bool) database.Condition {
panic("unimplemented")
}
// OrgIDCondition implements [domain.OrganizationDomainRepository].
func (o *orgDomain) OrgIDCondition(orgID string) database.Condition {
panic("unimplemented")
}
// -------------------------------------------------------------
// columns
// -------------------------------------------------------------
// CreatedAtColumn implements [domain.OrganizationDomainRepository].
// Subtle: this method shadows the method (*org).CreatedAtColumn of orgDomain.org.
func (o *orgDomain) CreatedAtColumn() database.Column {
panic("unimplemented")
}
// DomainColumn implements [domain.OrganizationDomainRepository].
func (o *orgDomain) DomainColumn() database.Column {
panic("unimplemented")
}
// InstanceIDColumn implements [domain.OrganizationDomainRepository].
// Subtle: this method shadows the method (*org).InstanceIDColumn of orgDomain.org.
func (o *orgDomain) InstanceIDColumn() database.Column {
panic("unimplemented")
}
// IsPrimaryColumn implements [domain.OrganizationDomainRepository].
func (o *orgDomain) IsPrimaryColumn() database.Column {
panic("unimplemented")
}
// IsVerifiedColumn implements [domain.OrganizationDomainRepository].
func (o *orgDomain) IsVerifiedColumn() database.Column {
panic("unimplemented")
}
// OrgIDColumn implements [domain.OrganizationDomainRepository].
func (o *orgDomain) OrgIDColumn() database.Column {
panic("unimplemented")
}
// UpdatedAtColumn implements [domain.OrganizationDomainRepository].
// Subtle: this method shadows the method (*org).UpdatedAtColumn of orgDomain.org.
func (o *orgDomain) UpdatedAtColumn() database.Column {
panic("unimplemented")
}
// VerificationTypeColumn implements [domain.OrganizationDomainRepository].
func (o *orgDomain) VerificationTypeColumn() database.Column {
panic("unimplemented")
}
// -------------------------------------------------------------
// scanners
// -------------------------------------------------------------