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 "

Welcome!

" --from "welcome@travel0.com" --lifetime 6100 --subject "Welcome to Travel0" --url "travel0.com" --force exit-code: 0 - 003 - it successfully shows welcome email template: + 101 - it successfully shows welcome email template: command: auth0 email templates show welcome exit-code: 0 stdout: @@ -22,11 +70,12 @@ tests: - RESULT URL travel0.com - RESULT URL LIFETIME 6100 - ENABLED ✓ - 004 - it successfully updates verify-link email template: + + 102 - it successfully updates verify-link email template: command: auth0 email templates update verify-link --enabled --body "

Verify link

" --from "verify@travel0.com" --lifetime 6100 --subject "Verify link" --url "travel0.com" --force exit-code: 0 - 005 - it successfully shows verify-link email template: + 103 - it successfully shows verify-link email template: command: auth0 email templates show verify-link exit-code: 0 stdout: @@ -37,11 +86,11 @@ tests: - RESULT URL travel0.com - RESULT URL LIFETIME 6100 - ENABLED ✓ - 006 - it successfully updates verify-code email template: + 104 - it successfully updates verify-code email template: command: auth0 email templates update verify-code --enabled --body "

Verify code

" --from "verify@travel0.com" --lifetime 6100 --subject "Verify code" --url "travel0.com" --force exit-code: 0 - 007 - it successfully shows verify-code email template: + 105 - it successfully shows verify-code email template: command: auth0 email templates show verify-code exit-code: 0 stdout: @@ -52,11 +101,11 @@ tests: - RESULT URL travel0.com - RESULT URL LIFETIME 6100 - ENABLED ✓ - 008 - it successfully updates change-password email template: + 106 - it successfully updates change-password email template: command: auth0 email templates update change-password --enabled --body "

Change password

" --from "change-password@travel0.com" --lifetime 6100 --subject "Change password" --url "travel0.com" --force exit-code: 0 - 009 - it successfully shows change-password email template: + 107 - it successfully shows change-password email template: command: auth0 email templates show change-password exit-code: 0 stdout: @@ -67,11 +116,11 @@ tests: - RESULT URL travel0.com - RESULT URL LIFETIME 6100 - ENABLED ✓ - 010 - it successfully updates blocked-account email template: + 108 - it successfully updates blocked-account email template: command: auth0 email templates update blocked-account --enabled --body "

Change password

" --from "blocked@travel0.com" --lifetime 6100 --subject "Blocked!" --url "travel0.com" --force exit-code: 0 - 011 - it successfully shows blocked-account email template: + 109 - it successfully shows blocked-account email template: command: auth0 email templates show blocked-account exit-code: 0 stdout: @@ -82,11 +131,11 @@ tests: - RESULT URL travel0.com - RESULT URL LIFETIME 6100 - ENABLED ✓ - 012 - it successfully updates password-breach email template: + 110 - it successfully updates password-breach email template: command: auth0 email templates update password-breach --enabled --body "

Password breached

" --from "security@travel0.com" --lifetime 6100 --subject "Breached Password!" --url "travel0.com" --force exit-code: 0 - 013 - it successfully shows password-breach email template: + 111 - it successfully shows password-breach email template: command: auth0 email templates show password-breach exit-code: 0 stdout: @@ -97,11 +146,11 @@ tests: - RESULT URL travel0.com - RESULT URL LIFETIME 6100 - ENABLED ✓ - 014 - it successfully updates mfa-enrollment email template: + 112 - it successfully updates mfa-enrollment email template: command: auth0 email templates update mfa-enrollment --enabled --body "

Enroll in MFA

" --from "security@travel0.com" --lifetime 6100 --subject "Enroll in MFA!" --url "travel0.com" --force exit-code: 0 - 015 - it successfully shows mfa-enrollment email template: + 113 - it successfully shows mfa-enrollment email template: command: auth0 email templates show mfa-enrollment exit-code: 0 stdout: @@ -112,11 +161,11 @@ tests: - RESULT URL travel0.com - RESULT URL LIFETIME 6100 - ENABLED ✓ - 016 - it successfully updates mfa-code email template: + 114 - it successfully updates mfa-code email template: command: auth0 email templates update mfa-code --enabled --body "

MFA Enrollment code

" --from "security@travel0.com" --lifetime 6100 --subject "MFA Enrollment Code" --url "travel0.com" --force exit-code: 0 - 017 - it successfully shows mfa-code email template: + 115 - it successfully shows mfa-code email template: command: auth0 email templates show mfa-code exit-code: 0 stdout: @@ -127,11 +176,11 @@ tests: - RESULT URL travel0.com - RESULT URL LIFETIME 6100 - ENABLED ✓ - 018 - it successfully updates user-invitation email template: + 116 - it successfully updates user-invitation email template: command: auth0 email templates update user-invitation --enabled --body "

You are invited!

" --from "invited@travel0.com" --lifetime 6100 --subject "You are invited!" --url "travel0.com" --force exit-code: 0 - 019 - it successfully shows user-invitation email template: + 117 - it successfully shows user-invitation email template: command: auth0 email templates show user-invitation exit-code: 0 stdout: @@ -142,7 +191,7 @@ tests: - RESULT URL travel0.com - RESULT URL LIFETIME 6100 - ENABLED ✓ - 020 - it successfully shows user-invitation email template (json): + 118 - it successfully shows user-invitation email template (json): command: "auth0 email templates show user-invitation --json | jq ." exit-code: 0 stdout: diff --git a/test/integration/fixtures/update-rule.json b/test/integration/fixtures/update-rule.json index 347e717d7..8d0e2cc77 100644 --- a/test/integration/fixtures/update-rule.json +++ b/test/integration/fixtures/update-rule.json @@ -1,5 +1,5 @@ { - "id": "rul_xWavq6OFKbMma9DH", + "id": "rul_qqjKFCxGrZgYNZQL", "name": "integration-test-rule-betterName", "script": "function(user, context, cb) {\n cb(null, user, context);\n}\n", "order": 3, diff --git a/test/integration/scripts/create-custom-email-action.sh b/test/integration/scripts/create-custom-email-action.sh new file mode 100755 index 000000000..01397ecc7 --- /dev/null +++ b/test/integration/scripts/create-custom-email-action.sh @@ -0,0 +1,12 @@ +#! /bin/bash + +FILE=./test/integration/identifiers/custom-email-action-id + +# Create the action. +action_id=$( auth0 actions create -n "integration-test-custom-email-action" -t "custom-email-provider" -c "exports.onExecuteCustomEmailProvider = async (event, api) => { return; };" --json | jq -r '.["id"]' ) + +# Deploy the action. +auth0 actions deploy "$action_id" + +mkdir -p ./test/integration/identifiers +echo "$action_id" > $FILE diff --git a/test/integration/scripts/create-email-provider.sh b/test/integration/scripts/create-email-provider.sh deleted file mode 100644 index 09bcdd17f..000000000 --- a/test/integration/scripts/create-email-provider.sh +++ /dev/null @@ -1,4 +0,0 @@ -EXIT_STATUS=0 -auth0 api POST "emails/provider" --data '{"name":"mandrill","credentials":{"api_key":"some-api-key"}}' || EXIT_STATUS=$? -auth0 api PATCH "emails/provider" --data '{"enabled":true}' || EXIT_STATUS=$? -exit $EXIT_STATUS \ No newline at end of file diff --git a/test/integration/scripts/delete-custom-email-action.sh b/test/integration/scripts/delete-custom-email-action.sh new file mode 100755 index 000000000..531a0b70a --- /dev/null +++ b/test/integration/scripts/delete-custom-email-action.sh @@ -0,0 +1,13 @@ +#! /bin/bash + +FILE=./test/integration/identifiers/custom-email-action-id +if [ -e "$FILE" ] +then + action_id=$(cat $FILE) + + # Delete the action. + auth0 actions delete "$action_id" --force + + rm "$FILE" +fi + diff --git a/test/integration/scripts/test-cleanup.sh b/test/integration/scripts/test-cleanup.sh index 9b51d685b..ca0eed9a7 100755 --- a/test/integration/scripts/test-cleanup.sh +++ b/test/integration/scripts/test-cleanup.sh @@ -33,6 +33,9 @@ delete_resources "logs streams" "integration-test-" "id" auth0 domains delete $(./test/integration/scripts/get-custom-domain-id.sh) --no-input +# clean up the email provider +auth0 email provider delete --force + # Reset universal login branding auth0 ul update --accent "#2A2E35" --background "#FF4F40" --logo "https://example.com/logo.png" --favicon "https://example.com/favicon.png" --font https://example.com/font.woff --no-input diff --git a/test/integration/terraform-test-cases.yaml b/test/integration/terraform-test-cases.yaml index 5b1490fcc..112a06217 100644 --- a/test/integration/terraform-test-cases.yaml +++ b/test/integration/terraform-test-cases.yaml @@ -40,7 +40,7 @@ tests: command: ./test/integration/scripts/assert-tf-generate-files-exist.sh exit-code: 0 002.3 - cleanup: - command: rm -rdf tmp-tf-gen + command: mv tmp-tf-gen /tmp/tmp-tf-gen 003.1 - it partially succeeds if Terraform credentials not provided: command: unset AUTH0_DOMAIN && auth0 tf generate --output-dir tmp-tf-gen