diff --git a/pkg/probod/custom_domains_config.go b/pkg/probod/custom_domains_config.go index c33133454..1f493fc88 100644 --- a/pkg/probod/custom_domains_config.go +++ b/pkg/probod/custom_domains_config.go @@ -17,6 +17,7 @@ package probod type customDomainsConfig struct { RenewalInterval int `json:"renewal-interval"` ProvisionInterval int `json:"provision-interval"` + CnameTarget string `json:"cname-target"` ACME acmeConfig `json:"acme"` } diff --git a/pkg/probod/probod.go b/pkg/probod/probod.go index a9cb9f28d..23d52285a 100644 --- a/pkg/probod/probod.go +++ b/pkg/probod/probod.go @@ -309,9 +309,8 @@ func (impl *Implm) Run( ConnectorRegistry: defaultConnectorRegistry, Agent: agent, SafeRedirect: &saferedirect.SafeRedirect{AllowedHost: impl.cfg.Hostname}, + CustomDomainCname: impl.cfg.CustomDomains.CnameTarget, Logger: l.Named("http.server"), - PgClient: pgClient, - EncryptionKey: impl.cfg.EncryptionKey, Auth: api.ConsoleAuthConfig{ CookieName: impl.cfg.Auth.Cookie.Name, CookieDomain: impl.cfg.Auth.Cookie.Domain, diff --git a/pkg/server/api/api.go b/pkg/server/api/api.go index b8eeed082..380c44b98 100644 --- a/pkg/server/api/api.go +++ b/pkg/server/api/api.go @@ -53,15 +53,16 @@ type ( } Config struct { - AllowedOrigins []string - Probo *probo.Service - Usrmgr *usrmgr.Service - Trust *trust.Service - Auth ConsoleAuthConfig - TrustAuth TrustAuthConfig - ConnectorRegistry *connector.ConnectorRegistry - SafeRedirect *saferedirect.SafeRedirect - Logger *log.Logger + AllowedOrigins []string + Probo *probo.Service + Usrmgr *usrmgr.Service + Trust *trust.Service + Auth ConsoleAuthConfig + TrustAuth TrustAuthConfig + ConnectorRegistry *connector.ConnectorRegistry + SafeRedirect *saferedirect.SafeRedirect + CustomDomainCname string + Logger *log.Logger } Server struct { @@ -154,6 +155,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { }, s.cfg.ConnectorRegistry, s.cfg.SafeRedirect, + s.cfg.CustomDomainCname, ), ) diff --git a/pkg/server/api/console/v1/resolver.go b/pkg/server/api/console/v1/resolver.go index e62226de1..e643c2a88 100644 --- a/pkg/server/api/console/v1/resolver.go +++ b/pkg/server/api/console/v1/resolver.go @@ -53,9 +53,10 @@ type ( } Resolver struct { - proboSvc *probo.Service - usrmgrSvc *usrmgr.Service - authCfg AuthConfig + proboSvc *probo.Service + usrmgrSvc *usrmgr.Service + authCfg AuthConfig + customDomainCname string } ctxKey struct{ name string } @@ -84,6 +85,7 @@ func NewMux( authCfg AuthConfig, connectorRegistry *connector.ConnectorRegistry, safeRedirect *saferedirect.SafeRedirect, + customDomainCname string, ) *chi.Mux { r := chi.NewMux() @@ -204,20 +206,21 @@ func NewMux( })) r.Get("/", playground.Handler("GraphQL", "/api/console/v1/query")) - r.Post("/query", graphqlHandler(logger, proboSvc, usrmgrSvc, authCfg)) + r.Post("/query", graphqlHandler(logger, proboSvc, usrmgrSvc, authCfg, customDomainCname)) return r } -func graphqlHandler(logger *log.Logger, proboSvc *probo.Service, usrmgrSvc *usrmgr.Service, authCfg AuthConfig) http.HandlerFunc { +func graphqlHandler(logger *log.Logger, proboSvc *probo.Service, usrmgrSvc *usrmgr.Service, authCfg AuthConfig, customDomainCname string) http.HandlerFunc { var mb int64 = 1 << 20 es := schema.NewExecutableSchema( schema.Config{ Resolvers: &Resolver{ - proboSvc: proboSvc, - usrmgrSvc: usrmgrSvc, - authCfg: authCfg, + proboSvc: proboSvc, + usrmgrSvc: usrmgrSvc, + authCfg: authCfg, + customDomainCname: customDomainCname, }, }, ) diff --git a/pkg/server/api/console/v1/types/custom_domain.go b/pkg/server/api/console/v1/types/custom_domain.go new file mode 100644 index 000000000..c23c3fbd8 --- /dev/null +++ b/pkg/server/api/console/v1/types/custom_domain.go @@ -0,0 +1,84 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package types + +import ( + "github.com/getprobo/probo/pkg/coredata" + "github.com/getprobo/probo/pkg/gid" + "github.com/getprobo/probo/pkg/page" +) + +func NewCustomDomainConnection( + p *page.Page[*coredata.CustomDomain, coredata.CustomDomainOrderField], + parentType any, + parentID gid.GID, + cnameTarget string, +) *CustomDomainConnection { + var edges = make([]*CustomDomainEdge, len(p.Data)) + + for i := range edges { + edges[i] = NewCustomDomainEdge(p.Data[i], p.Cursor.OrderBy.Field, cnameTarget) + } + + return &CustomDomainConnection{ + Edges: edges, + PageInfo: NewPageInfo(p), + TotalCount: len(p.Data), + } +} + +func NewCustomDomainEdge( + d *coredata.CustomDomain, + orderBy coredata.CustomDomainOrderField, + cnameTarget string, +) *CustomDomainEdge { + return &CustomDomainEdge{ + Cursor: d.CursorKey(orderBy), + Node: NewCustomDomain(d, cnameTarget), + } +} + +func NewCustomDomain(d *coredata.CustomDomain, cnameTarget string) *CustomDomain { + result := &CustomDomain{ + ID: d.ID, + Domain: d.Domain, + IsActive: d.IsActive, + SslStatus: d.SSLStatus, + CreatedAt: d.CreatedAt, + UpdatedAt: d.UpdatedAt, + SslExpiresAt: d.SSLExpiresAt, + } + + // Convert DNS records + result.DNSRecords = convertDNSRecords(d, cnameTarget) + + return result +} + +func convertDNSRecords(d *coredata.CustomDomain, cnameTarget string) []*DNSRecordInstruction { + var records []*DNSRecordInstruction + + // For HTTP-01 challenges, we just need the domain to point to our servers via CNAME + record := &DNSRecordInstruction{ + Type: "CNAME", + Name: d.Domain, + Value: cnameTarget, + TTL: 300, + Purpose: "Point domain to Probo servers", + } + records = append(records, record) + + return records +} diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index 9dccde88b..7d51e2960 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -3325,7 +3325,7 @@ func (r *mutationResolver) CreateCustomDomain(ctx context.Context, input types.C } edge := &types.CustomDomainEdge{ - Node: types.NewCustomDomain(domain), + Node: types.NewCustomDomain(domain, r.customDomainCname), Cursor: page.NewCursorKey(domain.ID, domain.CreatedAt), } @@ -4032,7 +4032,7 @@ func (r *organizationResolver) CustomDomains(ctx context.Context, obj *types.Org return nil, fmt.Errorf("failed to list custom domains: %w", err) } - return types.NewCustomDomainConnection(page, r, obj.ID), nil + return types.NewCustomDomainConnection(page, r, obj.ID, r.customDomainCname), nil } // TotalCount is the resolver for the totalCount field. diff --git a/pkg/server/server.go b/pkg/server/server.go index 6d91e20e1..73c25fb17 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -44,6 +44,7 @@ type Config struct { ConnectorRegistry *connector.ConnectorRegistry Agent *agents.Agent SafeRedirect *saferedirect.SafeRedirect + CustomDomainCname string Logger *log.Logger } @@ -68,6 +69,7 @@ func NewServer(cfg Config) (*Server, error) { TrustAuth: cfg.TrustAuth, ConnectorRegistry: cfg.ConnectorRegistry, SafeRedirect: cfg.SafeRedirect, + CustomDomainCname: cfg.CustomDomainCname, Logger: cfg.Logger.Named("api"), } apiServer, err := api.NewServer(apiCfg)