136 lines
5.2 KiB
Go
136 lines
5.2 KiB
Go
package hostnames
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/go-errors/errors"
|
|
"github.com/supabase/cli/internal/utils"
|
|
"github.com/supabase/cli/pkg/api"
|
|
)
|
|
|
|
func GetCustomHostnameConfig(ctx context.Context, projectRef string) (*api.V1GetHostnameConfigResponse, error) {
|
|
resp, err := utils.GetSupabase().V1GetHostnameConfigWithResponse(ctx, projectRef)
|
|
if err != nil {
|
|
return nil, errors.Errorf("failed to get custom hostname: %w", err)
|
|
}
|
|
if resp.JSON200 == nil {
|
|
return nil, errors.New("failed to get custom hostname config; received: " + string(resp.Body))
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
func VerifyCNAME(ctx context.Context, projectRef string, customHostname string) error {
|
|
expectedEndpoint := fmt.Sprintf("%s.", utils.GetSupabaseHost(projectRef))
|
|
cname, err := utils.ResolveCNAME(ctx, customHostname)
|
|
if err != nil {
|
|
return errors.Errorf("expected custom hostname '%s' to have a CNAME record pointing to your project at '%s', but it failed to resolve: %w", customHostname, expectedEndpoint, err)
|
|
}
|
|
if cname != expectedEndpoint {
|
|
return errors.Errorf("expected custom hostname '%s' to have a CNAME record pointing to your project at '%s', but it is currently set to '%s'", customHostname, expectedEndpoint, cname)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type RawResponse struct {
|
|
Result struct {
|
|
CustomOriginServer string `json:"custom_origin_server"`
|
|
OwnershipVerification struct {
|
|
Name string
|
|
Type string
|
|
Value string
|
|
} `json:"ownership_verification"`
|
|
Ssl struct {
|
|
ValidationRecords []struct {
|
|
Status string `json:"status"`
|
|
TxtName string `json:"txt_name"`
|
|
TxtValue string `json:"txt_value"`
|
|
} `json:"validation_records"`
|
|
ValidationErrors []struct {
|
|
Message string `json:"message"`
|
|
} `json:"validation_errors"`
|
|
Status string `json:"status"`
|
|
}
|
|
} `json:"result"`
|
|
}
|
|
|
|
func serializeRawOutput(response *api.UpdateCustomHostnameResponse) (string, error) {
|
|
output, err := json.MarshalIndent(response, "", " ")
|
|
if err != nil {
|
|
return "", errors.Errorf("failed to serialize json: %w", err)
|
|
}
|
|
return string(output), nil
|
|
}
|
|
|
|
func appendRawOutputIfNeeded(status string, response *api.UpdateCustomHostnameResponse, includeRawOutput bool) string {
|
|
if !includeRawOutput {
|
|
return status
|
|
}
|
|
rawOutput, err := serializeRawOutput(response)
|
|
if err != nil {
|
|
return fmt.Sprintf("%s\nFailed to serialize raw output: %+v\n", status, err)
|
|
}
|
|
return fmt.Sprintf("%s\nRaw output follows:\n%s\n", status, rawOutput)
|
|
}
|
|
|
|
func TranslateStatus(response *api.UpdateCustomHostnameResponse, includeRawOutput bool) (string, error) {
|
|
if response.Status == api.N5ServicesReconfigured {
|
|
return appendRawOutputIfNeeded(fmt.Sprintf("Custom hostname setup completed. Project is now accessible at %s.", response.CustomHostname), response, includeRawOutput), nil
|
|
}
|
|
if response.Status == api.N4OriginSetupCompleted {
|
|
var res RawResponse
|
|
rawBody, err := json.Marshal(response.Data)
|
|
if err != nil {
|
|
return "", errors.Errorf("failed to serialize body: %w", err)
|
|
}
|
|
err = json.Unmarshal(rawBody, &res)
|
|
if err != nil {
|
|
return "", errors.Errorf("failed to deserialize body: %w", err)
|
|
}
|
|
return appendRawOutputIfNeeded(fmt.Sprintf(`Custom hostname configuration complete, and ready for activation.
|
|
|
|
Please ensure that your custom domain is set up as a CNAME record to your Supabase subdomain:
|
|
%s CNAME -> %s`, response.CustomHostname, res.Result.CustomOriginServer), response, includeRawOutput), nil
|
|
}
|
|
if response.Status == api.N2Initiated {
|
|
var res RawResponse
|
|
rawBody, err := json.Marshal(response.Data)
|
|
if err != nil {
|
|
return "", errors.Errorf("failed to serialize body: %w", err)
|
|
}
|
|
err = json.Unmarshal(rawBody, &res)
|
|
if err != nil {
|
|
return "", errors.Errorf("failed to deserialize body: %w", err)
|
|
}
|
|
ssl := res.Result.Ssl.ValidationRecords
|
|
if res.Result.Ssl.Status == "initializing" {
|
|
return appendRawOutputIfNeeded("Custom hostname setup is being initialized; please request re-verification in a few seconds.\n", response, includeRawOutput), nil
|
|
}
|
|
if len(res.Result.Ssl.ValidationErrors) > 0 {
|
|
var errorMessages []string
|
|
for _, valError := range res.Result.Ssl.ValidationErrors {
|
|
if strings.Contains(valError.Message, "caa_error") {
|
|
return appendRawOutputIfNeeded("CAA mismatch; please remove any existing CAA records on your domain, or add one for \"digicert.com\"\n", response, includeRawOutput), nil
|
|
}
|
|
errorMessages = append(errorMessages, valError.Message)
|
|
}
|
|
valErrors := strings.Join(errorMessages, "\n\t- ")
|
|
return appendRawOutputIfNeeded(fmt.Sprintf("SSL validation errors: \n\t- %s\n", valErrors), response, includeRawOutput), nil
|
|
}
|
|
if len(ssl) != 1 {
|
|
return "", errors.Errorf("expected a single SSL verification record, received: %+v", ssl)
|
|
}
|
|
records := ""
|
|
if ssl[0].TxtName != "" {
|
|
records = fmt.Sprintf("%s\n\t%s TXT -> %s", records, ssl[0].TxtName, ssl[0].TxtValue)
|
|
}
|
|
status := fmt.Sprintf("Custom hostname verification in-progress; please configure the appropriate DNS entries and request re-verification.\n"+
|
|
"Required outstanding validation records: %s\n",
|
|
records)
|
|
return appendRawOutputIfNeeded(status, response, includeRawOutput), nil
|
|
}
|
|
return appendRawOutputIfNeeded("Custom hostname configuration not started.", response, includeRawOutput), nil
|
|
}
|