11package client
22
33import (
4+ "bytes"
45 "context"
56 "encoding/json"
67 "errors"
78 "fmt"
89 "io"
910 "net/http"
11+ "net/url"
1012 "strings"
1113 "sync"
1214 "time"
@@ -15,6 +17,9 @@ import (
1517 "github.com/ory/x/urlx"
1618)
1719
20+ // ErrCustomDomainNotFound is returned when a custom domain ID is not found in the project's domain list.
21+ var ErrCustomDomainNotFound = errors .New ("custom domain not found" )
22+
1823const (
1924 // maxRetries is the maximum number of retry attempts for rate-limited requests.
2025 maxRetries = 3
@@ -1282,6 +1287,148 @@ func (c *OryClient) ListIdentitySchemas(ctx context.Context) ([]ory.IdentitySche
12821287 return schemas , nil
12831288}
12841289
1290+ // Custom Domain (CNAME) operations
1291+ // The Ory SDK does not generate API methods for custom domains,
1292+ // so we use raw HTTP calls against the console API.
1293+
1294+ // consoleHTTPDo executes a raw HTTP request against the console API.
1295+ func (c * OryClient ) consoleHTTPDo (ctx context.Context , method , path string , body io.Reader ) (* http.Response , error ) {
1296+ if c .config .WorkspaceAPIKey == "" || c .config .ConsoleAPIURL == "" {
1297+ return nil , fmt .Errorf ("workspace_api_key and console_api_url must be configured for custom domain operations" )
1298+ }
1299+ baseURL , err := url .Parse (c .config .ConsoleAPIURL )
1300+ if err != nil {
1301+ return nil , fmt .Errorf ("invalid console_api_url %q: %w" , c .config .ConsoleAPIURL , err )
1302+ }
1303+ requestURL := baseURL .JoinPath (path )
1304+ req , err := http .NewRequestWithContext (ctx , method , requestURL .String (), body )
1305+ if err != nil {
1306+ return nil , err
1307+ }
1308+ req .Header .Set ("Authorization" , "Bearer " + c .config .WorkspaceAPIKey )
1309+ if body != nil {
1310+ req .Header .Set ("Content-Type" , "application/json" )
1311+ }
1312+ httpClient := http .DefaultClient
1313+ if c .consoleClient != nil {
1314+ if cfg := c .consoleClient .GetConfig (); cfg .HTTPClient != nil {
1315+ httpClient = cfg .HTTPClient
1316+ }
1317+ }
1318+ return httpClient .Do (req ) // #nosec G704 -- URL is constructed from trusted provider configuration
1319+ }
1320+
1321+ // ListCustomDomains lists all custom domains for a project.
1322+ func (c * OryClient ) ListCustomDomains (ctx context.Context , projectID string ) ([]ory.CustomDomain , error ) {
1323+ httpResp , err := c .consoleHTTPDo (ctx , http .MethodGet , "/projects/" + projectID + "/cname" , nil )
1324+ if err != nil {
1325+ return nil , wrapAPIError (err , "listing custom domains" )
1326+ }
1327+ defer httpResp .Body .Close ()
1328+
1329+ if httpResp .StatusCode != http .StatusOK {
1330+ respBody , _ := io .ReadAll (httpResp .Body )
1331+ return nil , wrapAPIError (
1332+ fmt .Errorf ("unexpected status %d: %s" , httpResp .StatusCode , string (respBody )),
1333+ "listing custom domains" ,
1334+ )
1335+ }
1336+
1337+ var domains []ory.CustomDomain
1338+ if err := json .NewDecoder (httpResp .Body ).Decode (& domains ); err != nil {
1339+ return nil , fmt .Errorf ("listing custom domains: decoding response: %w" , err )
1340+ }
1341+ return domains , nil
1342+ }
1343+
1344+ // GetCustomDomain gets a custom domain by ID from the list.
1345+ func (c * OryClient ) GetCustomDomain (ctx context.Context , projectID , domainID string ) (* ory.CustomDomain , error ) {
1346+ domains , err := c .ListCustomDomains (ctx , projectID )
1347+ if err != nil {
1348+ return nil , err
1349+ }
1350+ for i := range domains {
1351+ if domains [i ].GetId () == domainID {
1352+ return & domains [i ], nil
1353+ }
1354+ }
1355+ return nil , fmt .Errorf ("%w: %s in project %s" , ErrCustomDomainNotFound , domainID , projectID )
1356+ }
1357+
1358+ // CreateCustomDomain creates a new custom domain for a project.
1359+ func (c * OryClient ) CreateCustomDomain (ctx context.Context , projectID string , body ory.CreateCustomDomainBody ) (* ory.CustomDomain , error ) {
1360+ bodyBytes , err := json .Marshal (body )
1361+ if err != nil {
1362+ return nil , fmt .Errorf ("creating custom domain: marshaling body: %w" , err )
1363+ }
1364+
1365+ httpResp , err := c .consoleHTTPDo (ctx , http .MethodPost , "/projects/" + projectID + "/cname" , bytes .NewReader (bodyBytes ))
1366+ if err != nil {
1367+ return nil , wrapAPIError (err , "creating custom domain" )
1368+ }
1369+ defer httpResp .Body .Close ()
1370+
1371+ if httpResp .StatusCode != http .StatusCreated {
1372+ respBody , _ := io .ReadAll (httpResp .Body )
1373+ return nil , wrapAPIError (
1374+ fmt .Errorf ("unexpected status %d: %s" , httpResp .StatusCode , string (respBody )),
1375+ "creating custom domain" ,
1376+ )
1377+ }
1378+
1379+ var domain ory.CustomDomain
1380+ if err := json .NewDecoder (httpResp .Body ).Decode (& domain ); err != nil {
1381+ return nil , fmt .Errorf ("creating custom domain: decoding response: %w" , err )
1382+ }
1383+ return & domain , nil
1384+ }
1385+
1386+ // UpdateCustomDomain updates an existing custom domain.
1387+ func (c * OryClient ) UpdateCustomDomain (ctx context.Context , projectID , domainID string , body ory.SetCustomDomainBody ) (* ory.CustomDomain , error ) {
1388+ bodyBytes , err := json .Marshal (body )
1389+ if err != nil {
1390+ return nil , fmt .Errorf ("updating custom domain: marshaling body: %w" , err )
1391+ }
1392+
1393+ httpResp , err := c .consoleHTTPDo (ctx , http .MethodPut , "/projects/" + projectID + "/cname/" + domainID , bytes .NewReader (bodyBytes ))
1394+ if err != nil {
1395+ return nil , wrapAPIError (err , "updating custom domain" )
1396+ }
1397+ defer httpResp .Body .Close ()
1398+
1399+ if httpResp .StatusCode != http .StatusOK {
1400+ respBody , _ := io .ReadAll (httpResp .Body )
1401+ return nil , wrapAPIError (
1402+ fmt .Errorf ("unexpected status %d: %s" , httpResp .StatusCode , string (respBody )),
1403+ "updating custom domain" ,
1404+ )
1405+ }
1406+
1407+ var domain ory.CustomDomain
1408+ if err := json .NewDecoder (httpResp .Body ).Decode (& domain ); err != nil {
1409+ return nil , fmt .Errorf ("updating custom domain: decoding response: %w" , err )
1410+ }
1411+ return & domain , nil
1412+ }
1413+
1414+ // DeleteCustomDomain deletes a custom domain.
1415+ func (c * OryClient ) DeleteCustomDomain (ctx context.Context , projectID , domainID string ) error {
1416+ httpResp , err := c .consoleHTTPDo (ctx , http .MethodDelete , "/projects/" + projectID + "/cname/" + domainID , nil )
1417+ if err != nil {
1418+ return wrapAPIError (err , "deleting custom domain" )
1419+ }
1420+ defer httpResp .Body .Close ()
1421+
1422+ if httpResp .StatusCode != http .StatusNoContent {
1423+ respBody , _ := io .ReadAll (httpResp .Body )
1424+ return wrapAPIError (
1425+ fmt .Errorf ("unexpected status %d: %s" , httpResp .StatusCode , string (respBody )),
1426+ "deleting custom domain" ,
1427+ )
1428+ }
1429+ return nil
1430+ }
1431+
12851432// ListWorkspaces lists all workspaces.
12861433func (c * OryClient ) ListWorkspaces (ctx context.Context ) ([]ory.Workspace , error ) {
12871434 resp , httpResp , err := c .consoleClient .WorkspaceAPI .ListWorkspaces (ctx ).Execute ()
0 commit comments