Skip to content

Commit

Permalink
Support disabling custom smtp
Browse files Browse the repository at this point in the history
ref DEV-2348
  • Loading branch information
louischan-oursky committed Dec 9, 2024
2 parents 2bb7dcf + c5cf909 commit db537db
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 9 deletions.
58 changes: 56 additions & 2 deletions pkg/lib/config/configsource/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,10 @@ func (d AuthgearYAMLDescriptor) validateFeatureConfig(validationCtx *validation.
if incomingFCError == nil || !ok {
return incomingFCError
}
// https://github.com/authgear/authgear-server/commit/888e57b4b6fa9de7cd5786111cdc5cc244a85ac0
// If the original config has some feature config error, we allow the user
// to save the config without correcting them. This is for the case that
// the app is downgraded from a higher plan.
originalFCError := d.validateBasedOnFeatureConfig(original, fc)
originalAggregatedError, ok := originalFCError.(*validation.AggregatedError)
if originalFCError == nil || !ok {
Expand Down Expand Up @@ -519,6 +523,11 @@ func (d AuthgearSecretYAMLDescriptor) UpdateResource(ctx context.Context, _ []re
return nil, fmt.Errorf("cannot delete '%v'", AuthgearSecretYAML)
}

fc, ok := ctx.Value(ContextKeyFeatureConfig).(*config.FeatureConfig)
if !ok || fc == nil {
return nil, fmt.Errorf("missing feature config in context")
}

var original *config.SecretConfig
original, err := config.ParseSecret(resrc.Data)
if err != nil {
Expand Down Expand Up @@ -548,12 +557,17 @@ func (d AuthgearSecretYAMLDescriptor) UpdateResource(ctx context.Context, _ []re
return config.GenerateSAMLIdpSigningCertificate(commonName)
},
}
updatedConfig, err := updateInstruction.ApplyTo(updateInstructionContext, original)
incoming, err := updateInstruction.ApplyTo(updateInstructionContext, original)
if err != nil {
return nil, err
}

updatedYAML, err := yaml.Marshal(updatedConfig)
err = d.validate(ctx, original, incoming, fc)
if err != nil {
return nil, err
}

updatedYAML, err := yaml.Marshal(incoming)
if err != nil {
return nil, err
}
Expand All @@ -563,6 +577,46 @@ func (d AuthgearSecretYAMLDescriptor) UpdateResource(ctx context.Context, _ []re
return &newResrc, nil
}

func (d AuthgearSecretYAMLDescriptor) validate(ctx context.Context, original *config.SecretConfig, incoming *config.SecretConfig, fc *config.FeatureConfig) error {
validationCtx := &validation.Context{}

featureConfigErr := func() error {
incomingFCError := d.validateBasedOnFeatureConfig(incoming, fc)
incomingAggregatedError, ok := incomingFCError.(*validation.AggregatedError)
if incomingFCError == nil || !ok {
return incomingFCError
}
// https://github.com/authgear/authgear-server/commit/888e57b4b6fa9de7cd5786111cdc5cc244a85ac0
// If the original config has some feature config error, we allow the user
// to save the config without correcting them. This is for the case that
// the app is downgraded from a higher plan.
originalFCError := d.validateBasedOnFeatureConfig(original, fc)
originalAggregatedError, ok := originalFCError.(*validation.AggregatedError)
if originalFCError == nil || !ok {
return incomingFCError
}

aggregatedError := incomingAggregatedError.Subtract(originalAggregatedError)
return aggregatedError
}()

validationCtx.AddError(featureConfigErr)

return validationCtx.Error(fmt.Sprintf("invalid %v", AuthgearSecretYAML))
}

func (d AuthgearSecretYAMLDescriptor) validateBasedOnFeatureConfig(secretConfig *config.SecretConfig, fc *config.FeatureConfig) error {
validationCtx := &validation.Context{}

if fc.Messaging.CustomSMTPDisabled {
if _, _, ok := secretConfig.Lookup(config.SMTPServerCredentialsKey); ok {
validationCtx.EmitErrorMessage("custom smtp is not allowed")
}
}

return validationCtx.Error("features are limited by feature config")
}

var SecretConfig = resource.RegisterResource(AuthgearSecretYAMLDescriptor{})

type AuthgearFeatureYAMLDescriptor struct{}
Expand Down
5 changes: 4 additions & 1 deletion pkg/lib/config/feature_messaging.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ var _ = FeatureConfigSchema.Add("MessagingFeatureConfig", `
"whatsapp_usage": { "$ref": "#/$defs/UsageLimitConfig" },
"sms_usage_count_disabled": { "type": "boolean" },
"whatsapp_usage_count_disabled": { "type": "boolean" },
"template_customization_disabled": { "type": "boolean" }
"template_customization_disabled": { "type": "boolean" },
"custom_smtp_disabled": { "type": "boolean" }
}
}
`)
Expand All @@ -27,6 +28,8 @@ type MessagingFeatureConfig struct {
WhatsappUsageCountDisabled bool `json:"whatsapp_usage_count_disabled,omitempty"`

TemplateCustomizationDisabled bool `json:"template_customization_disabled,omitempty"`

CustomSMTPDisabled bool `json:"custom_smtp_disabled,omitempty"`
}

func (c *MessagingFeatureConfig) SetDefaults() {
Expand Down
2 changes: 1 addition & 1 deletion portal/src/BlueMessageBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export default function BlueMessageBar(
);

return (
<ThemeProvider theme={newTheme}>
<ThemeProvider as={React.Fragment} theme={newTheme}>
<MessageBar styles={styles} {...rest} />
</ThemeProvider>
);
Expand Down
33 changes: 28 additions & 5 deletions portal/src/graphql/portal/SMTPConfigurationScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ import DefaultButton from "../../DefaultButton";
import { AppSecretKey } from "./globalTypes.generated";
import { useLocationEffect } from "../../hook/useLocationEffect";
import { useAppSecretVisitToken } from "./mutations/generateAppSecretVisitTokenMutation";
import { useAppFeatureConfigQuery } from "./query/appFeatureConfigQuery";
import FeatureDisabledMessageBar from "./FeatureDisabledMessageBar";

interface LocationState {
isEdit: boolean;
Expand Down Expand Up @@ -245,13 +247,14 @@ function ProviderDescription(props: ProviderDescriptionProps) {
}

interface SMTPConfigurationScreenContentProps {
isCustomSMTPDisabled: boolean;
sendTestEmailHandle: UseSendTestEmailMutationReturnType;
form: AppSecretConfigFormModel<FormState>;
}

const SMTPConfigurationScreenContent: React.VFC<SMTPConfigurationScreenContentProps> =
function SMTPConfigurationScreenContent(props) {
const { form, sendTestEmailHandle } = props;
const { form, sendTestEmailHandle, isCustomSMTPDisabled } = props;
const { state, setState } = form;
const { sendTestEmail, loading } = sendTestEmailHandle;

Expand Down Expand Up @@ -503,6 +506,12 @@ const SMTPConfigurationScreenContent: React.VFC<SMTPConfigurationScreenContentPr
<ScreenDescription className={styles.widget}>
<FormattedMessage id="SMTPConfigurationScreen.description" />
</ScreenDescription>
{isCustomSMTPDisabled ? (
<FeatureDisabledMessageBar
className={styles.widget}
messageID="FeatureConfig.custom-smtp.disabled"
/>
) : null}

<Widget className={styles.widget} contentLayout="grid">
<Toggle
Expand All @@ -511,7 +520,7 @@ const SMTPConfigurationScreenContent: React.VFC<SMTPConfigurationScreenContentPr
onChange={onChangeEnabled}
label={renderToString("SMTPConfigurationScreen.enable.label")}
inlineLabel={true}
disabled={state.isPasswordMasked}
disabled={state.isPasswordMasked || isCustomSMTPDisabled}
/>
{state.enabled ? (
<>
Expand Down Expand Up @@ -622,6 +631,7 @@ const SMTPConfigurationScreenContent: React.VFC<SMTPConfigurationScreenContentPr
{state.isPasswordMasked ? (
<PrimaryButton
className={styles.columnSmall}
disabled={isCustomSMTPDisabled}
onClick={onClickEdit}
text={<FormattedMessage id="edit" />}
/>
Expand Down Expand Up @@ -678,20 +688,33 @@ const SMTPConfigurationScreen1: React.VFC<{
constructConfig,
constructSecretUpdateInstruction,
});
const featureConfig = useAppFeatureConfigQuery(appID);

const sendTestEmailHandle = useSendTestEmailMutation(appID);

if (form.isLoading) {
if (form.isLoading || featureConfig.loading) {
return <ShowLoading />;
}

if (form.loadError) {
return <ShowError error={form.loadError} onRetry={form.reload} />;
if (form.loadError ?? featureConfig.error) {
return (
<ShowError
error={form.loadError ?? featureConfig.error}
onRetry={() => {
form.reload();
featureConfig.refetch().finally(() => {});
}}
/>
);
}

return (
<FormContainer form={form} localError={sendTestEmailHandle.error}>
<SMTPConfigurationScreenContent
isCustomSMTPDisabled={
featureConfig.effectiveFeatureConfig?.messaging
?.custom_smtp_disabled ?? false
}
sendTestEmailHandle={sendTestEmailHandle}
form={form}
/>
Expand Down
1 change: 1 addition & 0 deletions portal/src/locale-data/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1552,6 +1552,7 @@
"FeatureConfig.collaborator.contact-us": "Your plan only include {maximum, plural, one{1 member seat} other{# member seats}}, {ExternalLink, react, href{{contactUsHref}} children{contact us to upgrade} target{_blank}}",
"FeatureConfig.web3-nft.maximum": "You can index {maximum, plural, one{1 collection} other{# collections}} max. {ReactRouterLink, react, to{{planPagePath}} children{Upgrade your project plan} target{_blank}} to add more",
"FeatureConfig.edit-template.disabled": "{ReactRouterLink, react, to{{planPagePath}} children{Upgrade your project plan} target{_blank}} to change the templates.",
"FeatureConfig.custom-smtp.disabled": "Custom email provider is not available for your project plan. {ReactRouterLink, react, to{{planPagePath}} children{Upgrade your project plan} target{_blank}}.",
"SubscriptionCurrentPlanSummary.title.known-plan": "Current Plan: {name} {expiredAt, select, false{(${amount}/mo)} other{(Expire at: {expiredAt})}}",
"SubscriptionCurrentPlanSummary.title.custom-plan": "Custom Plan: {name}",
"SubscriptionCurrentPlanSummary.label.free": "Free",
Expand Down

0 comments on commit db537db

Please sign in to comment.