diff --git a/docs/auth0_email.md b/docs/auth0_email.md index 80227283c..159b96579 100644 --- a/docs/auth0_email.md +++ b/docs/auth0_email.md @@ -8,5 +8,6 @@ You can configure a test SMTP email server in your development or test environme ## Commands +- [auth0 email provider](auth0_email_provider.md) - Manage custom email provider - [auth0 email templates](auth0_email_templates.md) - Manage custom email templates diff --git a/docs/auth0_email_provider.md b/docs/auth0_email_provider.md new file mode 100644 index 000000000..ff9f33eaa --- /dev/null +++ b/docs/auth0_email_provider.md @@ -0,0 +1,16 @@ +--- +layout: default +has_toc: false +has_children: true +--- +# auth0 email provider + +Manage custom email provider for the tenant. + +## Commands + +- [auth0 email provider create](auth0_email_provider_create.md) - Create the email provider +- [auth0 email provider delete](auth0_email_provider_delete.md) - Delete the email provider +- [auth0 email provider show](auth0_email_provider_show.md) - Show the email provider +- [auth0 email provider update](auth0_email_provider_update.md) - Update the email provider + diff --git a/docs/auth0_email_provider_create.md b/docs/auth0_email_provider_create.md new file mode 100644 index 000000000..aa678ffbb --- /dev/null +++ b/docs/auth0_email_provider_create.md @@ -0,0 +1,68 @@ +--- +layout: default +parent: auth0 email provider +has_toc: false +--- +# auth0 email provider create + +Create the email provider. + +To create interactively, use `auth0 email provider create` with no arguments. + +To create non-interactively, supply the provider name and other information through the flags. + +## Usage +``` +auth0 email provider create [flags] +``` + +## Examples + +``` + auth0 email provider create + auth0 email provider create --json + auth0 email provider create --provider mandrill --enabled=true --credentials='{ "api_key":"TheAPIKey" }' --settings='{ "message": { "view_control_link": true } }' + auth0 email provider create --provider mandrill --default-from-address='admin@example.com' --credentials='{ "api_key":"TheAPIKey" }' --settings='{ "message": { "view_control_link": true } }' + auth0 email provider create --provider ses --credentials='{ "accessKeyId":"TheAccessKeyId", "secretAccessKey":"TheSecretAccessKey", "region":"eu" }' --settings='{ "message": { "configuration_set_name": "TheConfigurationSetName" } }' + auth0 email provider create --provider sendgrid --credentials='{ "api_key":"TheAPIKey" }' + auth0 email provider create --provider sparkpost --credentials='{ "api_key":"TheAPIKey" }' + auth0 email provider create --provider sparkpost --credentials='{ "api_key":"TheAPIKey", "region":"eu" }' + auth0 email provider create --provider mailgun --credentials='{ "api_key":"TheAPIKey", "domain": "example.com"}' + auth0 email provider create --provider mailgun --credentials='{ "api_key":"TheAPIKey", "domain": "example.com", "region":"eu" }' + auth0 email provider create --provider smtp --credentials='{ "smtp_host":"smtp.example.com", "smtp_port":25, "smtp_user":"smtp", "smtp_pass":"TheSMTPPassword" }' + auth0 email provider create --provider azure_cs --credentials='{ "connection_string":"TheConnectionString" }' + auth0 email provider create --provider ms365 --credentials='{ "tenantId":"TheTenantId", "clientId":"TheClientID", "clientSecret":"TheClientSecret" }' + auth0 email provider create --provider custom --enabled=true --default-from-address="admin@example.com" +``` + + +## Flags + +``` + -c, --credentials string Credentials for the email provider, formatted as JSON. + -f, --default-from-address string Provider default FROM address if none is specified. + -e, --enabled Whether the provided is enabled (true) or disabled (false). (default true) + --json Output in json format. + -p, --provider string Provider name. Can be 'mandrill', 'ses', 'sendgrid', 'sparkpost', 'mailgun', 'smtp', 'azure_cs', 'ms365', or 'custom' + -s, --settings string Settings for the email provider. formatted as JSON. +``` + + +## Inherited Flags + +``` + --debug Enable debug mode. + --no-color Disable colors. + --no-input Disable interactivity. + --tenant string Specific tenant to use. +``` + + +## Related Commands + +- [auth0 email provider create](auth0_email_provider_create.md) - Create the email provider +- [auth0 email provider delete](auth0_email_provider_delete.md) - Delete the email provider +- [auth0 email provider show](auth0_email_provider_show.md) - Show the email provider +- [auth0 email provider update](auth0_email_provider_update.md) - Update the email provider + + diff --git a/docs/auth0_email_provider_delete.md b/docs/auth0_email_provider_delete.md new file mode 100644 index 000000000..e158d70b8 --- /dev/null +++ b/docs/auth0_email_provider_delete.md @@ -0,0 +1,53 @@ +--- +layout: default +parent: auth0 email provider +has_toc: false +--- +# auth0 email provider delete + +Delete the email provider. + +To delete interactively, use `auth0 email provider delete` with no arguments. + +To delete non-interactively, supply the the `--force` flag to skip confirmation. + +## Usage +``` +auth0 email provider delete [flags] +``` + +## Examples + +``` + auth0 provider delete + auth0 email provider rm + auth0 email provider delete --force + auth0 email provider rm --force +``` + + +## Flags + +``` + --force Skip confirmation. +``` + + +## Inherited Flags + +``` + --debug Enable debug mode. + --no-color Disable colors. + --no-input Disable interactivity. + --tenant string Specific tenant to use. +``` + + +## Related Commands + +- [auth0 email provider create](auth0_email_provider_create.md) - Create the email provider +- [auth0 email provider delete](auth0_email_provider_delete.md) - Delete the email provider +- [auth0 email provider show](auth0_email_provider_show.md) - Show the email provider +- [auth0 email provider update](auth0_email_provider_update.md) - Update the email provider + + diff --git a/docs/auth0_email_provider_show.md b/docs/auth0_email_provider_show.md new file mode 100644 index 000000000..5d586f3b5 --- /dev/null +++ b/docs/auth0_email_provider_show.md @@ -0,0 +1,47 @@ +--- +layout: default +parent: auth0 email provider +has_toc: false +--- +# auth0 email provider show + +Display information about the email provider. + +## Usage +``` +auth0 email provider show [flags] +``` + +## Examples + +``` + auth0 email provider show + auth0 email provider show --json +``` + + +## Flags + +``` + --json Output in json format. +``` + + +## Inherited Flags + +``` + --debug Enable debug mode. + --no-color Disable colors. + --no-input Disable interactivity. + --tenant string Specific tenant to use. +``` + + +## Related Commands + +- [auth0 email provider create](auth0_email_provider_create.md) - Create the email provider +- [auth0 email provider delete](auth0_email_provider_delete.md) - Delete the email provider +- [auth0 email provider show](auth0_email_provider_show.md) - Show the email provider +- [auth0 email provider update](auth0_email_provider_update.md) - Update the email provider + + diff --git a/docs/auth0_email_provider_update.md b/docs/auth0_email_provider_update.md new file mode 100644 index 000000000..abbfc5c09 --- /dev/null +++ b/docs/auth0_email_provider_update.md @@ -0,0 +1,72 @@ +--- +layout: default +parent: auth0 email provider +has_toc: false +--- +# auth0 email provider update + +Update the email provider. + +To update interactively, use `auth0 email provider update` with no arguments. + +To update non-interactively, supply the provider name and other information through the flags. + +## Usage +``` +auth0 email provider update [flags] +``` + +## Examples + +``` + auth0 email provider update + auth0 email provider update --json + auth0 email provider update --enabled=false + auth0 email provider update --credentials='{ "api_key":"NewAPIKey" }' + auth0 email provider update --settings='{ "message": { "view_control_link": true } }' + auth0 email provider update --default-from-address="admin@example.com" + auth0 email provider update --provider mandrill --enabled=true --credentials='{ "api_key":"TheAPIKey" }' --settings='{ "message": { "view_control_link": true } }' + auth0 email provider update --provider mandrill --default-from-address='admin@example.com' --credentials='{ "api_key":"TheAPIKey" }' --settings='{ "message": { "view_control_link": true } }' + auth0 email provider update --provider ses --credentials='{ "accessKeyId":"TheAccessKeyId", "secretAccessKey":"TheSecretAccessKey", "region":"eu" }' --settings='{ "message": { "configuration_set_name": "TheConfigurationSetName" } }' + auth0 email provider update --provider sendgrid --credentials='{ "api_key":"TheAPIKey" }' + auth0 email provider update --provider sparkpost --credentials='{ "api_key":"TheAPIKey" }' + auth0 email provider update --provider sparkpost --credentials='{ "api_key":"TheAPIKey", "region":"eu" }' + auth0 email provider update --provider mailgun --credentials='{ "api_key":"TheAPIKey", "domain": "example.com"}' + auth0 email provider update --provider mailgun --credentials='{ "api_key":"TheAPIKey", "domain": "example.com", "region":"eu" }' + auth0 email provider update --provider smtp --credentials='{ "smtp_host":"smtp.example.com", "smtp_port":25, "smtp_user":"smtp", "smtp_pass":"TheSMTPPassword" }' + auth0 email provider update --provider azure_cs --credentials='{ "connection_string":"TheConnectionString" }' + auth0 email provider update --provider ms365 --credentials='{ "tenantId":"TheTenantId", "clientId":"TheClientID", "clientSecret":"TheClientSecret" }' + auth0 email provider update --provider custom --enabled=true --default-from-address="admin@example.com" +``` + + +## Flags + +``` + -c, --credentials string Credentials for the email provider, formatted as JSON. + -f, --default-from-address string Provider default FROM address if none is specified. + -e, --enabled Whether the provided is enabled (true) or disabled (false). (default true) + --json Output in json format. + -p, --provider string Provider name. Can be 'mandrill', 'ses', 'sendgrid', 'sparkpost', 'mailgun', 'smtp', 'azure_cs', 'ms365', or 'custom' + -s, --settings string Settings for the email provider. formatted as JSON. +``` + + +## Inherited Flags + +``` + --debug Enable debug mode. + --no-color Disable colors. + --no-input Disable interactivity. + --tenant string Specific tenant to use. +``` + + +## Related Commands + +- [auth0 email provider create](auth0_email_provider_create.md) - Create the email provider +- [auth0 email provider delete](auth0_email_provider_delete.md) - Delete the email provider +- [auth0 email provider show](auth0_email_provider_show.md) - Show the email provider +- [auth0 email provider update](auth0_email_provider_update.md) - Update the email provider + + diff --git a/internal/auth0/email_provider.go b/internal/auth0/email_provider.go index 127217feb..65b788b85 100644 --- a/internal/auth0/email_provider.go +++ b/internal/auth0/email_provider.go @@ -9,7 +9,20 @@ import ( ) type EmailProviderAPI interface { + // Create email provider. + // See: https://auth0.com/docs/api/management/v2#!/Emails/post_provider + Create(ctx context.Context, ep *management.EmailProvider, opts ...management.RequestOption) error + + // Update email provider. + // See: https://auth0.com/docs/api/management/v2#!/Emails/patch_provider + Update(ctx context.Context, ep *management.EmailProvider, opts ...management.RequestOption) (err error) + // Read email provider details. // See: https://auth0.com/docs/api/management/v2#!/Emails/get_provider Read(ctx context.Context, opts ...management.RequestOption) (ep *management.EmailProvider, err error) + + // Delete the email provider. + // + // See: https://auth0.com/docs/api/management/v2#!/Emails/delete_provider + Delete(ctx context.Context, opts ...management.RequestOption) (err error) } diff --git a/internal/auth0/mock/email_provider_mock.go b/internal/auth0/mock/email_provider_mock.go index 149873b61..b797209d2 100644 --- a/internal/auth0/mock/email_provider_mock.go +++ b/internal/auth0/mock/email_provider_mock.go @@ -35,6 +35,44 @@ func (m *MockEmailProviderAPI) EXPECT() *MockEmailProviderAPIMockRecorder { return m.recorder } +// Create mocks base method. +func (m *MockEmailProviderAPI) Create(ctx context.Context, ep *management.EmailProvider, opts ...management.RequestOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, ep} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Create", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockEmailProviderAPIMockRecorder) Create(ctx, ep interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, ep}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockEmailProviderAPI)(nil).Create), varargs...) +} + +// Delete mocks base method. +func (m *MockEmailProviderAPI) Delete(ctx context.Context, opts ...management.RequestOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{ctx} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Delete", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockEmailProviderAPIMockRecorder) Delete(ctx interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockEmailProviderAPI)(nil).Delete), varargs...) +} + // Read mocks base method. func (m *MockEmailProviderAPI) Read(ctx context.Context, opts ...management.RequestOption) (*management.EmailProvider, error) { m.ctrl.T.Helper() @@ -54,3 +92,22 @@ func (mr *MockEmailProviderAPIMockRecorder) Read(ctx interface{}, opts ...interf varargs := append([]interface{}{ctx}, opts...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockEmailProviderAPI)(nil).Read), varargs...) } + +// Update mocks base method. +func (m *MockEmailProviderAPI) Update(ctx context.Context, ep *management.EmailProvider, opts ...management.RequestOption) error { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, ep} + for _, a := range opts { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Update indicates an expected call of Update. +func (mr *MockEmailProviderAPIMockRecorder) Update(ctx, ep interface{}, opts ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, ep}, opts...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockEmailProviderAPI)(nil).Update), varargs...) +} diff --git a/internal/cli/actions.go b/internal/cli/actions.go index 7bb9e8958..ede8a013f 100644 --- a/internal/cli/actions.go +++ b/internal/cli/actions.go @@ -65,6 +65,7 @@ var ( "post-user-registration": actionTemplatePostUserRegistration, "post-change-password": actionTemplatePostChangePassword, "send-phone-message": actionTemplateSendPhoneMessage, + "custom-email-provider": actionTemplateCustomEmailProvider, } ) diff --git a/internal/cli/actions_embed.go b/internal/cli/actions_embed.go index 6147d548e..49b75f090 100644 --- a/internal/cli/actions_embed.go +++ b/internal/cli/actions_embed.go @@ -23,6 +23,9 @@ var ( //go:embed data/action-template-send-phone-message.js actionTemplateSendPhoneMessage string + //go:embed data/action-template-custom-email-provider.js + actionTemplateCustomEmailProvider string + //go:embed data/action-template-empty.js actionTemplateEmpty string ) diff --git a/internal/cli/data/action-template-custom-email-provider.js b/internal/cli/data/action-template-custom-email-provider.js new file mode 100644 index 000000000..0c3428ea8 --- /dev/null +++ b/internal/cli/data/action-template-custom-email-provider.js @@ -0,0 +1,9 @@ +/** +* Handler to be executed while sending an email notification +* @param {Event} event - Details about the user and the context in which they are logging in. +* @param {CustomEmailProviderAPI} api - Methods and utilities to help change the behavior of sending a email notification. +*/ +exports.onExecuteCustomEmailProvider = async (event, api) => { + // Code goes here + return; +}; diff --git a/internal/cli/email.go b/internal/cli/email.go index 2c68ed277..11a5f4aaf 100644 --- a/internal/cli/email.go +++ b/internal/cli/email.go @@ -13,6 +13,7 @@ func emailCmd(cli *cli) *cobra.Command { } cmd.AddCommand(emailTemplateCmd(cli)) + cmd.AddCommand(emailProviderCmd(cli)) return cmd } diff --git a/internal/cli/email_provider.go b/internal/cli/email_provider.go new file mode 100644 index 000000000..a9aa5e823 --- /dev/null +++ b/internal/cli/email_provider.go @@ -0,0 +1,425 @@ +package cli + +import ( + "encoding/json" + "fmt" + + "github.com/auth0/go-auth0/management" + "github.com/spf13/cobra" + + "github.com/auth0/auth0-cli/internal/ansi" + "github.com/auth0/auth0-cli/internal/prompt" +) + +const ( + emailProviderMandrill = management.EmailProviderMandrill + emailProviderSES = management.EmailProviderSES + emailProviderSendGrid = management.EmailProviderSendGrid + emailProviderSparkPost = management.EmailProviderSparkPost + emailProviderMailgun = management.EmailProviderMailgun + emailProviderSMTP = management.EmailProviderSMTP + emailProviderAzureCS = management.EmailProviderAzureCS + emailProviderMS365 = management.EmailProviderMS365 + emailProviderCustom = management.EmailProviderCustom +) + +var ( + providerNameOptions = []string{ + emailProviderMandrill, + emailProviderSES, + emailProviderSendGrid, + emailProviderSparkPost, + emailProviderMailgun, + emailProviderSMTP, + emailProviderAzureCS, + emailProviderMS365, + emailProviderCustom, + } + + emailProviderName = Flag{ + Name: "Provider", + LongForm: "provider", + ShortForm: "p", + Help: fmt.Sprintf("Provider name. Can be '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', or '%s'", + emailProviderMandrill, + emailProviderSES, + emailProviderSendGrid, + emailProviderSparkPost, + emailProviderMailgun, + emailProviderSMTP, + emailProviderAzureCS, + emailProviderMS365, + emailProviderCustom), + AlwaysPrompt: true, + } + + emailProviderFrom = Flag{ + Name: "DefaultFromAddress", + LongForm: "default-from-address", + ShortForm: "f", + Help: "Provider default FROM address if none is specified.", + AlwaysPrompt: true, + } + + emailProviderCredentials = Flag{ + Name: "Credentials", + LongForm: "credentials", + ShortForm: "c", + Help: "Credentials for the email provider, formatted as JSON.", + AlwaysPrompt: true, + } + + emailProviderSettings = Flag{ + Name: "Settings", + LongForm: "settings", + ShortForm: "s", + Help: "Settings for the email provider. formatted as JSON.", + AlwaysPrompt: true, + } + + emailProviderEnabled = Flag{ + Name: "Enabled", + LongForm: "enabled", + ShortForm: "e", + Help: "Whether the provided is enabled (true) or disabled (false).", + AlwaysPrompt: true, + } +) + +func emailProviderCmd(cli *cli) *cobra.Command { + cmd := &cobra.Command{ + Use: "provider", + Short: "Manage custom email provider", + Long: "Manage custom email provider for the tenant.", + } + + cmd.SetUsageTemplate(resourceUsageTemplate()) + cmd.AddCommand(showEmailProviderCmd(cli)) + cmd.AddCommand(createEmailProviderCmd(cli)) + cmd.AddCommand(updateEmailProviderCmd(cli)) + cmd.AddCommand(deleteEmailProviderCmd(cli)) + return cmd +} + +func showEmailProviderCmd(cli *cli) *cobra.Command { + cmd := &cobra.Command{ + Use: "show", + Args: cobra.NoArgs, + Short: "Show the email provider", + Long: "Display information about the email provider.", + Example: ` auth0 email provider show + auth0 email provider show --json`, + RunE: func(cmd *cobra.Command, args []string) error { + var emailProvider *management.EmailProvider + if err := ansi.Waiting(func() (err error) { + emailProvider, err = cli.api.EmailProvider.Read(cmd.Context()) + return err + }); err != nil { + return fmt.Errorf("failed to read email provider: %w", err) + } + + return cli.renderer.EmailProviderShow(emailProvider) + }, + } + + cmd.Flags().BoolVar(&cli.json, "json", false, "Output in json format.") + + return cmd +} + +func createEmailProviderCmd(cli *cli) *cobra.Command { + var inputs struct { + name string + defaultFromAddress string + credentials string + settings string + enabled bool + } + + cmd := &cobra.Command{ + Use: "create", + Args: cobra.NoArgs, + Short: "Create the email provider", + Long: "Create the email provider.\n\n" + + "To create interactively, use `auth0 email provider create` with no arguments.\n\n" + + "To create non-interactively, supply the provider name and other information " + + "through the flags.", + Example: ` auth0 email provider create + auth0 email provider create --json + auth0 email provider create --provider mandrill --enabled=true --credentials='{ "api_key":"TheAPIKey" }' --settings='{ "message": { "view_control_link": true } }' + auth0 email provider create --provider mandrill --default-from-address='admin@example.com' --credentials='{ "api_key":"TheAPIKey" }' --settings='{ "message": { "view_control_link": true } }' + auth0 email provider create --provider ses --credentials='{ "accessKeyId":"TheAccessKeyId", "secretAccessKey":"TheSecretAccessKey", "region":"eu" }' --settings='{ "message": { "configuration_set_name": "TheConfigurationSetName" } }' + auth0 email provider create --provider sendgrid --credentials='{ "api_key":"TheAPIKey" }' + auth0 email provider create --provider sparkpost --credentials='{ "api_key":"TheAPIKey" }' + auth0 email provider create --provider sparkpost --credentials='{ "api_key":"TheAPIKey", "region":"eu" }' + auth0 email provider create --provider mailgun --credentials='{ "api_key":"TheAPIKey", "domain": "example.com"}' + auth0 email provider create --provider mailgun --credentials='{ "api_key":"TheAPIKey", "domain": "example.com", "region":"eu" }' + auth0 email provider create --provider smtp --credentials='{ "smtp_host":"smtp.example.com", "smtp_port":25, "smtp_user":"smtp", "smtp_pass":"TheSMTPPassword" }' + auth0 email provider create --provider azure_cs --credentials='{ "connection_string":"TheConnectionString" }' + auth0 email provider create --provider ms365 --credentials='{ "tenantId":"TheTenantId", "clientId":"TheClientID", "clientSecret":"TheClientSecret" }' + auth0 email provider create --provider custom --enabled=true --default-from-address="admin@example.com"`, + RunE: func(cmd *cobra.Command, args []string) error { + if err := emailProviderName.Select(cmd, &inputs.name, providerNameOptions, nil); err != nil { + return err + } + if err := emailProviderFrom.Ask(cmd, &inputs.defaultFromAddress, nil); err != nil { + return err + } + if err := emailProviderEnabled.AskBool(cmd, &inputs.enabled, nil); err != nil { + return err + } + + var credentials map[string]interface{} + if inputs.name == emailProviderCustom { + if len(inputs.credentials) > 0 { + return fmt.Errorf("credentials not supported for provider: %s", inputs.name) + } + credentials = make(map[string]interface{}) + } else { + if err := emailProviderCredentials.Ask(cmd, &inputs.credentials, nil); err != nil { + return err + } + if err := json.Unmarshal([]byte(inputs.credentials), &credentials); err != nil { + return fmt.Errorf("provider: %s credentials invalid JSON: %w", inputs.name, err) + } + } + + var settings map[string]interface{} + switch inputs.name { + case emailProviderMandrill, emailProviderSES, emailProviderSMTP: + if err := emailProviderSettings.Ask(cmd, &inputs.settings, nil); err != nil { + return err + } + if len(inputs.settings) > 0 { + if err := json.Unmarshal([]byte(inputs.settings), &settings); err != nil { + return fmt.Errorf("provider: %s settings invalid JSON: %w", inputs.name, err) + } + } + case emailProviderSendGrid, + emailProviderSparkPost, + emailProviderMailgun, + emailProviderAzureCS, + emailProviderMS365, + emailProviderCustom: + if len(inputs.settings) > 0 { + return fmt.Errorf("settings not supported for provider: %s", inputs.name) + } + default: + return fmt.Errorf("unknown provider: %s", inputs.name) + } + + emailProvider := &management.EmailProvider{ + Name: &inputs.name, + Enabled: &inputs.enabled, + } + + if len(inputs.defaultFromAddress) > 0 { + emailProvider.DefaultFromAddress = &inputs.defaultFromAddress + } + + if credentials != nil { + emailProvider.Credentials = &credentials + } + + if settings != nil { + emailProvider.Settings = &settings + } + + if err := ansi.Waiting(func() error { + return cli.api.EmailProvider.Create(cmd.Context(), emailProvider) + }); err != nil { + return fmt.Errorf("failed to create email provider %s: %w", inputs.name, err) + } + + return cli.renderer.EmailProviderCreate(emailProvider) + }, + } + + cmd.Flags().BoolVar(&cli.json, "json", false, "Output in json format.") + + emailProviderName.RegisterString(cmd, &inputs.name, "") + emailProviderFrom.RegisterString(cmd, &inputs.defaultFromAddress, "") + emailProviderCredentials.RegisterString(cmd, &inputs.credentials, "") + emailProviderSettings.RegisterString(cmd, &inputs.settings, "") + emailProviderEnabled.RegisterBool(cmd, &inputs.enabled, true) + + return cmd +} + +func updateEmailProviderCmd(cli *cli) *cobra.Command { + var inputs struct { + name string + defaultFromAddress string + credentials string + settings string + enabled bool + } + + cmd := &cobra.Command{ + Use: "update", + Args: cobra.NoArgs, + Short: "Update the email provider", + Long: "Update the email provider.\n\n" + + "To update interactively, use `auth0 email provider update` with no arguments.\n\n" + + "To update non-interactively, supply the provider name and other information " + + "through the flags.", + Example: ` auth0 email provider update + auth0 email provider update --json + auth0 email provider update --enabled=false + auth0 email provider update --credentials='{ "api_key":"NewAPIKey" }' + auth0 email provider update --settings='{ "message": { "view_control_link": true } }' + auth0 email provider update --default-from-address="admin@example.com" + auth0 email provider update --provider mandrill --enabled=true --credentials='{ "api_key":"TheAPIKey" }' --settings='{ "message": { "view_control_link": true } }' + auth0 email provider update --provider mandrill --default-from-address='admin@example.com' --credentials='{ "api_key":"TheAPIKey" }' --settings='{ "message": { "view_control_link": true } }' + auth0 email provider update --provider ses --credentials='{ "accessKeyId":"TheAccessKeyId", "secretAccessKey":"TheSecretAccessKey", "region":"eu" }' --settings='{ "message": { "configuration_set_name": "TheConfigurationSetName" } }' + auth0 email provider update --provider sendgrid --credentials='{ "api_key":"TheAPIKey" }' + auth0 email provider update --provider sparkpost --credentials='{ "api_key":"TheAPIKey" }' + auth0 email provider update --provider sparkpost --credentials='{ "api_key":"TheAPIKey", "region":"eu" }' + auth0 email provider update --provider mailgun --credentials='{ "api_key":"TheAPIKey", "domain": "example.com"}' + auth0 email provider update --provider mailgun --credentials='{ "api_key":"TheAPIKey", "domain": "example.com", "region":"eu" }' + auth0 email provider update --provider smtp --credentials='{ "smtp_host":"smtp.example.com", "smtp_port":25, "smtp_user":"smtp", "smtp_pass":"TheSMTPPassword" }' + auth0 email provider update --provider azure_cs --credentials='{ "connection_string":"TheConnectionString" }' + auth0 email provider update --provider ms365 --credentials='{ "tenantId":"TheTenantId", "clientId":"TheClientID", "clientSecret":"TheClientSecret" }' + auth0 email provider update --provider custom --enabled=true --default-from-address="admin@example.com"`, + RunE: func(cmd *cobra.Command, args []string) error { + var currentProvider *management.EmailProvider + + if err := ansi.Waiting(func() (err error) { + currentProvider, err = cli.api.EmailProvider.Read(cmd.Context()) + return + }); err != nil { + return fmt.Errorf("failed to read email provider: %w", err) + } + + if err := emailProviderName.SelectU(cmd, &inputs.name, providerNameOptions, currentProvider.Name); err != nil { + return err + } + if err := emailProviderFrom.AskU(cmd, &inputs.defaultFromAddress, currentProvider.DefaultFromAddress); err != nil { + return err + } + if err := emailProviderEnabled.AskBoolU(cmd, &inputs.enabled, currentProvider.Enabled); err != nil { + return err + } + + var credentials map[string]interface{} + var settings map[string]interface{} + + emailProvider := &management.EmailProvider{} + + // Check if we are changing providers. + if len(inputs.name) > 0 && inputs.name != currentProvider.GetName() { + // Only set the name if we are changing it. + emailProvider.Name = &inputs.name + + // If we are changing providers, we need new credentials and settings. + if inputs.name == emailProviderCustom { + if len(inputs.credentials) > 0 { + return fmt.Errorf("credentials not supported for provider: %s", inputs.name) + } + credentials = make(map[string]interface{}) + } else { + if err := emailProviderCredentials.AskU(cmd, &inputs.credentials, nil); err != nil { + return err + } + if err := json.Unmarshal([]byte(inputs.credentials), &credentials); err != nil { + return fmt.Errorf("provider: %s credentials invalid JSON: %w", inputs.name, err) + } + } + + switch inputs.name { + case emailProviderMandrill, emailProviderSES, emailProviderSMTP: + if err := emailProviderSettings.AskU(cmd, &inputs.settings, nil); err != nil { + return err + } + if len(inputs.settings) > 0 { + if err := json.Unmarshal([]byte(inputs.settings), &settings); err != nil { + return fmt.Errorf("provider: %s settings invalid JSON: %w", inputs.name, err) + } + } + case emailProviderSendGrid, + emailProviderSparkPost, + emailProviderMailgun, + emailProviderAzureCS, + emailProviderMS365, + emailProviderCustom: + if len(inputs.settings) > 0 { + return fmt.Errorf("settings not supported for provider: %s", inputs.name) + } + default: + return fmt.Errorf("unknown provider: %s", inputs.name) + } + } + + // Set the flag if it was supplied or entered by the prompt. + if canPrompt(cmd) || emailProviderEnabled.IsSet(cmd) { + emailProvider.Enabled = &inputs.enabled + } + + if len(inputs.defaultFromAddress) > 0 { + emailProvider.DefaultFromAddress = &inputs.defaultFromAddress + } + + if credentials != nil { + emailProvider.Credentials = &credentials + } + + if settings != nil { + emailProvider.Settings = &settings + } + + if err := ansi.Waiting(func() error { + return cli.api.EmailProvider.Update(cmd.Context(), emailProvider) + }); err != nil { + return fmt.Errorf("failed to update email provider %s: %w", inputs.name, err) + } + + return cli.renderer.EmailProviderUpdate(emailProvider) + }, + } + + cmd.Flags().BoolVar(&cli.json, "json", false, "Output in json format.") + + emailProviderName.RegisterString(cmd, &inputs.name, "") + emailProviderFrom.RegisterString(cmd, &inputs.defaultFromAddress, "") + emailProviderCredentials.RegisterString(cmd, &inputs.credentials, "") + emailProviderSettings.RegisterString(cmd, &inputs.settings, "") + emailProviderEnabled.RegisterBool(cmd, &inputs.enabled, true) + + return cmd +} + +func deleteEmailProviderCmd(cli *cli) *cobra.Command { + cmd := &cobra.Command{ + Use: "delete", + Aliases: []string{"rm"}, + Args: cobra.NoArgs, + Short: "Delete the email provider", + Long: "Delete the email provider.\n\n" + + "To delete interactively, use `auth0 email provider delete` with no arguments.\n\n" + + "To delete non-interactively, supply the the `--force`" + + " flag to skip confirmation.", + Example: ` auth0 provider delete + auth0 email provider rm + auth0 email provider delete --force + auth0 email provider rm --force`, + RunE: func(cmd *cobra.Command, args []string) error { + if !cli.force && canPrompt(cmd) { + if confirmed := prompt.Confirm("Are you sure you want to proceed?"); !confirmed { + return nil + } + } + + if err := ansi.Waiting(func() error { + return cli.api.EmailProvider.Delete(cmd.Context()) + }); err != nil { + return fmt.Errorf("failed to delete email provider: %w", err) + } + + return nil + }, + } + + cmd.Flags().BoolVar(&cli.force, "force", false, "Skip confirmation.") + + return cmd +} diff --git a/internal/display/email_provider.go b/internal/display/email_provider.go new file mode 100644 index 000000000..ef7155280 --- /dev/null +++ b/internal/display/email_provider.go @@ -0,0 +1,133 @@ +package display + +import ( + "encoding/json" + + "github.com/auth0/go-auth0/management" + + "github.com/auth0/auth0-cli/internal/ansi" +) + +type emailProviderView struct { + Provider string + Enabled string + DefaultFromAddress string + Credentials string + Settings string + + raw interface{} +} + +func (v *emailProviderView) AsTableHeader() []string { + return []string{"Provider", "Enabled", "DefaultFromAddress", "Settings"} +} + +func (v *emailProviderView) AsTableRow() []string { + return []string{ + v.Provider, + v.Enabled, + v.DefaultFromAddress, + v.Settings, + } +} + +func (v *emailProviderView) KeyValues() [][]string { + return [][]string{ + {"PROVIDER", v.Provider}, + {"ENABLED", v.Enabled}, + {"DEFAULT FROM ADDRESS", v.DefaultFromAddress}, + {"SETTINGS", v.Settings}, + } +} + +func (v *emailProviderView) Object() interface{} { + return v.raw +} + +func (r *Renderer) EmailProviderShow(emailProvider *management.EmailProvider) error { + r.Heading("email provider") + view, err := makeEmailProviderView(emailProvider) + if err != nil { + return err + } + r.Result(view) + return nil +} + +func (r *Renderer) EmailProviderCreate(emailProvider *management.EmailProvider) error { + r.Heading("email provider created") + + view, err := makeEmailProviderView(emailProvider) + if err != nil { + return err + } + r.Result(view) + r.Newline() + + // TODO(cyx): possibly guard this with a --no-hint flag. + r.Infof("%s To edit the email provider, run `auth0 email provider update`", + ansi.Faint("Hint:"), + ) + + return nil +} + +func (r *Renderer) EmailProviderUpdate(emailProvider *management.EmailProvider) error { + r.Heading("email provider updated") + + view, err := makeEmailProviderView(emailProvider) + if err != nil { + return err + } + r.Result(view) + + return nil +} + +func makeEmailProviderView(emailProvider *management.EmailProvider) (*emailProviderView, error) { + credentials, err := formatProviderCredentials(emailProvider.Credentials) + if err != nil { + return nil, err + } + + settings, err := formatProviderSettings(emailProvider.Settings) + if err != nil { + return nil, err + } + + return &emailProviderView{ + Provider: emailProvider.GetName(), + Enabled: boolean(emailProvider.GetEnabled()), + DefaultFromAddress: emailProvider.GetDefaultFromAddress(), + Credentials: credentials, + Settings: settings, + + raw: emailProvider, + }, nil +} + +func formatProviderCredentials(credentials interface{}) (string, error) { + if credentials == nil { + return "", nil + } + + raw, err := json.Marshal(credentials) + if err != nil { + return "", err + } + + return string(raw), nil +} + +func formatProviderSettings(settings interface{}) (string, error) { + if settings == nil { + return "", nil + } + + raw, err := json.Marshal(settings) + if err != nil { + return "", err + } + + return string(raw), nil +} diff --git a/test/integration/email-test-cases.yaml b/test/integration/email-test-cases.yaml index 4ac89be82..1e268a908 100644 --- a/test/integration/email-test-cases.yaml +++ b/test/integration/email-test-cases.yaml @@ -3,15 +3,63 @@ config: retries: 1 tests: - 001 - configure and enable email provider: - command: sh ./test/integration/scripts/create-email-provider.sh + 001 - clean up email provider: + command: auth0 email provider delete --force exit-code: 0 - 002 - it successfully updates welcome email template: + 002 - create custom email provider action: + command: ./test/integration/scripts/create-custom-email-action.sh + exit-code: 0 + + 003 - create custom email provider: + command: auth0 email provider create --provider=custom --enabled=true --default-from-address='not-admin@auth0.invalid' + exit-code: 0 + + 004 - delete email provider: + command: auth0 email provider delete --force + exit-code: 0 + + 005 - it doesn't show the email provider: + command: auth0 email provider show + exit-code: 1 + + 006 - delete custom email provider action: + command: ./test/integration/scripts/delete-custom-email-action.sh + exit-code: 0 + + 007 - create email provider: + command: auth0 email provider create --provider=mandrill --enabled=false --default-from-address='not-admin@auth0.invalid' --credentials='{"api_key":"some-api-key"}' --settings='{}' + exit-code: 0 + + 008 - it successfully shows the email provider: + command: auth0 email provider show + exit-code: 0 + stdout: + contains: + - PROVIDER mandrill + - ENABLED ✗ + - DEFAULT FROM ADDRESS not-admin@auth0.invalid + - SETTINGS {} + + 009 - update and enable email provider: + command: auth0 email provider update --enabled --default-from-address='admin@auth0.invalid' + exit-code: 0 + + 010 - it successfully shows the email provider: + command: auth0 email provider show + exit-code: 0 + stdout: + contains: + - PROVIDER mandrill + - ENABLED ✓ + - DEFAULT FROM ADDRESS admin@auth0.invalid + - SETTINGS {} + + 100 - it successfully updates welcome email template: command: auth0 email templates update welcome --enabled --body "