From 8cbbf5c53739d6052e23d67fce252c4cb8f6f4a6 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Mon, 23 Aug 2021 14:36:03 -0500 Subject: [PATCH 01/32] add messaging/sid support to mocktwilio --- devtools/mocktwilio/server.go | 42 ++++++++++++++++++++++++++++++++ devtools/mocktwilio/sms.go | 7 +++--- devtools/mocktwilio/voicecall.go | 2 +- smoketest/harness/harness.go | 21 ++++++++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/devtools/mocktwilio/server.go b/devtools/mocktwilio/server.go index 9e6f628cc2..31b56a5915 100644 --- a/devtools/mocktwilio/server.go +++ b/devtools/mocktwilio/server.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "math/rand" "net/http" "net/url" "strings" @@ -47,6 +48,7 @@ type Server struct { messages map[string]*SMS calls map[string]*VoiceCall + msgSvc map[string][]string mux *http.ServeMux @@ -71,6 +73,7 @@ func NewServer(cfg Config) *Server { mux: http.NewServeMux(), messages: make(map[string]*SMS), calls: make(map[string]*VoiceCall), + msgSvc: make(map[string][]string), smsCh: make(chan *SMS), smsInCh: make(chan *SMS), callCh: make(chan *VoiceCall), @@ -196,6 +199,45 @@ func (s *Server) SetCarrierInfo(number string, info twilio.CarrierInfo) { s.carrierInfo[number] = info } +// getFromNumber will return a random number from the messaging service if ID is a +// messaging SID, or the value itself otherwise. +func (s *Server) getFromNumber(id string) string { + if !strings.HasPrefix(id, "MG") { + return id + } + + s.mx.Lock() + defer s.mx.Unlock() + + // select a random number from the message service + if len(s.msgSvc[id]) == 0 { + return "" + } + + return s.msgSvc[id][rand.Intn(len(s.msgSvc[id]))] +} + +// NewMessagingService registers a new Messaging SID for the given numbers. +func (s *Server) NewMessagingService(smsURL, voiceURL string, numbers ...string) (string, error) { + err := validate.Many( + validate.URL("SMS URL", smsURL), + validate.URL("Voice URL", voiceURL), + ) + if err != nil { + return "", err + } + svcID := s.id("MG") + + s.mx.Lock() + defer s.mx.Unlock() + for _, num := range numbers { + s.callbacks["SMS:"+num] = smsURL + s.callbacks["VOICE:"+num] = voiceURL + } + + return svcID, nil +} + // RegisterSMSCallback will set/update a callback URL for SMS calls made to the given number. func (s *Server) RegisterSMSCallback(number, url string) error { err := validate.URL("URL", url) diff --git a/devtools/mocktwilio/sms.go b/devtools/mocktwilio/sms.go index 786e565a19..7f67e2a716 100644 --- a/devtools/mocktwilio/sms.go +++ b/devtools/mocktwilio/sms.go @@ -29,7 +29,8 @@ type SMS struct { doneCh chan struct{} } -func (s *Server) sendSMS(from, to, body, statusURL, destURL string) (*SMS, error) { +func (s *Server) sendSMS(fromValue, to, body, statusURL, destURL string) (*SMS, error) { + fromNumber := s.getFromNumber(fromValue) if statusURL != "" { err := validate.URL("StatusCallback", statusURL) if err != nil { @@ -39,7 +40,7 @@ func (s *Server) sendSMS(from, to, body, statusURL, destURL string) (*SMS, error } } s.mx.RLock() - _, hasCallback := s.callbacks["SMS:"+from] + _, hasCallback := s.callbacks["SMS:"+fromNumber] s.mx.RUnlock() if !hasCallback { @@ -63,7 +64,7 @@ func (s *Server) sendSMS(from, to, body, statusURL, destURL string) (*SMS, error s: s, msg: twilio.Message{ To: to, - From: from, + From: fromNumber, Status: twilio.MessageStatusAccepted, SID: s.id("SM"), }, diff --git a/devtools/mocktwilio/voicecall.go b/devtools/mocktwilio/voicecall.go index 361c5e7c8b..15b7ee446d 100644 --- a/devtools/mocktwilio/voicecall.go +++ b/devtools/mocktwilio/voicecall.go @@ -150,7 +150,7 @@ func (s *Server) serveNewCall(w http.ResponseWriter, req *http.Request) { hangupCh: make(chan struct{}), } - vc.call.From = req.FormValue("From") + vc.call.From = s.getFromNumber(req.FormValue("From")) s.mx.RLock() _, hasCallback := s.callbacks["VOICE:"+vc.call.From] s.mx.RUnlock() diff --git a/smoketest/harness/harness.go b/smoketest/harness/harness.go index e164b2858b..2d15bed13f 100644 --- a/smoketest/harness/harness.go +++ b/smoketest/harness/harness.go @@ -73,6 +73,8 @@ type Harness struct { t *testing.T closing bool + msgSvcID string + tw *twilioAssertionAPI twS *httptest.Server @@ -613,6 +615,25 @@ func (h *Harness) TwilioNumber(id string) string { return num } +// TwilioMessagingService will return the id and phone numbers for the mock messaging service. +func (h *Harness) TwilioMessagingService() string { + h.mx.Lock() + if h.msgSvcID != "" { + h.mx.Unlock() + return h.msgSvcID + } + defer h.mx.Unlock() + + nums := []string{h.phoneCCG.Get(""), h.phoneCCG.Get(""), h.phoneCCG.Get("")} + newID, err := h.tw.NewMessagingService(h.URL()+"/v1/twilio/sms/messages", h.URL()+"/v1/twilio/voice/call", nums...) + if err != nil { + panic(err) + } + + h.msgSvcID = newID + return newID +} + // CreateUser generates a random user. func (h *Harness) CreateUser() (u *user.User) { h.t.Helper() From 44d930c5c17e413df62dadf673343c4543b37794 Mon Sep 17 00:00:00 2001 From: dctalbot Date: Mon, 13 Sep 2021 15:49:51 -0500 Subject: [PATCH 02/32] inputTypes and helperText --- web/src/app/admin/AdminNumberLookup.tsx | 2 - web/src/app/admin/AdminSMSSend.tsx | 10 +---- web/src/app/users/UserContactMethodForm.tsx | 6 --- .../users/UserPhoneNumberFilterContainer.tsx | 2 - web/src/app/util/TelTextField.tsx | 43 +++++++++++-------- 5 files changed, 27 insertions(+), 36 deletions(-) diff --git a/web/src/app/admin/AdminNumberLookup.tsx b/web/src/app/admin/AdminNumberLookup.tsx index 6a5ca095c1..e305ce7e89 100644 --- a/web/src/app/admin/AdminNumberLookup.tsx +++ b/web/src/app/admin/AdminNumberLookup.tsx @@ -96,8 +96,6 @@ export default function AdminNumberLookup(): JSX.Element { }} value={number} label='Phone Number' - helperText='Please provide your country code e.g. +1 (USA)' - type='tel' /> diff --git a/web/src/app/admin/AdminSMSSend.tsx b/web/src/app/admin/AdminSMSSend.tsx index 34befd45a5..6cd1946367 100644 --- a/web/src/app/admin/AdminSMSSend.tsx +++ b/web/src/app/admin/AdminSMSSend.tsx @@ -69,9 +69,8 @@ export default function AdminSMSSend(): JSX.Element { onChange={(e) => setFromNumber(e.target.value)} value={fromNumber} fullWidth - label='From Number' - helperText='Please provide your country code e.g. +1 (USA)' - type='tel' + label='From Number or SID' + inputTypes={['tel', 'sid']} /> @@ -80,8 +79,6 @@ export default function AdminSMSSend(): JSX.Element { value={toNumber} fullWidth label='To Number' - helperText='Please provide your country code e.g. +1 (USA)' - type='tel' /> @@ -90,9 +87,6 @@ export default function AdminSMSSend(): JSX.Element { value={body} fullWidth label='Body' - InputLabelProps={{ - shrink: true, - }} multiline /> diff --git a/web/src/app/users/UserContactMethodForm.tsx b/web/src/app/users/UserContactMethodForm.tsx index c725c9f1e4..2827c030d7 100644 --- a/web/src/app/users/UserContactMethodForm.tsx +++ b/web/src/app/users/UserContactMethodForm.tsx @@ -48,15 +48,9 @@ function renderPhoneField(edit: boolean): JSX.Element { name='value' required label='Phone Number' - type='tel' component={TelTextField} disabled={edit} /> - {!edit && ( - - Please provide your country code e.g. +1 (USA), +91 (India), +44 (UK) - - )} ) } diff --git a/web/src/app/users/UserPhoneNumberFilterContainer.tsx b/web/src/app/users/UserPhoneNumberFilterContainer.tsx index 680a425d6d..8421a4931a 100644 --- a/web/src/app/users/UserPhoneNumberFilterContainer.tsx +++ b/web/src/app/users/UserPhoneNumberFilterContainer.tsx @@ -52,8 +52,6 @@ export default function UserPhoneNumberFilterContainer( fullWidth name='user-phone-search' label='Search by Phone Number' - helperText='Please provide your country code e.g. +1 (USA)' - type='tel' /> diff --git a/web/src/app/util/TelTextField.tsx b/web/src/app/util/TelTextField.tsx index bbfe813f40..69b0e8360c 100644 --- a/web/src/app/util/TelTextField.tsx +++ b/web/src/app/util/TelTextField.tsx @@ -3,7 +3,6 @@ import { useQuery, gql } from '@apollo/client' import TextField, { TextFieldProps } from '@material-ui/core/TextField' import { InputProps } from '@material-ui/core/Input' import { Check, Close } from '@material-ui/icons' -import _ from 'lodash' import InputAdornment from '@material-ui/core/InputAdornment' import { makeStyles } from '@material-ui/core' import { DEBOUNCE_DELAY } from '../config' @@ -26,19 +25,24 @@ const useStyles = makeStyles({ }, }) -export default function TelTextField( - props: TextFieldProps & { value: string }, -): JSX.Element { +type InputType = 'tel' | 'sid' +type TelTextFieldProps = TextFieldProps & { + value: string + inputTypes?: InputType[] +} + +export default function TelTextField(_props: TelTextFieldProps): JSX.Element { + const { inputTypes = ['tel'], value = '', helperText = '', ...props } = _props const classes = useStyles() const [phoneNumber, setPhoneNumber] = useState('') // debounce to set the phone number useEffect(() => { const t = setTimeout(() => { - setPhoneNumber(props.value) + setPhoneNumber(value) }, DEBOUNCE_DELAY) return () => clearTimeout(t) - }, [props.value]) + }, [value]) // check validation of the input phoneNumber through graphql const { data } = useQuery(isValidNumber, { @@ -49,10 +53,10 @@ export default function TelTextField( }) // fetch validation - const valid = _.get(data, 'phoneNumberInfo.valid', null) + const valid = Boolean(data?.phoneNumberInfo?.valid) let adorn - if (!props.value) { + if (value === '') { // no adornment if empty } else if (valid) { adorn = @@ -61,13 +65,7 @@ export default function TelTextField( } let iprops: Partial - iprops = { - startAdornment: ( - - + - - ), - } + iprops = {} // if has inputProps from parent commponent, spread it in the iprops if (props.InputProps !== undefined) { @@ -89,18 +87,27 @@ export default function TelTextField( function handleChange(e: React.ChangeEvent): void { if (!props.onChange) return if (!e.target.value) return props.onChange(e) - e.target.value = '+' + e.target.value.replace(/[^0-9]/g, '') + // e.target.value = e.target.value return props.onChange(e) } + const getHelperText = (): TextFieldProps['helperText'] => { + if (helperText) { + return helperText + } + if (inputTypes.includes('tel') && inputTypes.length === 1) { + return 'Please include a country code e.g. +1 (USA), +91 (India), +44 (UK)' + } + return 'For phone numbers, please include a country code e.g. +1 (USA), +91 (India), +44 (UK)' + } + return ( ) } From e9257e8b40cc9c23d232a8c154fec838f44b41af Mon Sep 17 00:00:00 2001 From: KatieMSB Date: Mon, 13 Sep 2021 16:25:36 -0500 Subject: [PATCH 03/32] initial validation --- web/src/app/util/TelTextField.tsx | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/web/src/app/util/TelTextField.tsx b/web/src/app/util/TelTextField.tsx index 69b0e8360c..616ca0ed32 100644 --- a/web/src/app/util/TelTextField.tsx +++ b/web/src/app/util/TelTextField.tsx @@ -36,6 +36,10 @@ export default function TelTextField(_props: TelTextFieldProps): JSX.Element { const classes = useStyles() const [phoneNumber, setPhoneNumber] = useState('') + const onlyTel = inputTypes.includes('tel') && inputTypes.length === 1 + const onlySID = inputTypes.includes('sid') && inputTypes.length === 1 + const hasSID = inputTypes.includes('sid') && value.startsWith('MG') + // debounce to set the phone number useEffect(() => { const t = setTimeout(() => { @@ -44,12 +48,28 @@ export default function TelTextField(_props: TelTextFieldProps): JSX.Element { return () => clearTimeout(t) }, [value]) + const skipValidatePhoneNumber = (): boolean => { + if (!phoneNumber || props.disabled) { + return true + } + if (onlyTel && !value.match('/(^[0-9])|(^\\+)/')) { + return true + } + if (onlySID) { + return true + } + if (hasSID) { + return true + } + return false + } + // check validation of the input phoneNumber through graphql const { data } = useQuery(isValidNumber, { pollInterval: 0, - variables: { number: '+' + phoneNumber }, + variables: { number: phoneNumber }, fetchPolicy: 'cache-first', - skip: !phoneNumber || props.disabled, + skip: skipValidatePhoneNumber(), }) // fetch validation @@ -76,7 +96,7 @@ export default function TelTextField(_props: TelTextFieldProps): JSX.Element { } // add live validation icon to the right of the textfield as an endAdornment - if (adorn) { + if (adorn && !props.disabled) { iprops = { endAdornment: {adorn}, ...iprops, From 03918afd41850f0fde4abff8b69404ca794188c1 Mon Sep 17 00:00:00 2001 From: KatieMSB Date: Tue, 14 Sep 2021 11:08:37 -0500 Subject: [PATCH 04/32] refine validation --- web/src/app/util/TelTextField.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/web/src/app/util/TelTextField.tsx b/web/src/app/util/TelTextField.tsx index 616ca0ed32..6101f590e9 100644 --- a/web/src/app/util/TelTextField.tsx +++ b/web/src/app/util/TelTextField.tsx @@ -38,7 +38,7 @@ export default function TelTextField(_props: TelTextFieldProps): JSX.Element { const onlyTel = inputTypes.includes('tel') && inputTypes.length === 1 const onlySID = inputTypes.includes('sid') && inputTypes.length === 1 - const hasSID = inputTypes.includes('sid') && value.startsWith('MG') + const isSID = inputTypes.includes('sid') && value.match(/^MG[a-zA-Z0-9]+$/) // debounce to set the phone number useEffect(() => { @@ -52,13 +52,13 @@ export default function TelTextField(_props: TelTextFieldProps): JSX.Element { if (!phoneNumber || props.disabled) { return true } - if (onlyTel && !value.match('/(^[0-9])|(^\\+)/')) { + if (onlyTel && !value.match(/(^\+)[0-9]+$/)) { return true } if (onlySID) { return true } - if (hasSID) { + if (isSID) { return true } return false @@ -76,7 +76,7 @@ export default function TelTextField(_props: TelTextFieldProps): JSX.Element { const valid = Boolean(data?.phoneNumberInfo?.valid) let adorn - if (value === '') { + if (value === '' || isSID || props.disabled) { // no adornment if empty } else if (valid) { adorn = @@ -96,7 +96,7 @@ export default function TelTextField(_props: TelTextFieldProps): JSX.Element { } // add live validation icon to the right of the textfield as an endAdornment - if (adorn && !props.disabled) { + if (adorn) { iprops = { endAdornment: {adorn}, ...iprops, @@ -107,7 +107,6 @@ export default function TelTextField(_props: TelTextFieldProps): JSX.Element { function handleChange(e: React.ChangeEvent): void { if (!props.onChange) return if (!e.target.value) return props.onChange(e) - // e.target.value = e.target.value return props.onChange(e) } From 0455aad6039f4d30319dd42e5c78b190e75eae02 Mon Sep 17 00:00:00 2001 From: KatieMSB Date: Tue, 14 Sep 2021 12:09:22 -0500 Subject: [PATCH 05/32] update fields for sid support where needed --- config/config.go | 2 +- devtools/configparams/main.go | 20 ++- graphql2/generated.go | 49 ++++++ graphql2/mapconfig.go | 156 ++++++++++---------- graphql2/models_gen.go | 1 + graphql2/schema.graphql | 1 + web/src/app/admin/AdminConfig.tsx | 4 +- web/src/app/admin/AdminFieldComponents.tsx | 10 +- web/src/app/admin/AdminSection.tsx | 2 +- web/src/app/users/UserContactMethodForm.tsx | 2 +- web/src/app/util/TelTextField.tsx | 1 + web/src/schema.d.ts | 1 + 12 files changed, 157 insertions(+), 92 deletions(-) diff --git a/config/config.go b/config/config.go index 024721b5ff..58a428fba2 100644 --- a/config/config.go +++ b/config/config.go @@ -93,7 +93,7 @@ type Config struct { AccountSID string AuthToken string `password:"true" info:"The primary Auth Token for Twilio. Must be primary (not secondary) for request valiation."` - FromNumber string `public:"true" info:"The Twilio number to use for outgoing notifications."` + FromNumber string `displayName:"From Number or Messenger SID" public:"true" info:"The Twilio number or Messenger SID to use for outgoing notifications."` DisableTwoWaySMS bool `info:"Disables SMS reply codes for alert messages."` SMSCarrierLookup bool `info:"Perform carrier lookup of SMS contact methods (required for SMSFromNumberOverride). Extra charges may apply."` diff --git a/devtools/configparams/main.go b/devtools/configparams/main.go index 1f5ba66bd2..12b02afcd7 100644 --- a/devtools/configparams/main.go +++ b/devtools/configparams/main.go @@ -48,7 +48,7 @@ func MapConfigHints(cfg config.Hints) []ConfigHint { func MapConfigValues(cfg config.Config) []ConfigValue { return []ConfigValue{ {{- range .ConfigFields }} - {ID: {{quote .ID}}, Type: {{.Type}}, Description: {{quote .Desc}}, Value: {{.Value}}{{if .Password}}, Password: true{{end}}}, + {ID: {{quote .ID}},Type: {{.Type}}, DisplayName: {{quote .DisplayName}}, Description: {{quote .Desc}}, Value: {{.Value}}{{if .Password}}, Password: true{{end}}}, {{- end}} } } @@ -58,7 +58,7 @@ func MapPublicConfigValues(cfg config.Config) []ConfigValue { return []ConfigValue{ {{- range .ConfigFields }} {{- if .Public}} - {ID: {{quote .ID}}, Type: {{.Type}}, Description: {{quote .Desc}}, Value: {{.Value}}{{if .Password}}, Password: true{{end}}}, + {ID: {{quote .ID}}, Type: {{.Type}}, DisplayName: {{quote .DisplayName}}, Description: {{quote .Desc}}, Value: {{.Value}}{{if .Password}}, Password: true{{end}}}, {{- end}} {{- end}} } @@ -129,7 +129,7 @@ func ApplyConfigValues(cfg config.Config, vals []ConfigValueInput) (config.Confi `)) type field struct { - ID, Type, Desc, Value string + ID, DisplayName, Type, Desc, Value string Public, Password bool } @@ -155,8 +155,8 @@ package graphql2`) ConfigFields []field HintFields []field } - input.ConfigFields = printType("", reflect.TypeOf(config.Config{}), "", false, false) - input.HintFields = printType("", reflect.TypeOf(config.Hints{}), "", false, false) + input.ConfigFields = printType("", reflect.TypeOf(config.Config{}), "", "", false, false) + input.HintFields = printType("", reflect.TypeOf(config.Hints{}), "", "", false, false) err := tmpl.Execute(w, input) if err != nil { @@ -169,9 +169,9 @@ func printField(prefix string, f reflect.StructField) []field { if f.Type.Kind() == reflect.Slice && f.Type.Elem().Kind() == reflect.Struct { fPrefix = prefix + f.Name + "[]." } - return printType(fPrefix, f.Type, f.Tag.Get("info"), f.Tag.Get("public") == "true", f.Tag.Get("password") == "true") + return printType(fPrefix, f.Type, f.Tag.Get("displayName"), f.Tag.Get("info"), f.Tag.Get("public") == "true", f.Tag.Get("password") == "true") } -func printType(prefix string, v reflect.Type, details string, public, pass bool) []field { +func printType(prefix string, v reflect.Type, displayName string, info string, public, pass bool) []field { var f []field key := strings.TrimSuffix(prefix, ".") @@ -207,6 +207,10 @@ func printType(prefix string, v reflect.Type, details string, public, pass bool) panic(fmt.Sprintf("not implemented for type %T", v.Kind())) } - f = append(f, field{ID: key, Type: typ, Desc: details, Value: value, Public: public, Password: pass}) + if displayName == "" { + displayName = key + } + + f = append(f, field{ID: key, Type: typ, DisplayName: displayName, Desc: info, Value: value, Public: public, Password: pass}) return f } diff --git a/graphql2/generated.go b/graphql2/generated.go index dd4c6dc8e9..69800ecfb6 100644 --- a/graphql2/generated.go +++ b/graphql2/generated.go @@ -140,6 +140,7 @@ type ComplexityRoot struct { ConfigValue struct { Description func(childComplexity int) int + DisplayName func(childComplexity int) int ID func(childComplexity int) int Password func(childComplexity int) int Type func(childComplexity int) int @@ -941,6 +942,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ConfigValue.Description(childComplexity), true + case "ConfigValue.displayName": + if e.complexity.ConfigValue.DisplayName == nil { + break + } + + return e.complexity.ConfigValue.DisplayName(childComplexity), true + case "ConfigValue.id": if e.complexity.ConfigValue.ID == nil { break @@ -3314,6 +3322,7 @@ input SystemLimitInput { type ConfigValue { id: String! + displayName: String! description: String! value: String! type: ConfigType! @@ -6571,6 +6580,41 @@ func (ec *executionContext) _ConfigValue_id(ctx context.Context, field graphql.C return ec.marshalNString2string(ctx, field.Selections, res) } +func (ec *executionContext) _ConfigValue_displayName(ctx context.Context, field graphql.CollectedField, obj *ConfigValue) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ConfigValue", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DisplayName, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + func (ec *executionContext) _ConfigValue_description(ctx context.Context, field graphql.CollectedField, obj *ConfigValue) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -20159,6 +20203,11 @@ func (ec *executionContext) _ConfigValue(ctx context.Context, sel ast.SelectionS if out.Values[i] == graphql.Null { invalids++ } + case "displayName": + out.Values[i] = ec._ConfigValue_displayName(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } case "description": out.Values[i] = ec._ConfigValue_description(ctx, field, obj) if out.Values[i] == graphql.Null { diff --git a/graphql2/mapconfig.go b/graphql2/mapconfig.go index 0450156949..f043a90e86 100644 --- a/graphql2/mapconfig.go +++ b/graphql2/mapconfig.go @@ -24,90 +24,90 @@ func MapConfigHints(cfg config.Hints) []ConfigHint { // MapConfigValues will map a Config struct into a flat list of ConfigValue structs. func MapConfigValues(cfg config.Config) []ConfigValue { return []ConfigValue{ - {ID: "General.PublicURL", Type: ConfigTypeString, Description: "Publicly routable URL for UI links and API calls.", Value: cfg.General.PublicURL}, - {ID: "General.GoogleAnalyticsID", Type: ConfigTypeString, Description: "", Value: cfg.General.GoogleAnalyticsID}, - {ID: "General.NotificationDisclaimer", Type: ConfigTypeString, Description: "Disclaimer text for receiving pre-recorded notifications (appears on profile page).", Value: cfg.General.NotificationDisclaimer}, - {ID: "General.MessageBundles", Type: ConfigTypeBoolean, Description: "Enables bundling status updates and alert notifications. Also allows 'ack/close all' responses to bundled alerts.", Value: fmt.Sprintf("%t", cfg.General.MessageBundles)}, - {ID: "General.ShortURL", Type: ConfigTypeString, Description: "If set, messages will contain a shorter URL using this as a prefix (e.g. http://example.com). It should point to GoAlert and can be the same as the PublicURL.", Value: cfg.General.ShortURL}, - {ID: "General.DisableSMSLinks", Type: ConfigTypeBoolean, Description: "If set, SMS messages will not contain a URL pointing to GoAlert.", Value: fmt.Sprintf("%t", cfg.General.DisableSMSLinks)}, - {ID: "General.DisableLabelCreation", Type: ConfigTypeBoolean, Description: "Disables the ability to create new labels for services.", Value: fmt.Sprintf("%t", cfg.General.DisableLabelCreation)}, - {ID: "General.DisableCalendarSubscriptions", Type: ConfigTypeBoolean, Description: "If set, disables all active calendar subscriptions as well as the ability to create new calendar subscriptions.", Value: fmt.Sprintf("%t", cfg.General.DisableCalendarSubscriptions)}, - {ID: "General.EnableV1GraphQL", Type: ConfigTypeBoolean, Description: "Enables the deprecated /v1/graphql endpoint (replaced by /api/graphql).", Value: fmt.Sprintf("%t", cfg.General.EnableV1GraphQL)}, - {ID: "Maintenance.AlertCleanupDays", Type: ConfigTypeInteger, Description: "Closed alerts will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertCleanupDays)}, - {ID: "Maintenance.APIKeyExpireDays", Type: ConfigTypeInteger, Description: "Unused calendar API keys will be disabled after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.APIKeyExpireDays)}, - {ID: "Auth.RefererURLs", Type: ConfigTypeStringList, Description: "Allowed referer URLs for auth and redirects.", Value: strings.Join(cfg.Auth.RefererURLs, "\n")}, - {ID: "Auth.DisableBasic", Type: ConfigTypeBoolean, Description: "Disallow username/password login.", Value: fmt.Sprintf("%t", cfg.Auth.DisableBasic)}, - {ID: "GitHub.Enable", Type: ConfigTypeBoolean, Description: "Enable GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.Enable)}, - {ID: "GitHub.NewUsers", Type: ConfigTypeBoolean, Description: "Allow new user creation via GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.NewUsers)}, - {ID: "GitHub.ClientID", Type: ConfigTypeString, Description: "", Value: cfg.GitHub.ClientID}, - {ID: "GitHub.ClientSecret", Type: ConfigTypeString, Description: "", Value: cfg.GitHub.ClientSecret, Password: true}, - {ID: "GitHub.AllowedUsers", Type: ConfigTypeStringList, Description: "Allow any of the listed GitHub usernames to authenticate. Use '*' to allow any user.", Value: strings.Join(cfg.GitHub.AllowedUsers, "\n")}, - {ID: "GitHub.AllowedOrgs", Type: ConfigTypeStringList, Description: "Allow any member of any listed GitHub org (or team, using the format 'org/team') to authenticate.", Value: strings.Join(cfg.GitHub.AllowedOrgs, "\n")}, - {ID: "GitHub.EnterpriseURL", Type: ConfigTypeString, Description: "GitHub URL (without /api) when used with GitHub Enterprise.", Value: cfg.GitHub.EnterpriseURL}, - {ID: "OIDC.Enable", Type: ConfigTypeBoolean, Description: "Enable OpenID Connect authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.Enable)}, - {ID: "OIDC.NewUsers", Type: ConfigTypeBoolean, Description: "Allow new user creation via OIDC authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.NewUsers)}, - {ID: "OIDC.OverrideName", Type: ConfigTypeString, Description: "Set the name/label on the login page to something other than OIDC.", Value: cfg.OIDC.OverrideName}, - {ID: "OIDC.IssuerURL", Type: ConfigTypeString, Description: "", Value: cfg.OIDC.IssuerURL}, - {ID: "OIDC.ClientID", Type: ConfigTypeString, Description: "", Value: cfg.OIDC.ClientID}, - {ID: "OIDC.ClientSecret", Type: ConfigTypeString, Description: "", Value: cfg.OIDC.ClientSecret, Password: true}, - {ID: "OIDC.Scopes", Type: ConfigTypeString, Description: "Requested scopes for authentication. If left blank, openid, profile, and email will be used.", Value: cfg.OIDC.Scopes}, - {ID: "OIDC.UserInfoEmailPath", Type: ConfigTypeString, Description: "JMESPath expression to find email address in UserInfo. If set, the email claim will be ignored in favor of this. (suggestion: email).", Value: cfg.OIDC.UserInfoEmailPath}, - {ID: "OIDC.UserInfoEmailVerifiedPath", Type: ConfigTypeString, Description: "JMESPath expression to find email verification state in UserInfo. If set, the email_verified claim will be ignored in favor of this. (suggestion: email_verified).", Value: cfg.OIDC.UserInfoEmailVerifiedPath}, - {ID: "OIDC.UserInfoNamePath", Type: ConfigTypeString, Description: "JMESPath expression to find full name in UserInfo. If set, the name claim will be ignored in favor of this. (suggestion: name || cn || join(' ', [firstname, lastname]))", Value: cfg.OIDC.UserInfoNamePath}, - {ID: "Mailgun.Enable", Type: ConfigTypeBoolean, Description: "", Value: fmt.Sprintf("%t", cfg.Mailgun.Enable)}, - {ID: "Mailgun.APIKey", Type: ConfigTypeString, Description: "", Value: cfg.Mailgun.APIKey, Password: true}, - {ID: "Mailgun.EmailDomain", Type: ConfigTypeString, Description: "The TO address for all incoming alerts.", Value: cfg.Mailgun.EmailDomain}, - {ID: "Slack.Enable", Type: ConfigTypeBoolean, Description: "", Value: fmt.Sprintf("%t", cfg.Slack.Enable)}, - {ID: "Slack.ClientID", Type: ConfigTypeString, Description: "", Value: cfg.Slack.ClientID}, - {ID: "Slack.ClientSecret", Type: ConfigTypeString, Description: "", Value: cfg.Slack.ClientSecret, Password: true}, - {ID: "Slack.AccessToken", Type: ConfigTypeString, Description: "Slack app bot user OAuth access token (should start with xoxb-).", Value: cfg.Slack.AccessToken, Password: true}, - {ID: "Twilio.Enable", Type: ConfigTypeBoolean, Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, - {ID: "Twilio.AccountSID", Type: ConfigTypeString, Description: "", Value: cfg.Twilio.AccountSID}, - {ID: "Twilio.AuthToken", Type: ConfigTypeString, Description: "The primary Auth Token for Twilio. Must be primary (not secondary) for request valiation.", Value: cfg.Twilio.AuthToken, Password: true}, - {ID: "Twilio.FromNumber", Type: ConfigTypeString, Description: "The Twilio number to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, - {ID: "Twilio.DisableTwoWaySMS", Type: ConfigTypeBoolean, Description: "Disables SMS reply codes for alert messages.", Value: fmt.Sprintf("%t", cfg.Twilio.DisableTwoWaySMS)}, - {ID: "Twilio.SMSCarrierLookup", Type: ConfigTypeBoolean, Description: "Perform carrier lookup of SMS contact methods (required for SMSFromNumberOverride). Extra charges may apply.", Value: fmt.Sprintf("%t", cfg.Twilio.SMSCarrierLookup)}, - {ID: "Twilio.SMSFromNumberOverride", Type: ConfigTypeStringList, Description: "List of 'carrier=number' pairs, SMS messages to numbers of the provided carrier string (exact match) will use the alternate From Number.", Value: strings.Join(cfg.Twilio.SMSFromNumberOverride, "\n")}, - {ID: "SMTP.Enable", Type: ConfigTypeBoolean, Description: "Enables email as a contact method.", Value: fmt.Sprintf("%t", cfg.SMTP.Enable)}, - {ID: "SMTP.From", Type: ConfigTypeString, Description: "The email address messages should be sent from.", Value: cfg.SMTP.From}, - {ID: "SMTP.Address", Type: ConfigTypeString, Description: "The server address to use for sending email. Port is optional.", Value: cfg.SMTP.Address}, - {ID: "SMTP.DisableTLS", Type: ConfigTypeBoolean, Description: "Disables TLS on the connection (STARTTLS will still be used if supported).", Value: fmt.Sprintf("%t", cfg.SMTP.DisableTLS)}, - {ID: "SMTP.SkipVerify", Type: ConfigTypeBoolean, Description: "Disables certificate validation for TLS/STARTTLS (insecure).", Value: fmt.Sprintf("%t", cfg.SMTP.SkipVerify)}, - {ID: "SMTP.Username", Type: ConfigTypeString, Description: "Username for authentication.", Value: cfg.SMTP.Username}, - {ID: "SMTP.Password", Type: ConfigTypeString, Description: "Password for authentication.", Value: cfg.SMTP.Password, Password: true}, - {ID: "Webhook.Enable", Type: ConfigTypeBoolean, Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, - {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, - {ID: "Feedback.Enable", Type: ConfigTypeBoolean, Description: "Enables Feedback link in nav bar.", Value: fmt.Sprintf("%t", cfg.Feedback.Enable)}, - {ID: "Feedback.OverrideURL", Type: ConfigTypeString, Description: "Use a custom URL for Feedback link in nav bar.", Value: cfg.Feedback.OverrideURL}, + {ID: "General.PublicURL", Type: ConfigTypeString, DisplayName: "General.PublicURL", Description: "Publicly routable URL for UI links and API calls.", Value: cfg.General.PublicURL}, + {ID: "General.GoogleAnalyticsID", Type: ConfigTypeString, DisplayName: "General.GoogleAnalyticsID", Description: "", Value: cfg.General.GoogleAnalyticsID}, + {ID: "General.NotificationDisclaimer", Type: ConfigTypeString, DisplayName: "General.NotificationDisclaimer", Description: "Disclaimer text for receiving pre-recorded notifications (appears on profile page).", Value: cfg.General.NotificationDisclaimer}, + {ID: "General.MessageBundles", Type: ConfigTypeBoolean, DisplayName: "General.MessageBundles", Description: "Enables bundling status updates and alert notifications. Also allows 'ack/close all' responses to bundled alerts.", Value: fmt.Sprintf("%t", cfg.General.MessageBundles)}, + {ID: "General.ShortURL", Type: ConfigTypeString, DisplayName: "General.ShortURL", Description: "If set, messages will contain a shorter URL using this as a prefix (e.g. http://example.com). It should point to GoAlert and can be the same as the PublicURL.", Value: cfg.General.ShortURL}, + {ID: "General.DisableSMSLinks", Type: ConfigTypeBoolean, DisplayName: "General.DisableSMSLinks", Description: "If set, SMS messages will not contain a URL pointing to GoAlert.", Value: fmt.Sprintf("%t", cfg.General.DisableSMSLinks)}, + {ID: "General.DisableLabelCreation", Type: ConfigTypeBoolean, DisplayName: "General.DisableLabelCreation", Description: "Disables the ability to create new labels for services.", Value: fmt.Sprintf("%t", cfg.General.DisableLabelCreation)}, + {ID: "General.DisableCalendarSubscriptions", Type: ConfigTypeBoolean, DisplayName: "General.DisableCalendarSubscriptions", Description: "If set, disables all active calendar subscriptions as well as the ability to create new calendar subscriptions.", Value: fmt.Sprintf("%t", cfg.General.DisableCalendarSubscriptions)}, + {ID: "General.EnableV1GraphQL", Type: ConfigTypeBoolean, DisplayName: "General.EnableV1GraphQL", Description: "Enables the deprecated /v1/graphql endpoint (replaced by /api/graphql).", Value: fmt.Sprintf("%t", cfg.General.EnableV1GraphQL)}, + {ID: "Maintenance.AlertCleanupDays", Type: ConfigTypeInteger, DisplayName: "Maintenance.AlertCleanupDays", Description: "Closed alerts will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertCleanupDays)}, + {ID: "Maintenance.APIKeyExpireDays", Type: ConfigTypeInteger, DisplayName: "Maintenance.APIKeyExpireDays", Description: "Unused calendar API keys will be disabled after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.APIKeyExpireDays)}, + {ID: "Auth.RefererURLs", Type: ConfigTypeStringList, DisplayName: "Auth.RefererURLs", Description: "Allowed referer URLs for auth and redirects.", Value: strings.Join(cfg.Auth.RefererURLs, "\n")}, + {ID: "Auth.DisableBasic", Type: ConfigTypeBoolean, DisplayName: "Auth.DisableBasic", Description: "Disallow username/password login.", Value: fmt.Sprintf("%t", cfg.Auth.DisableBasic)}, + {ID: "GitHub.Enable", Type: ConfigTypeBoolean, DisplayName: "GitHub.Enable", Description: "Enable GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.Enable)}, + {ID: "GitHub.NewUsers", Type: ConfigTypeBoolean, DisplayName: "GitHub.NewUsers", Description: "Allow new user creation via GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.NewUsers)}, + {ID: "GitHub.ClientID", Type: ConfigTypeString, DisplayName: "GitHub.ClientID", Description: "", Value: cfg.GitHub.ClientID}, + {ID: "GitHub.ClientSecret", Type: ConfigTypeString, DisplayName: "GitHub.ClientSecret", Description: "", Value: cfg.GitHub.ClientSecret, Password: true}, + {ID: "GitHub.AllowedUsers", Type: ConfigTypeStringList, DisplayName: "GitHub.AllowedUsers", Description: "Allow any of the listed GitHub usernames to authenticate. Use '*' to allow any user.", Value: strings.Join(cfg.GitHub.AllowedUsers, "\n")}, + {ID: "GitHub.AllowedOrgs", Type: ConfigTypeStringList, DisplayName: "GitHub.AllowedOrgs", Description: "Allow any member of any listed GitHub org (or team, using the format 'org/team') to authenticate.", Value: strings.Join(cfg.GitHub.AllowedOrgs, "\n")}, + {ID: "GitHub.EnterpriseURL", Type: ConfigTypeString, DisplayName: "GitHub.EnterpriseURL", Description: "GitHub URL (without /api) when used with GitHub Enterprise.", Value: cfg.GitHub.EnterpriseURL}, + {ID: "OIDC.Enable", Type: ConfigTypeBoolean, DisplayName: "OIDC.Enable", Description: "Enable OpenID Connect authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.Enable)}, + {ID: "OIDC.NewUsers", Type: ConfigTypeBoolean, DisplayName: "OIDC.NewUsers", Description: "Allow new user creation via OIDC authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.NewUsers)}, + {ID: "OIDC.OverrideName", Type: ConfigTypeString, DisplayName: "OIDC.OverrideName", Description: "Set the name/label on the login page to something other than OIDC.", Value: cfg.OIDC.OverrideName}, + {ID: "OIDC.IssuerURL", Type: ConfigTypeString, DisplayName: "OIDC.IssuerURL", Description: "", Value: cfg.OIDC.IssuerURL}, + {ID: "OIDC.ClientID", Type: ConfigTypeString, DisplayName: "OIDC.ClientID", Description: "", Value: cfg.OIDC.ClientID}, + {ID: "OIDC.ClientSecret", Type: ConfigTypeString, DisplayName: "OIDC.ClientSecret", Description: "", Value: cfg.OIDC.ClientSecret, Password: true}, + {ID: "OIDC.Scopes", Type: ConfigTypeString, DisplayName: "OIDC.Scopes", Description: "Requested scopes for authentication. If left blank, openid, profile, and email will be used.", Value: cfg.OIDC.Scopes}, + {ID: "OIDC.UserInfoEmailPath", Type: ConfigTypeString, DisplayName: "OIDC.UserInfoEmailPath", Description: "JMESPath expression to find email address in UserInfo. If set, the email claim will be ignored in favor of this. (suggestion: email).", Value: cfg.OIDC.UserInfoEmailPath}, + {ID: "OIDC.UserInfoEmailVerifiedPath", Type: ConfigTypeString, DisplayName: "OIDC.UserInfoEmailVerifiedPath", Description: "JMESPath expression to find email verification state in UserInfo. If set, the email_verified claim will be ignored in favor of this. (suggestion: email_verified).", Value: cfg.OIDC.UserInfoEmailVerifiedPath}, + {ID: "OIDC.UserInfoNamePath", Type: ConfigTypeString, DisplayName: "OIDC.UserInfoNamePath", Description: "JMESPath expression to find full name in UserInfo. If set, the name claim will be ignored in favor of this. (suggestion: name || cn || join(' ', [firstname, lastname]))", Value: cfg.OIDC.UserInfoNamePath}, + {ID: "Mailgun.Enable", Type: ConfigTypeBoolean, DisplayName: "Mailgun.Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Mailgun.Enable)}, + {ID: "Mailgun.APIKey", Type: ConfigTypeString, DisplayName: "Mailgun.APIKey", Description: "", Value: cfg.Mailgun.APIKey, Password: true}, + {ID: "Mailgun.EmailDomain", Type: ConfigTypeString, DisplayName: "Mailgun.EmailDomain", Description: "The TO address for all incoming alerts.", Value: cfg.Mailgun.EmailDomain}, + {ID: "Slack.Enable", Type: ConfigTypeBoolean, DisplayName: "Slack.Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Slack.Enable)}, + {ID: "Slack.ClientID", Type: ConfigTypeString, DisplayName: "Slack.ClientID", Description: "", Value: cfg.Slack.ClientID}, + {ID: "Slack.ClientSecret", Type: ConfigTypeString, DisplayName: "Slack.ClientSecret", Description: "", Value: cfg.Slack.ClientSecret, Password: true}, + {ID: "Slack.AccessToken", Type: ConfigTypeString, DisplayName: "Slack.AccessToken", Description: "Slack app bot user OAuth access token (should start with xoxb-).", Value: cfg.Slack.AccessToken, Password: true}, + {ID: "Twilio.Enable", Type: ConfigTypeBoolean, DisplayName: "Twilio.Enable", Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, + {ID: "Twilio.AccountSID", Type: ConfigTypeString, DisplayName: "Twilio.AccountSID", Description: "", Value: cfg.Twilio.AccountSID}, + {ID: "Twilio.AuthToken", Type: ConfigTypeString, DisplayName: "Twilio.AuthToken", Description: "The primary Auth Token for Twilio. Must be primary (not secondary) for request valiation.", Value: cfg.Twilio.AuthToken, Password: true}, + {ID: "Twilio.FromNumber", Type: ConfigTypeString, DisplayName: "From Number or Messenger SID", Description: "The Twilio number or Messenger SID to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, + {ID: "Twilio.DisableTwoWaySMS", Type: ConfigTypeBoolean, DisplayName: "Twilio.DisableTwoWaySMS", Description: "Disables SMS reply codes for alert messages.", Value: fmt.Sprintf("%t", cfg.Twilio.DisableTwoWaySMS)}, + {ID: "Twilio.SMSCarrierLookup", Type: ConfigTypeBoolean, DisplayName: "Twilio.SMSCarrierLookup", Description: "Perform carrier lookup of SMS contact methods (required for SMSFromNumberOverride). Extra charges may apply.", Value: fmt.Sprintf("%t", cfg.Twilio.SMSCarrierLookup)}, + {ID: "Twilio.SMSFromNumberOverride", Type: ConfigTypeStringList, DisplayName: "Twilio.SMSFromNumberOverride", Description: "List of 'carrier=number' pairs, SMS messages to numbers of the provided carrier string (exact match) will use the alternate From Number.", Value: strings.Join(cfg.Twilio.SMSFromNumberOverride, "\n")}, + {ID: "SMTP.Enable", Type: ConfigTypeBoolean, DisplayName: "SMTP.Enable", Description: "Enables email as a contact method.", Value: fmt.Sprintf("%t", cfg.SMTP.Enable)}, + {ID: "SMTP.From", Type: ConfigTypeString, DisplayName: "SMTP.From", Description: "The email address messages should be sent from.", Value: cfg.SMTP.From}, + {ID: "SMTP.Address", Type: ConfigTypeString, DisplayName: "SMTP.Address", Description: "The server address to use for sending email. Port is optional.", Value: cfg.SMTP.Address}, + {ID: "SMTP.DisableTLS", Type: ConfigTypeBoolean, DisplayName: "SMTP.DisableTLS", Description: "Disables TLS on the connection (STARTTLS will still be used if supported).", Value: fmt.Sprintf("%t", cfg.SMTP.DisableTLS)}, + {ID: "SMTP.SkipVerify", Type: ConfigTypeBoolean, DisplayName: "SMTP.SkipVerify", Description: "Disables certificate validation for TLS/STARTTLS (insecure).", Value: fmt.Sprintf("%t", cfg.SMTP.SkipVerify)}, + {ID: "SMTP.Username", Type: ConfigTypeString, DisplayName: "SMTP.Username", Description: "Username for authentication.", Value: cfg.SMTP.Username}, + {ID: "SMTP.Password", Type: ConfigTypeString, DisplayName: "SMTP.Password", Description: "Password for authentication.", Value: cfg.SMTP.Password, Password: true}, + {ID: "Webhook.Enable", Type: ConfigTypeBoolean, DisplayName: "Webhook.Enable", Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, + {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, DisplayName: "Webhook.AllowedURLs", Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, + {ID: "Feedback.Enable", Type: ConfigTypeBoolean, DisplayName: "Feedback.Enable", Description: "Enables Feedback link in nav bar.", Value: fmt.Sprintf("%t", cfg.Feedback.Enable)}, + {ID: "Feedback.OverrideURL", Type: ConfigTypeString, DisplayName: "Feedback.OverrideURL", Description: "Use a custom URL for Feedback link in nav bar.", Value: cfg.Feedback.OverrideURL}, } } // MapPublicConfigValues will map a Config struct into a flat list of ConfigValue structs. func MapPublicConfigValues(cfg config.Config) []ConfigValue { return []ConfigValue{ - {ID: "General.PublicURL", Type: ConfigTypeString, Description: "Publicly routable URL for UI links and API calls.", Value: cfg.General.PublicURL}, - {ID: "General.GoogleAnalyticsID", Type: ConfigTypeString, Description: "", Value: cfg.General.GoogleAnalyticsID}, - {ID: "General.NotificationDisclaimer", Type: ConfigTypeString, Description: "Disclaimer text for receiving pre-recorded notifications (appears on profile page).", Value: cfg.General.NotificationDisclaimer}, - {ID: "General.MessageBundles", Type: ConfigTypeBoolean, Description: "Enables bundling status updates and alert notifications. Also allows 'ack/close all' responses to bundled alerts.", Value: fmt.Sprintf("%t", cfg.General.MessageBundles)}, - {ID: "General.ShortURL", Type: ConfigTypeString, Description: "If set, messages will contain a shorter URL using this as a prefix (e.g. http://example.com). It should point to GoAlert and can be the same as the PublicURL.", Value: cfg.General.ShortURL}, - {ID: "General.DisableSMSLinks", Type: ConfigTypeBoolean, Description: "If set, SMS messages will not contain a URL pointing to GoAlert.", Value: fmt.Sprintf("%t", cfg.General.DisableSMSLinks)}, - {ID: "General.DisableLabelCreation", Type: ConfigTypeBoolean, Description: "Disables the ability to create new labels for services.", Value: fmt.Sprintf("%t", cfg.General.DisableLabelCreation)}, - {ID: "General.DisableCalendarSubscriptions", Type: ConfigTypeBoolean, Description: "If set, disables all active calendar subscriptions as well as the ability to create new calendar subscriptions.", Value: fmt.Sprintf("%t", cfg.General.DisableCalendarSubscriptions)}, - {ID: "Maintenance.AlertCleanupDays", Type: ConfigTypeInteger, Description: "Closed alerts will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertCleanupDays)}, - {ID: "Maintenance.APIKeyExpireDays", Type: ConfigTypeInteger, Description: "Unused calendar API keys will be disabled after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.APIKeyExpireDays)}, - {ID: "Auth.DisableBasic", Type: ConfigTypeBoolean, Description: "Disallow username/password login.", Value: fmt.Sprintf("%t", cfg.Auth.DisableBasic)}, - {ID: "GitHub.Enable", Type: ConfigTypeBoolean, Description: "Enable GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.Enable)}, - {ID: "OIDC.Enable", Type: ConfigTypeBoolean, Description: "Enable OpenID Connect authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.Enable)}, - {ID: "Mailgun.Enable", Type: ConfigTypeBoolean, Description: "", Value: fmt.Sprintf("%t", cfg.Mailgun.Enable)}, - {ID: "Slack.Enable", Type: ConfigTypeBoolean, Description: "", Value: fmt.Sprintf("%t", cfg.Slack.Enable)}, - {ID: "Twilio.Enable", Type: ConfigTypeBoolean, Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, - {ID: "Twilio.FromNumber", Type: ConfigTypeString, Description: "The Twilio number to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, - {ID: "SMTP.Enable", Type: ConfigTypeBoolean, Description: "Enables email as a contact method.", Value: fmt.Sprintf("%t", cfg.SMTP.Enable)}, - {ID: "SMTP.From", Type: ConfigTypeString, Description: "The email address messages should be sent from.", Value: cfg.SMTP.From}, - {ID: "Webhook.Enable", Type: ConfigTypeBoolean, Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, - {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, - {ID: "Feedback.Enable", Type: ConfigTypeBoolean, Description: "Enables Feedback link in nav bar.", Value: fmt.Sprintf("%t", cfg.Feedback.Enable)}, - {ID: "Feedback.OverrideURL", Type: ConfigTypeString, Description: "Use a custom URL for Feedback link in nav bar.", Value: cfg.Feedback.OverrideURL}, + {ID: "General.PublicURL", Type: ConfigTypeString, DisplayName: "General.PublicURL", Description: "Publicly routable URL for UI links and API calls.", Value: cfg.General.PublicURL}, + {ID: "General.GoogleAnalyticsID", Type: ConfigTypeString, DisplayName: "General.GoogleAnalyticsID", Description: "", Value: cfg.General.GoogleAnalyticsID}, + {ID: "General.NotificationDisclaimer", Type: ConfigTypeString, DisplayName: "General.NotificationDisclaimer", Description: "Disclaimer text for receiving pre-recorded notifications (appears on profile page).", Value: cfg.General.NotificationDisclaimer}, + {ID: "General.MessageBundles", Type: ConfigTypeBoolean, DisplayName: "General.MessageBundles", Description: "Enables bundling status updates and alert notifications. Also allows 'ack/close all' responses to bundled alerts.", Value: fmt.Sprintf("%t", cfg.General.MessageBundles)}, + {ID: "General.ShortURL", Type: ConfigTypeString, DisplayName: "General.ShortURL", Description: "If set, messages will contain a shorter URL using this as a prefix (e.g. http://example.com). It should point to GoAlert and can be the same as the PublicURL.", Value: cfg.General.ShortURL}, + {ID: "General.DisableSMSLinks", Type: ConfigTypeBoolean, DisplayName: "General.DisableSMSLinks", Description: "If set, SMS messages will not contain a URL pointing to GoAlert.", Value: fmt.Sprintf("%t", cfg.General.DisableSMSLinks)}, + {ID: "General.DisableLabelCreation", Type: ConfigTypeBoolean, DisplayName: "General.DisableLabelCreation", Description: "Disables the ability to create new labels for services.", Value: fmt.Sprintf("%t", cfg.General.DisableLabelCreation)}, + {ID: "General.DisableCalendarSubscriptions", Type: ConfigTypeBoolean, DisplayName: "General.DisableCalendarSubscriptions", Description: "If set, disables all active calendar subscriptions as well as the ability to create new calendar subscriptions.", Value: fmt.Sprintf("%t", cfg.General.DisableCalendarSubscriptions)}, + {ID: "Maintenance.AlertCleanupDays", Type: ConfigTypeInteger, DisplayName: "Maintenance.AlertCleanupDays", Description: "Closed alerts will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertCleanupDays)}, + {ID: "Maintenance.APIKeyExpireDays", Type: ConfigTypeInteger, DisplayName: "Maintenance.APIKeyExpireDays", Description: "Unused calendar API keys will be disabled after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.APIKeyExpireDays)}, + {ID: "Auth.DisableBasic", Type: ConfigTypeBoolean, DisplayName: "Auth.DisableBasic", Description: "Disallow username/password login.", Value: fmt.Sprintf("%t", cfg.Auth.DisableBasic)}, + {ID: "GitHub.Enable", Type: ConfigTypeBoolean, DisplayName: "GitHub.Enable", Description: "Enable GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.Enable)}, + {ID: "OIDC.Enable", Type: ConfigTypeBoolean, DisplayName: "OIDC.Enable", Description: "Enable OpenID Connect authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.Enable)}, + {ID: "Mailgun.Enable", Type: ConfigTypeBoolean, DisplayName: "Mailgun.Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Mailgun.Enable)}, + {ID: "Slack.Enable", Type: ConfigTypeBoolean, DisplayName: "Slack.Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Slack.Enable)}, + {ID: "Twilio.Enable", Type: ConfigTypeBoolean, DisplayName: "Twilio.Enable", Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, + {ID: "Twilio.FromNumber", Type: ConfigTypeString, DisplayName: "From Number or Messenger SID", Description: "The Twilio number or Messenger SID to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, + {ID: "SMTP.Enable", Type: ConfigTypeBoolean, DisplayName: "SMTP.Enable", Description: "Enables email as a contact method.", Value: fmt.Sprintf("%t", cfg.SMTP.Enable)}, + {ID: "SMTP.From", Type: ConfigTypeString, DisplayName: "SMTP.From", Description: "The email address messages should be sent from.", Value: cfg.SMTP.From}, + {ID: "Webhook.Enable", Type: ConfigTypeBoolean, DisplayName: "Webhook.Enable", Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, + {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, DisplayName: "Webhook.AllowedURLs", Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, + {ID: "Feedback.Enable", Type: ConfigTypeBoolean, DisplayName: "Feedback.Enable", Description: "Enables Feedback link in nav bar.", Value: fmt.Sprintf("%t", cfg.Feedback.Enable)}, + {ID: "Feedback.OverrideURL", Type: ConfigTypeString, DisplayName: "Feedback.OverrideURL", Description: "Use a custom URL for Feedback link in nav bar.", Value: cfg.Feedback.OverrideURL}, } } diff --git a/graphql2/models_gen.go b/graphql2/models_gen.go index 9ae64e17e0..b06dd5d683 100644 --- a/graphql2/models_gen.go +++ b/graphql2/models_gen.go @@ -80,6 +80,7 @@ type ConfigHint struct { type ConfigValue struct { ID string `json:"id"` + DisplayName string `json:"displayName"` Description string `json:"description"` Value string `json:"value"` Type ConfigType `json:"type"` diff --git a/graphql2/schema.graphql b/graphql2/schema.graphql index 109a07dbe5..e30f02efbe 100644 --- a/graphql2/schema.graphql +++ b/graphql2/schema.graphql @@ -136,6 +136,7 @@ input SystemLimitInput { type ConfigValue { id: String! + displayName: String! description: String! value: String! type: ConfigType! diff --git a/web/src/app/admin/AdminConfig.tsx b/web/src/app/admin/AdminConfig.tsx index fba25a800e..beac419c54 100644 --- a/web/src/app/admin/AdminConfig.tsx +++ b/web/src/app/admin/AdminConfig.tsx @@ -29,6 +29,7 @@ const query = gql` query getConfig { config(all: true) { id + displayName description password type @@ -224,7 +225,8 @@ export default function AdminConfig(): JSX.Element { ) .map((f: ConfigValue) => ({ id: f.id, - label: formatHeading(_.last(f.id.split('.'))), + displayName: f.displayName, + label: formatHeading(_.last(f.displayName.split('.'))), description: f.description, password: f.password, type: f.type, diff --git a/web/src/app/admin/AdminFieldComponents.tsx b/web/src/app/admin/AdminFieldComponents.tsx index 2b07ef4a79..6f103b8ef5 100644 --- a/web/src/app/admin/AdminFieldComponents.tsx +++ b/web/src/app/admin/AdminFieldComponents.tsx @@ -36,8 +36,14 @@ export function StringInput(props: InputProps): JSX.Element { ) } - if (type === 'tel') { - return onChange(e.target.value)} {...rest} /> + if (props.name === 'Twilio.FromNumber') { + return ( + onChange(e.target.value)} + {...rest} + inputTypes={['tel', 'sid']} + /> + ) } return (
) diff --git a/web/src/schema.d.ts b/web/src/schema.d.ts index dd9be63e69..dc32fcd434 100644 --- a/web/src/schema.d.ts +++ b/web/src/schema.d.ts @@ -65,6 +65,7 @@ export interface SystemLimitInput { export interface ConfigValue { id: string + displayName: string description: string value: string type: ConfigType From a18669cfc097106a4df6e6e63ef1ff1b8f1b94e2 Mon Sep 17 00:00:00 2001 From: dctalbot Date: Tue, 14 Sep 2021 15:15:46 -0500 Subject: [PATCH 06/32] server side validation --- graphql2/graphqlapp/toolbox.go | 8 +++++++- validation/validate/sid.go | 17 +++++++++++++++++ web/src/app/admin/AdminSMSSend.tsx | 7 +++++-- 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 validation/validate/sid.go diff --git a/graphql2/graphqlapp/toolbox.go b/graphql2/graphqlapp/toolbox.go index 9ce0e04ed0..0ffc82c433 100644 --- a/graphql2/graphqlapp/toolbox.go +++ b/graphql2/graphqlapp/toolbox.go @@ -8,6 +8,7 @@ import ( "github.com/target/goalert/graphql2" "github.com/target/goalert/notification/twilio" "github.com/target/goalert/permission" + "github.com/target/goalert/validation" "github.com/target/goalert/validation/validate" "github.com/ttacon/libphonenumber" ) @@ -22,9 +23,14 @@ func (a *Mutation) DebugSendSms(ctx context.Context, input graphql2.DebugSendSMS return nil, err } + var fromErr error + if validate.Phone("From", input.From) != nil && validate.SID("From", input.From) != nil { + fromErr = validation.NewFieldError("From", "is not a valid phone number or alphanumeric sender ID.") + } + err = validate.Many( validate.Phone("To", input.To), - validate.Phone("From", input.From), + fromErr, validate.Text("Body", input.Body, 1, 1000), ) if err != nil { diff --git a/validation/validate/sid.go b/validation/validate/sid.go new file mode 100644 index 0000000000..68ff233ed8 --- /dev/null +++ b/validation/validate/sid.go @@ -0,0 +1,17 @@ +package validate + +import ( + "strings" + + "github.com/target/goalert/validation" +) + +// SID will validate an SID, returning a FieldError if invalid. +func SID(fname, value string) error { + + if !strings.HasPrefix(value, "MG") { + return validation.NewFieldError(fname, "must begin with MG") + } + + return nil +} diff --git a/web/src/app/admin/AdminSMSSend.tsx b/web/src/app/admin/AdminSMSSend.tsx index 6cd1946367..b6c8265c0b 100644 --- a/web/src/app/admin/AdminSMSSend.tsx +++ b/web/src/app/admin/AdminSMSSend.tsx @@ -106,8 +106,11 @@ export default function AdminSMSSend(): JSX.Element {
- Sent from {sendStatus.data.debugSendSMS.fromNumber}. Open in - Twilio  + {/* TODO: query for message status if from number / SID not immediately available */} + {sendStatus.data.debugSendSMS.fromNumber + ? `Sent from ${sendStatus.data.debugSendSMS.fromNumber}. ` + : ''} + Open in Twilio 
From 984d3c15cf76abfc6a47f777ac3ff61d7c41fd8a Mon Sep 17 00:00:00 2001 From: dctalbot Date: Tue, 14 Sep 2021 15:25:06 -0500 Subject: [PATCH 07/32] refactor --- web/src/app/util/TelTextField.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/web/src/app/util/TelTextField.tsx b/web/src/app/util/TelTextField.tsx index 77af31753b..d03d5b187b 100644 --- a/web/src/app/util/TelTextField.tsx +++ b/web/src/app/util/TelTextField.tsx @@ -36,8 +36,8 @@ export default function TelTextField(_props: TelTextFieldProps): JSX.Element { const classes = useStyles() const [phoneNumber, setPhoneNumber] = useState('') - const onlyTel = inputTypes.includes('tel') && inputTypes.length === 1 - const onlySID = inputTypes.includes('sid') && inputTypes.length === 1 + const onlyTel = inputTypes.length === 1 && inputTypes[0] === 'tel' + const onlySID = inputTypes.length === 1 && inputTypes[0] === 'sid' const isSID = inputTypes.includes('sid') && value.match(/^MG[a-zA-Z0-9]+$/) // debounce to set the phone number @@ -49,7 +49,7 @@ export default function TelTextField(_props: TelTextFieldProps): JSX.Element { }, [value]) const skipValidatePhoneNumber = (): boolean => { - if (!phoneNumber || props.disabled) { + if (!phoneNumber || props.disabled || !inputTypes.includes('tel')) { return true } if (onlyTel && !value.match(/(^\+)[0-9]+$/)) { @@ -114,9 +114,12 @@ export default function TelTextField(_props: TelTextFieldProps): JSX.Element { if (helperText) { return helperText } - if (inputTypes.includes('tel') && inputTypes.length === 1) { + if (onlyTel) { return 'Please include a country code e.g. +1 (USA), +91 (India), +44 (UK)' } + if (onlySID) { + return '' + } return 'For phone numbers, please include a country code e.g. +1 (USA), +91 (India), +44 (UK)' } From 661663471d809a8a966cc5c6d4dd483108b897f8 Mon Sep 17 00:00:00 2001 From: dctalbot Date: Tue, 14 Sep 2021 15:33:36 -0500 Subject: [PATCH 08/32] add plus sign for region code --- web/src/cypress/integration/profile.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/cypress/integration/profile.ts b/web/src/cypress/integration/profile.ts index d3e5db0619..d923d60717 100644 --- a/web/src/cypress/integration/profile.ts +++ b/web/src/cypress/integration/profile.ts @@ -243,7 +243,7 @@ function testProfile(): void { countryCodeCheck('UK', '+44', '7911123456', '+44 7911 123456') it('should not allow fake country codes', () => { - const value = '810' + c.integer({ min: 3000000, max: 3999999 }) + const value = '+810' + c.integer({ min: 3000000, max: 3999999 }) const name = 'CM SM ' + c.word({ length: 8 }) const type = c.pickone(['SMS', 'VOICE']) From 902532ca5e0b0f19037f502a1933b4e38cb02519 Mon Sep 17 00:00:00 2001 From: dctalbot Date: Tue, 14 Sep 2021 15:33:49 -0500 Subject: [PATCH 09/32] set type as tel for telelphone-only fields --- web/src/app/util/TelTextField.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/src/app/util/TelTextField.tsx b/web/src/app/util/TelTextField.tsx index d03d5b187b..9abb70b8de 100644 --- a/web/src/app/util/TelTextField.tsx +++ b/web/src/app/util/TelTextField.tsx @@ -127,6 +127,7 @@ export default function TelTextField(_props: TelTextFieldProps): JSX.Element { Date: Wed, 15 Sep 2021 10:37:25 -0500 Subject: [PATCH 10/32] refactor --- web/src/app/util/TelTextField.tsx | 84 +++++++++++-------------------- 1 file changed, 30 insertions(+), 54 deletions(-) diff --git a/web/src/app/util/TelTextField.tsx b/web/src/app/util/TelTextField.tsx index 9abb70b8de..70068d01a8 100644 --- a/web/src/app/util/TelTextField.tsx +++ b/web/src/app/util/TelTextField.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react' import { useQuery, gql } from '@apollo/client' import TextField, { TextFieldProps } from '@material-ui/core/TextField' -import { InputProps } from '@material-ui/core/Input' import { Check, Close } from '@material-ui/icons' import InputAdornment from '@material-ui/core/InputAdornment' import { makeStyles } from '@material-ui/core' @@ -31,105 +30,82 @@ type TelTextFieldProps = TextFieldProps & { inputTypes?: InputType[] } -export default function TelTextField(_props: TelTextFieldProps): JSX.Element { - const { inputTypes = ['tel'], value = '', helperText = '', ...props } = _props +export default function TelTextField(props: TelTextFieldProps): JSX.Element { + const { inputTypes = ['tel'], value = '', ...textFieldProps } = props const classes = useStyles() - const [phoneNumber, setPhoneNumber] = useState('') - - const onlyTel = inputTypes.length === 1 && inputTypes[0] === 'tel' - const onlySID = inputTypes.length === 1 && inputTypes[0] === 'sid' - const isSID = inputTypes.includes('sid') && value.match(/^MG[a-zA-Z0-9]+$/) + const [debouncedValue, setDebouncedValue] = useState('') // debounce to set the phone number useEffect(() => { const t = setTimeout(() => { - setPhoneNumber(value) + setDebouncedValue(value) }, DEBOUNCE_DELAY) return () => clearTimeout(t) }, [value]) - const skipValidatePhoneNumber = (): boolean => { - if (!phoneNumber || props.disabled || !inputTypes.includes('tel')) { + const onlyTel = inputTypes.length === 1 && inputTypes[0] === 'tel' + const onlySID = inputTypes.length === 1 && inputTypes[0] === 'sid' + const isSID = inputTypes.includes('sid') && value.match(/^MG[a-zA-Z0-9]+$/) + + const skipValidation = (): boolean => { + if (!debouncedValue || props.disabled || !inputTypes.includes('tel')) { return true } if (onlyTel && !value.match(/(^\+)[0-9]+$/)) { return true } - if (onlySID) { - return true - } - if (isSID) { + if (onlySID || isSID) { return true } return false } - // check validation of the input phoneNumber through graphql + // validate the input value const { data } = useQuery(isValidNumber, { pollInterval: 0, - variables: { number: phoneNumber }, + variables: { number: debouncedValue }, fetchPolicy: 'cache-first', - skip: skipValidatePhoneNumber(), + skip: skipValidation(), }) - // fetch validation const valid = Boolean(data?.phoneNumberInfo?.valid) let adorn if (value === '' || isSID || props.disabled) { - // no adornment if empty + // no adornment } else if (valid) { adorn = - } else if (valid === false) { + } else { adorn = } - let iprops: Partial - iprops = {} - - // if has inputProps from parent commponent, spread it in the iprops - if (props.InputProps !== undefined) { - iprops = { - ...iprops, - ...props.InputProps, - } - } - - // add live validation icon to the right of the textfield as an endAdornment - if (adorn) { - iprops = { - endAdornment: {adorn}, - ...iprops, - } - } - - // remove unwanted character - function handleChange(e: React.ChangeEvent): void { - if (!props.onChange) return - if (!e.target.value) return props.onChange(e) - return props.onChange(e) - } + // add live validation icon to the right of the textfield + const InputProps = adorn + ? { + endAdornment: {adorn}, + ...props.InputProps, + } + : props.InputProps const getHelperText = (): TextFieldProps['helperText'] => { - if (helperText) { - return helperText + if (props.helperText) { + return props.helperText } if (onlyTel) { return 'Please include a country code e.g. +1 (USA), +91 (India), +44 (UK)' } - if (onlySID) { - return '' + if (inputTypes.includes('tel')) { + return 'For phone numbers, please include a country code e.g. +1 (USA), +91 (India), +44 (UK)' } - return 'For phone numbers, please include a country code e.g. +1 (USA), +91 (India), +44 (UK)' + return '' } return ( From 73cdf39c8bde2dacbaf0911aa1feb3804d9fcc4b Mon Sep 17 00:00:00 2001 From: dctalbot Date: Wed, 15 Sep 2021 10:50:34 -0500 Subject: [PATCH 11/32] enforce format for telephone-only fields --- web/src/app/util/TelTextField.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/web/src/app/util/TelTextField.tsx b/web/src/app/util/TelTextField.tsx index 70068d01a8..bc822debfe 100644 --- a/web/src/app/util/TelTextField.tsx +++ b/web/src/app/util/TelTextField.tsx @@ -100,6 +100,17 @@ export default function TelTextField(props: TelTextFieldProps): JSX.Element { return '' } + const handleChange = (e: React.ChangeEvent): void => { + if (!props.onChange) return + if (!e.target.value) return props.onChange(e) + + if (onlyTel) { + e.target.value = '+' + e.target.value.replace(/[^0-9]/g, '') + } + + return props.onChange(e) + } + return ( ) } From ef51f08a6d0e2254292f9fa74500b5cb3f3d38ce Mon Sep 17 00:00:00 2001 From: KatieMSB Date: Wed, 15 Sep 2021 11:07:12 -0500 Subject: [PATCH 12/32] format displayName in backend --- devtools/configparams/run.go | 30 +++++++++++++++++++++++++----- graphql2/schema.graphql | 1 + web/src/app/admin/AdminConfig.tsx | 6 ++---- web/src/app/admin/AdminSection.tsx | 10 +++------- 4 files changed, 31 insertions(+), 16 deletions(-) diff --git a/devtools/configparams/run.go b/devtools/configparams/run.go index 12b02afcd7..397907d461 100644 --- a/devtools/configparams/run.go +++ b/devtools/configparams/run.go @@ -5,6 +5,7 @@ import ( "fmt" "os" "reflect" + "regexp" "strconv" "strings" "text/template" @@ -39,7 +40,7 @@ import ( func MapConfigHints(cfg config.Hints) []ConfigHint { return []ConfigHint{ {{- range .HintFields }} - {ID: {{quote .ID}}, Value: {{.Value}}}, + {ID: {{quote .ID}}, DisplayName: {{quote .DisplayName}}, Value: {{.Value}}}, {{- end}} } } @@ -207,10 +208,29 @@ func printType(prefix string, v reflect.Type, displayName string, info string, p panic(fmt.Sprintf("not implemented for type %T", v.Kind())) } - if displayName == "" { - displayName = key + f = append(f, field{ID: key, Type: typ, DisplayName: printDisplayName(key, displayName), Desc: info, Value: value, Public: public, Password: pass}) + return f +} + +func printDisplayName(key, displayName string) string { + format := func(str string) string { + var formattedStr []string + pattern := regexp.MustCompile("(^[^A-Z]*|[A-Z]*)([A-Z][^A-Z]+|$)") + for _, subStr := range pattern.FindAllStringSubmatch(str, -1) { + if subStr[1] != "" { + formattedStr = append(formattedStr, subStr[1]) + } + if subStr[2] != "" { + formattedStr = append(formattedStr, subStr[2]) + } + } + return strings.Title(strings.Join(formattedStr, " ")) } - f = append(f, field{ID: key, Type: typ, DisplayName: displayName, Desc: info, Value: value, Public: public, Password: pass}) - return f + if displayName == "" { + fieldKey := strings.Split(key, ".") + displayName = format(fieldKey[len(fieldKey)-1]) + } + + return displayName } diff --git a/graphql2/schema.graphql b/graphql2/schema.graphql index e30f02efbe..30888e15ed 100644 --- a/graphql2/schema.graphql +++ b/graphql2/schema.graphql @@ -144,6 +144,7 @@ type ConfigValue { } type ConfigHint { id: String! + displayName: String! value: String! } enum ConfigType { diff --git a/web/src/app/admin/AdminConfig.tsx b/web/src/app/admin/AdminConfig.tsx index beac419c54..6c3485da1b 100644 --- a/web/src/app/admin/AdminConfig.tsx +++ b/web/src/app/admin/AdminConfig.tsx @@ -37,6 +37,7 @@ const query = gql` } configHints { id + displayName value } } @@ -123,8 +124,6 @@ export default function AdminConfig(): JSX.Element { .groupBy((f: ConfigHint) => f.id.split('.')[0]) .value() - const hintName = (id: string): string => startCase(id.split('.')[1]) - const handleExpandChange = (id: string) => () => setSection(id === section ? false : id) @@ -226,7 +225,6 @@ export default function AdminConfig(): JSX.Element { .map((f: ConfigValue) => ({ id: f.id, displayName: f.displayName, - label: formatHeading(_.last(f.displayName.split('.'))), description: f.description, password: f.password, type: f.type, @@ -238,7 +236,7 @@ export default function AdminConfig(): JSX.Element { hintGroups[groupID].map((h: ConfigHint) => ( void } @@ -78,7 +74,7 @@ export default function AdminSection(props: AdminSectionProps): JSX.Element { /> )} - {fields.map((f: FieldProps, idx: number) => { + {fields.map((f: ConfigValue, idx: number) => { const Field = components[f.type] return (
From 5b51f43ef53bce8dc9345085ef0a9521a2659fb9 Mon Sep 17 00:00:00 2001 From: KatieMSB Date: Wed, 15 Sep 2021 11:07:25 -0500 Subject: [PATCH 13/32] run generate --- graphql2/generated.go | 53 ++++++++++++- graphql2/mapconfig.go | 166 ++++++++++++++++++++--------------------- graphql2/models_gen.go | 5 +- web/src/schema.d.ts | 1 + 4 files changed, 138 insertions(+), 87 deletions(-) diff --git a/graphql2/generated.go b/graphql2/generated.go index 69800ecfb6..3b42b9cc03 100644 --- a/graphql2/generated.go +++ b/graphql2/generated.go @@ -134,8 +134,9 @@ type ComplexityRoot struct { } ConfigHint struct { - ID func(childComplexity int) int - Value func(childComplexity int) int + DisplayName func(childComplexity int) int + ID func(childComplexity int) int + Value func(childComplexity int) int } ConfigValue struct { @@ -921,6 +922,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.AuthSubjectConnection.PageInfo(childComplexity), true + case "ConfigHint.displayName": + if e.complexity.ConfigHint.DisplayName == nil { + break + } + + return e.complexity.ConfigHint.DisplayName(childComplexity), true + case "ConfigHint.id": if e.complexity.ConfigHint.ID == nil { break @@ -3330,6 +3338,7 @@ type ConfigValue { } type ConfigHint { id: String! + displayName: String! value: String! } enum ConfigType { @@ -6510,6 +6519,41 @@ func (ec *executionContext) _ConfigHint_id(ctx context.Context, field graphql.Co return ec.marshalNString2string(ctx, field.Selections, res) } +func (ec *executionContext) _ConfigHint_displayName(ctx context.Context, field graphql.CollectedField, obj *ConfigHint) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ConfigHint", + Field: field, + Args: nil, + IsMethod: false, + IsResolver: false, + } + + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.DisplayName, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + func (ec *executionContext) _ConfigHint_value(ctx context.Context, field graphql.CollectedField, obj *ConfigHint) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -20171,6 +20215,11 @@ func (ec *executionContext) _ConfigHint(ctx context.Context, sel ast.SelectionSe if out.Values[i] == graphql.Null { invalids++ } + case "displayName": + out.Values[i] = ec._ConfigHint_displayName(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } case "value": out.Values[i] = ec._ConfigHint_value(ctx, field, obj) if out.Values[i] == graphql.Null { diff --git a/graphql2/mapconfig.go b/graphql2/mapconfig.go index a2f67c09fa..b6d807dabd 100644 --- a/graphql2/mapconfig.go +++ b/graphql2/mapconfig.go @@ -13,103 +13,103 @@ import ( func MapConfigHints(cfg config.Hints) []ConfigHint { return []ConfigHint{ - {ID: "GitHub.AuthCallbackURL", Value: cfg.GitHub.AuthCallbackURL}, - {ID: "OIDC.RedirectURL", Value: cfg.OIDC.RedirectURL}, - {ID: "Mailgun.ForwardURL", Value: cfg.Mailgun.ForwardURL}, - {ID: "Twilio.MessageWebhookURL", Value: cfg.Twilio.MessageWebhookURL}, - {ID: "Twilio.VoiceWebhookURL", Value: cfg.Twilio.VoiceWebhookURL}, + {ID: "GitHub.AuthCallbackURL", DisplayName: "Auth Callback URL", Value: cfg.GitHub.AuthCallbackURL}, + {ID: "OIDC.RedirectURL", DisplayName: "Redirect URL", Value: cfg.OIDC.RedirectURL}, + {ID: "Mailgun.ForwardURL", DisplayName: "Forward URL", Value: cfg.Mailgun.ForwardURL}, + {ID: "Twilio.MessageWebhookURL", DisplayName: "Message Webhook URL", Value: cfg.Twilio.MessageWebhookURL}, + {ID: "Twilio.VoiceWebhookURL", DisplayName: "Voice Webhook URL", Value: cfg.Twilio.VoiceWebhookURL}, } } // MapConfigValues will map a Config struct into a flat list of ConfigValue structs. func MapConfigValues(cfg config.Config) []ConfigValue { return []ConfigValue{ - {ID: "General.ApplicationName", Type: ConfigTypeString, DisplayName: "General.ApplicationName", Description: "The name used in messaging and page titles. Defaults to \"GoAlert\".", Value: cfg.General.ApplicationName}, - {ID: "General.PublicURL", Type: ConfigTypeString, DisplayName: "General.PublicURL", Description: "Publicly routable URL for UI links and API calls.", Value: cfg.General.PublicURL}, - {ID: "General.GoogleAnalyticsID", Type: ConfigTypeString, DisplayName: "General.GoogleAnalyticsID", Description: "", Value: cfg.General.GoogleAnalyticsID}, - {ID: "General.NotificationDisclaimer", Type: ConfigTypeString, DisplayName: "General.NotificationDisclaimer", Description: "Disclaimer text for receiving pre-recorded notifications (appears on profile page).", Value: cfg.General.NotificationDisclaimer}, - {ID: "General.MessageBundles", Type: ConfigTypeBoolean, DisplayName: "General.MessageBundles", Description: "Enables bundling status updates and alert notifications. Also allows 'ack/close all' responses to bundled alerts.", Value: fmt.Sprintf("%t", cfg.General.MessageBundles)}, - {ID: "General.ShortURL", Type: ConfigTypeString, DisplayName: "General.ShortURL", Description: "If set, messages will contain a shorter URL using this as a prefix (e.g. http://example.com). It should point to GoAlert and can be the same as the PublicURL.", Value: cfg.General.ShortURL}, - {ID: "General.DisableSMSLinks", Type: ConfigTypeBoolean, DisplayName: "General.DisableSMSLinks", Description: "If set, SMS messages will not contain a URL pointing to GoAlert.", Value: fmt.Sprintf("%t", cfg.General.DisableSMSLinks)}, - {ID: "General.DisableLabelCreation", Type: ConfigTypeBoolean, DisplayName: "General.DisableLabelCreation", Description: "Disables the ability to create new labels for services.", Value: fmt.Sprintf("%t", cfg.General.DisableLabelCreation)}, - {ID: "General.DisableCalendarSubscriptions", Type: ConfigTypeBoolean, DisplayName: "General.DisableCalendarSubscriptions", Description: "If set, disables all active calendar subscriptions as well as the ability to create new calendar subscriptions.", Value: fmt.Sprintf("%t", cfg.General.DisableCalendarSubscriptions)}, - {ID: "General.EnableV1GraphQL", Type: ConfigTypeBoolean, DisplayName: "General.EnableV1GraphQL", Description: "Enables the deprecated /v1/graphql endpoint (replaced by /api/graphql).", Value: fmt.Sprintf("%t", cfg.General.EnableV1GraphQL)}, - {ID: "Maintenance.AlertCleanupDays", Type: ConfigTypeInteger, DisplayName: "Maintenance.AlertCleanupDays", Description: "Closed alerts will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertCleanupDays)}, - {ID: "Maintenance.APIKeyExpireDays", Type: ConfigTypeInteger, DisplayName: "Maintenance.APIKeyExpireDays", Description: "Unused calendar API keys will be disabled after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.APIKeyExpireDays)}, - {ID: "Auth.RefererURLs", Type: ConfigTypeStringList, DisplayName: "Auth.RefererURLs", Description: "Allowed referer URLs for auth and redirects.", Value: strings.Join(cfg.Auth.RefererURLs, "\n")}, - {ID: "Auth.DisableBasic", Type: ConfigTypeBoolean, DisplayName: "Auth.DisableBasic", Description: "Disallow username/password login.", Value: fmt.Sprintf("%t", cfg.Auth.DisableBasic)}, - {ID: "GitHub.Enable", Type: ConfigTypeBoolean, DisplayName: "GitHub.Enable", Description: "Enable GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.Enable)}, - {ID: "GitHub.NewUsers", Type: ConfigTypeBoolean, DisplayName: "GitHub.NewUsers", Description: "Allow new user creation via GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.NewUsers)}, - {ID: "GitHub.ClientID", Type: ConfigTypeString, DisplayName: "GitHub.ClientID", Description: "", Value: cfg.GitHub.ClientID}, - {ID: "GitHub.ClientSecret", Type: ConfigTypeString, DisplayName: "GitHub.ClientSecret", Description: "", Value: cfg.GitHub.ClientSecret, Password: true}, - {ID: "GitHub.AllowedUsers", Type: ConfigTypeStringList, DisplayName: "GitHub.AllowedUsers", Description: "Allow any of the listed GitHub usernames to authenticate. Use '*' to allow any user.", Value: strings.Join(cfg.GitHub.AllowedUsers, "\n")}, - {ID: "GitHub.AllowedOrgs", Type: ConfigTypeStringList, DisplayName: "GitHub.AllowedOrgs", Description: "Allow any member of any listed GitHub org (or team, using the format 'org/team') to authenticate.", Value: strings.Join(cfg.GitHub.AllowedOrgs, "\n")}, - {ID: "GitHub.EnterpriseURL", Type: ConfigTypeString, DisplayName: "GitHub.EnterpriseURL", Description: "GitHub URL (without /api) when used with GitHub Enterprise.", Value: cfg.GitHub.EnterpriseURL}, - {ID: "OIDC.Enable", Type: ConfigTypeBoolean, DisplayName: "OIDC.Enable", Description: "Enable OpenID Connect authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.Enable)}, - {ID: "OIDC.NewUsers", Type: ConfigTypeBoolean, DisplayName: "OIDC.NewUsers", Description: "Allow new user creation via OIDC authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.NewUsers)}, - {ID: "OIDC.OverrideName", Type: ConfigTypeString, DisplayName: "OIDC.OverrideName", Description: "Set the name/label on the login page to something other than OIDC.", Value: cfg.OIDC.OverrideName}, - {ID: "OIDC.IssuerURL", Type: ConfigTypeString, DisplayName: "OIDC.IssuerURL", Description: "", Value: cfg.OIDC.IssuerURL}, - {ID: "OIDC.ClientID", Type: ConfigTypeString, DisplayName: "OIDC.ClientID", Description: "", Value: cfg.OIDC.ClientID}, - {ID: "OIDC.ClientSecret", Type: ConfigTypeString, DisplayName: "OIDC.ClientSecret", Description: "", Value: cfg.OIDC.ClientSecret, Password: true}, - {ID: "OIDC.Scopes", Type: ConfigTypeString, DisplayName: "OIDC.Scopes", Description: "Requested scopes for authentication. If left blank, openid, profile, and email will be used.", Value: cfg.OIDC.Scopes}, - {ID: "OIDC.UserInfoEmailPath", Type: ConfigTypeString, DisplayName: "OIDC.UserInfoEmailPath", Description: "JMESPath expression to find email address in UserInfo. If set, the email claim will be ignored in favor of this. (suggestion: email).", Value: cfg.OIDC.UserInfoEmailPath}, - {ID: "OIDC.UserInfoEmailVerifiedPath", Type: ConfigTypeString, DisplayName: "OIDC.UserInfoEmailVerifiedPath", Description: "JMESPath expression to find email verification state in UserInfo. If set, the email_verified claim will be ignored in favor of this. (suggestion: email_verified).", Value: cfg.OIDC.UserInfoEmailVerifiedPath}, - {ID: "OIDC.UserInfoNamePath", Type: ConfigTypeString, DisplayName: "OIDC.UserInfoNamePath", Description: "JMESPath expression to find full name in UserInfo. If set, the name claim will be ignored in favor of this. (suggestion: name || cn || join(' ', [firstname, lastname]))", Value: cfg.OIDC.UserInfoNamePath}, - {ID: "Mailgun.Enable", Type: ConfigTypeBoolean, DisplayName: "Mailgun.Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Mailgun.Enable)}, - {ID: "Mailgun.APIKey", Type: ConfigTypeString, DisplayName: "Mailgun.APIKey", Description: "", Value: cfg.Mailgun.APIKey, Password: true}, - {ID: "Mailgun.EmailDomain", Type: ConfigTypeString, DisplayName: "Mailgun.EmailDomain", Description: "The TO address for all incoming alerts.", Value: cfg.Mailgun.EmailDomain}, - {ID: "Slack.Enable", Type: ConfigTypeBoolean, DisplayName: "Slack.Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Slack.Enable)}, - {ID: "Slack.ClientID", Type: ConfigTypeString, DisplayName: "Slack.ClientID", Description: "", Value: cfg.Slack.ClientID}, - {ID: "Slack.ClientSecret", Type: ConfigTypeString, DisplayName: "Slack.ClientSecret", Description: "", Value: cfg.Slack.ClientSecret, Password: true}, - {ID: "Slack.AccessToken", Type: ConfigTypeString, DisplayName: "Slack.AccessToken", Description: "Slack app bot user OAuth access token (should start with xoxb-).", Value: cfg.Slack.AccessToken, Password: true}, - {ID: "Twilio.Enable", Type: ConfigTypeBoolean, DisplayName: "Twilio.Enable", Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, - {ID: "Twilio.AccountSID", Type: ConfigTypeString, DisplayName: "Twilio.AccountSID", Description: "", Value: cfg.Twilio.AccountSID}, - {ID: "Twilio.AuthToken", Type: ConfigTypeString, DisplayName: "Twilio.AuthToken", Description: "The primary Auth Token for Twilio. Must be primary (not secondary) for request valiation.", Value: cfg.Twilio.AuthToken, Password: true}, + {ID: "General.ApplicationName", Type: ConfigTypeString, DisplayName: "Application Name", Description: "The name used in messaging and page titles. Defaults to \"GoAlert\".", Value: cfg.General.ApplicationName}, + {ID: "General.PublicURL", Type: ConfigTypeString, DisplayName: "Public URL", Description: "Publicly routable URL for UI links and API calls.", Value: cfg.General.PublicURL}, + {ID: "General.GoogleAnalyticsID", Type: ConfigTypeString, DisplayName: "Google Analytics ID", Description: "", Value: cfg.General.GoogleAnalyticsID}, + {ID: "General.NotificationDisclaimer", Type: ConfigTypeString, DisplayName: "Notification Disclaimer", Description: "Disclaimer text for receiving pre-recorded notifications (appears on profile page).", Value: cfg.General.NotificationDisclaimer}, + {ID: "General.MessageBundles", Type: ConfigTypeBoolean, DisplayName: "Message Bundles", Description: "Enables bundling status updates and alert notifications. Also allows 'ack/close all' responses to bundled alerts.", Value: fmt.Sprintf("%t", cfg.General.MessageBundles)}, + {ID: "General.ShortURL", Type: ConfigTypeString, DisplayName: "Short URL", Description: "If set, messages will contain a shorter URL using this as a prefix (e.g. http://example.com). It should point to GoAlert and can be the same as the PublicURL.", Value: cfg.General.ShortURL}, + {ID: "General.DisableSMSLinks", Type: ConfigTypeBoolean, DisplayName: "Disable SMS Links", Description: "If set, SMS messages will not contain a URL pointing to GoAlert.", Value: fmt.Sprintf("%t", cfg.General.DisableSMSLinks)}, + {ID: "General.DisableLabelCreation", Type: ConfigTypeBoolean, DisplayName: "Disable Label Creation", Description: "Disables the ability to create new labels for services.", Value: fmt.Sprintf("%t", cfg.General.DisableLabelCreation)}, + {ID: "General.DisableCalendarSubscriptions", Type: ConfigTypeBoolean, DisplayName: "Disable Calendar Subscriptions", Description: "If set, disables all active calendar subscriptions as well as the ability to create new calendar subscriptions.", Value: fmt.Sprintf("%t", cfg.General.DisableCalendarSubscriptions)}, + {ID: "General.EnableV1GraphQL", Type: ConfigTypeBoolean, DisplayName: "Enable V1 Graph QL", Description: "Enables the deprecated /v1/graphql endpoint (replaced by /api/graphql).", Value: fmt.Sprintf("%t", cfg.General.EnableV1GraphQL)}, + {ID: "Maintenance.AlertCleanupDays", Type: ConfigTypeInteger, DisplayName: "Alert Cleanup Days", Description: "Closed alerts will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertCleanupDays)}, + {ID: "Maintenance.APIKeyExpireDays", Type: ConfigTypeInteger, DisplayName: "API Key Expire Days", Description: "Unused calendar API keys will be disabled after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.APIKeyExpireDays)}, + {ID: "Auth.RefererURLs", Type: ConfigTypeStringList, DisplayName: "Referer UR Ls", Description: "Allowed referer URLs for auth and redirects.", Value: strings.Join(cfg.Auth.RefererURLs, "\n")}, + {ID: "Auth.DisableBasic", Type: ConfigTypeBoolean, DisplayName: "Disable Basic", Description: "Disallow username/password login.", Value: fmt.Sprintf("%t", cfg.Auth.DisableBasic)}, + {ID: "GitHub.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enable GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.Enable)}, + {ID: "GitHub.NewUsers", Type: ConfigTypeBoolean, DisplayName: "New Users", Description: "Allow new user creation via GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.NewUsers)}, + {ID: "GitHub.ClientID", Type: ConfigTypeString, DisplayName: "Client ID", Description: "", Value: cfg.GitHub.ClientID}, + {ID: "GitHub.ClientSecret", Type: ConfigTypeString, DisplayName: "Client Secret", Description: "", Value: cfg.GitHub.ClientSecret, Password: true}, + {ID: "GitHub.AllowedUsers", Type: ConfigTypeStringList, DisplayName: "Allowed Users", Description: "Allow any of the listed GitHub usernames to authenticate. Use '*' to allow any user.", Value: strings.Join(cfg.GitHub.AllowedUsers, "\n")}, + {ID: "GitHub.AllowedOrgs", Type: ConfigTypeStringList, DisplayName: "Allowed Orgs", Description: "Allow any member of any listed GitHub org (or team, using the format 'org/team') to authenticate.", Value: strings.Join(cfg.GitHub.AllowedOrgs, "\n")}, + {ID: "GitHub.EnterpriseURL", Type: ConfigTypeString, DisplayName: "Enterprise URL", Description: "GitHub URL (without /api) when used with GitHub Enterprise.", Value: cfg.GitHub.EnterpriseURL}, + {ID: "OIDC.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enable OpenID Connect authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.Enable)}, + {ID: "OIDC.NewUsers", Type: ConfigTypeBoolean, DisplayName: "New Users", Description: "Allow new user creation via OIDC authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.NewUsers)}, + {ID: "OIDC.OverrideName", Type: ConfigTypeString, DisplayName: "Override Name", Description: "Set the name/label on the login page to something other than OIDC.", Value: cfg.OIDC.OverrideName}, + {ID: "OIDC.IssuerURL", Type: ConfigTypeString, DisplayName: "Issuer URL", Description: "", Value: cfg.OIDC.IssuerURL}, + {ID: "OIDC.ClientID", Type: ConfigTypeString, DisplayName: "Client ID", Description: "", Value: cfg.OIDC.ClientID}, + {ID: "OIDC.ClientSecret", Type: ConfigTypeString, DisplayName: "Client Secret", Description: "", Value: cfg.OIDC.ClientSecret, Password: true}, + {ID: "OIDC.Scopes", Type: ConfigTypeString, DisplayName: "Scopes", Description: "Requested scopes for authentication. If left blank, openid, profile, and email will be used.", Value: cfg.OIDC.Scopes}, + {ID: "OIDC.UserInfoEmailPath", Type: ConfigTypeString, DisplayName: "User Info Email Path", Description: "JMESPath expression to find email address in UserInfo. If set, the email claim will be ignored in favor of this. (suggestion: email).", Value: cfg.OIDC.UserInfoEmailPath}, + {ID: "OIDC.UserInfoEmailVerifiedPath", Type: ConfigTypeString, DisplayName: "User Info Email Verified Path", Description: "JMESPath expression to find email verification state in UserInfo. If set, the email_verified claim will be ignored in favor of this. (suggestion: email_verified).", Value: cfg.OIDC.UserInfoEmailVerifiedPath}, + {ID: "OIDC.UserInfoNamePath", Type: ConfigTypeString, DisplayName: "User Info Name Path", Description: "JMESPath expression to find full name in UserInfo. If set, the name claim will be ignored in favor of this. (suggestion: name || cn || join(' ', [firstname, lastname]))", Value: cfg.OIDC.UserInfoNamePath}, + {ID: "Mailgun.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Mailgun.Enable)}, + {ID: "Mailgun.APIKey", Type: ConfigTypeString, DisplayName: "API Key", Description: "", Value: cfg.Mailgun.APIKey, Password: true}, + {ID: "Mailgun.EmailDomain", Type: ConfigTypeString, DisplayName: "Email Domain", Description: "The TO address for all incoming alerts.", Value: cfg.Mailgun.EmailDomain}, + {ID: "Slack.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Slack.Enable)}, + {ID: "Slack.ClientID", Type: ConfigTypeString, DisplayName: "Client ID", Description: "", Value: cfg.Slack.ClientID}, + {ID: "Slack.ClientSecret", Type: ConfigTypeString, DisplayName: "Client Secret", Description: "", Value: cfg.Slack.ClientSecret, Password: true}, + {ID: "Slack.AccessToken", Type: ConfigTypeString, DisplayName: "Access Token", Description: "Slack app bot user OAuth access token (should start with xoxb-).", Value: cfg.Slack.AccessToken, Password: true}, + {ID: "Twilio.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, + {ID: "Twilio.AccountSID", Type: ConfigTypeString, DisplayName: "Account SID", Description: "", Value: cfg.Twilio.AccountSID}, + {ID: "Twilio.AuthToken", Type: ConfigTypeString, DisplayName: "Auth Token", Description: "The primary Auth Token for Twilio. Must be primary (not secondary) for request valiation.", Value: cfg.Twilio.AuthToken, Password: true}, {ID: "Twilio.FromNumber", Type: ConfigTypeString, DisplayName: "From Number or Messenger SID", Description: "The Twilio number or Messenger SID to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, - {ID: "Twilio.DisableTwoWaySMS", Type: ConfigTypeBoolean, DisplayName: "Twilio.DisableTwoWaySMS", Description: "Disables SMS reply codes for alert messages.", Value: fmt.Sprintf("%t", cfg.Twilio.DisableTwoWaySMS)}, - {ID: "Twilio.SMSCarrierLookup", Type: ConfigTypeBoolean, DisplayName: "Twilio.SMSCarrierLookup", Description: "Perform carrier lookup of SMS contact methods (required for SMSFromNumberOverride). Extra charges may apply.", Value: fmt.Sprintf("%t", cfg.Twilio.SMSCarrierLookup)}, - {ID: "Twilio.SMSFromNumberOverride", Type: ConfigTypeStringList, DisplayName: "Twilio.SMSFromNumberOverride", Description: "List of 'carrier=number' pairs, SMS messages to numbers of the provided carrier string (exact match) will use the alternate From Number.", Value: strings.Join(cfg.Twilio.SMSFromNumberOverride, "\n")}, - {ID: "SMTP.Enable", Type: ConfigTypeBoolean, DisplayName: "SMTP.Enable", Description: "Enables email as a contact method.", Value: fmt.Sprintf("%t", cfg.SMTP.Enable)}, - {ID: "SMTP.From", Type: ConfigTypeString, DisplayName: "SMTP.From", Description: "The email address messages should be sent from.", Value: cfg.SMTP.From}, - {ID: "SMTP.Address", Type: ConfigTypeString, DisplayName: "SMTP.Address", Description: "The server address to use for sending email. Port is optional.", Value: cfg.SMTP.Address}, - {ID: "SMTP.DisableTLS", Type: ConfigTypeBoolean, DisplayName: "SMTP.DisableTLS", Description: "Disables TLS on the connection (STARTTLS will still be used if supported).", Value: fmt.Sprintf("%t", cfg.SMTP.DisableTLS)}, - {ID: "SMTP.SkipVerify", Type: ConfigTypeBoolean, DisplayName: "SMTP.SkipVerify", Description: "Disables certificate validation for TLS/STARTTLS (insecure).", Value: fmt.Sprintf("%t", cfg.SMTP.SkipVerify)}, - {ID: "SMTP.Username", Type: ConfigTypeString, DisplayName: "SMTP.Username", Description: "Username for authentication.", Value: cfg.SMTP.Username}, - {ID: "SMTP.Password", Type: ConfigTypeString, DisplayName: "SMTP.Password", Description: "Password for authentication.", Value: cfg.SMTP.Password, Password: true}, - {ID: "Webhook.Enable", Type: ConfigTypeBoolean, DisplayName: "Webhook.Enable", Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, - {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, DisplayName: "Webhook.AllowedURLs", Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, - {ID: "Feedback.Enable", Type: ConfigTypeBoolean, DisplayName: "Feedback.Enable", Description: "Enables Feedback link in nav bar.", Value: fmt.Sprintf("%t", cfg.Feedback.Enable)}, - {ID: "Feedback.OverrideURL", Type: ConfigTypeString, DisplayName: "Feedback.OverrideURL", Description: "Use a custom URL for Feedback link in nav bar.", Value: cfg.Feedback.OverrideURL}, + {ID: "Twilio.DisableTwoWaySMS", Type: ConfigTypeBoolean, DisplayName: "Disable Two Way SMS", Description: "Disables SMS reply codes for alert messages.", Value: fmt.Sprintf("%t", cfg.Twilio.DisableTwoWaySMS)}, + {ID: "Twilio.SMSCarrierLookup", Type: ConfigTypeBoolean, DisplayName: "SMS Carrier Lookup", Description: "Perform carrier lookup of SMS contact methods (required for SMSFromNumberOverride). Extra charges may apply.", Value: fmt.Sprintf("%t", cfg.Twilio.SMSCarrierLookup)}, + {ID: "Twilio.SMSFromNumberOverride", Type: ConfigTypeStringList, DisplayName: "SMS From Number Override", Description: "List of 'carrier=number' pairs, SMS messages to numbers of the provided carrier string (exact match) will use the alternate From Number.", Value: strings.Join(cfg.Twilio.SMSFromNumberOverride, "\n")}, + {ID: "SMTP.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables email as a contact method.", Value: fmt.Sprintf("%t", cfg.SMTP.Enable)}, + {ID: "SMTP.From", Type: ConfigTypeString, DisplayName: "From", Description: "The email address messages should be sent from.", Value: cfg.SMTP.From}, + {ID: "SMTP.Address", Type: ConfigTypeString, DisplayName: "Address", Description: "The server address to use for sending email. Port is optional.", Value: cfg.SMTP.Address}, + {ID: "SMTP.DisableTLS", Type: ConfigTypeBoolean, DisplayName: "Disable TLS", Description: "Disables TLS on the connection (STARTTLS will still be used if supported).", Value: fmt.Sprintf("%t", cfg.SMTP.DisableTLS)}, + {ID: "SMTP.SkipVerify", Type: ConfigTypeBoolean, DisplayName: "Skip Verify", Description: "Disables certificate validation for TLS/STARTTLS (insecure).", Value: fmt.Sprintf("%t", cfg.SMTP.SkipVerify)}, + {ID: "SMTP.Username", Type: ConfigTypeString, DisplayName: "Username", Description: "Username for authentication.", Value: cfg.SMTP.Username}, + {ID: "SMTP.Password", Type: ConfigTypeString, DisplayName: "Password", Description: "Password for authentication.", Value: cfg.SMTP.Password, Password: true}, + {ID: "Webhook.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, + {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, DisplayName: "Allowed UR Ls", Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, + {ID: "Feedback.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables Feedback link in nav bar.", Value: fmt.Sprintf("%t", cfg.Feedback.Enable)}, + {ID: "Feedback.OverrideURL", Type: ConfigTypeString, DisplayName: "Override URL", Description: "Use a custom URL for Feedback link in nav bar.", Value: cfg.Feedback.OverrideURL}, } } // MapPublicConfigValues will map a Config struct into a flat list of ConfigValue structs. func MapPublicConfigValues(cfg config.Config) []ConfigValue { return []ConfigValue{ - {ID: "General.ApplicationName", Type: ConfigTypeString, DisplayName: "General.ApplicationName", Description: "The name used in messaging and page titles. Defaults to \"GoAlert\".", Value: cfg.General.ApplicationName}, - {ID: "General.PublicURL", Type: ConfigTypeString, DisplayName: "General.PublicURL", Description: "Publicly routable URL for UI links and API calls.", Value: cfg.General.PublicURL}, - {ID: "General.GoogleAnalyticsID", Type: ConfigTypeString, DisplayName: "General.GoogleAnalyticsID", Description: "", Value: cfg.General.GoogleAnalyticsID}, - {ID: "General.NotificationDisclaimer", Type: ConfigTypeString, DisplayName: "General.NotificationDisclaimer", Description: "Disclaimer text for receiving pre-recorded notifications (appears on profile page).", Value: cfg.General.NotificationDisclaimer}, - {ID: "General.MessageBundles", Type: ConfigTypeBoolean, DisplayName: "General.MessageBundles", Description: "Enables bundling status updates and alert notifications. Also allows 'ack/close all' responses to bundled alerts.", Value: fmt.Sprintf("%t", cfg.General.MessageBundles)}, - {ID: "General.ShortURL", Type: ConfigTypeString, DisplayName: "General.ShortURL", Description: "If set, messages will contain a shorter URL using this as a prefix (e.g. http://example.com). It should point to GoAlert and can be the same as the PublicURL.", Value: cfg.General.ShortURL}, - {ID: "General.DisableSMSLinks", Type: ConfigTypeBoolean, DisplayName: "General.DisableSMSLinks", Description: "If set, SMS messages will not contain a URL pointing to GoAlert.", Value: fmt.Sprintf("%t", cfg.General.DisableSMSLinks)}, - {ID: "General.DisableLabelCreation", Type: ConfigTypeBoolean, DisplayName: "General.DisableLabelCreation", Description: "Disables the ability to create new labels for services.", Value: fmt.Sprintf("%t", cfg.General.DisableLabelCreation)}, - {ID: "General.DisableCalendarSubscriptions", Type: ConfigTypeBoolean, DisplayName: "General.DisableCalendarSubscriptions", Description: "If set, disables all active calendar subscriptions as well as the ability to create new calendar subscriptions.", Value: fmt.Sprintf("%t", cfg.General.DisableCalendarSubscriptions)}, - {ID: "Maintenance.AlertCleanupDays", Type: ConfigTypeInteger, DisplayName: "Maintenance.AlertCleanupDays", Description: "Closed alerts will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertCleanupDays)}, - {ID: "Maintenance.APIKeyExpireDays", Type: ConfigTypeInteger, DisplayName: "Maintenance.APIKeyExpireDays", Description: "Unused calendar API keys will be disabled after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.APIKeyExpireDays)}, - {ID: "Auth.DisableBasic", Type: ConfigTypeBoolean, DisplayName: "Auth.DisableBasic", Description: "Disallow username/password login.", Value: fmt.Sprintf("%t", cfg.Auth.DisableBasic)}, - {ID: "GitHub.Enable", Type: ConfigTypeBoolean, DisplayName: "GitHub.Enable", Description: "Enable GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.Enable)}, - {ID: "OIDC.Enable", Type: ConfigTypeBoolean, DisplayName: "OIDC.Enable", Description: "Enable OpenID Connect authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.Enable)}, - {ID: "Mailgun.Enable", Type: ConfigTypeBoolean, DisplayName: "Mailgun.Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Mailgun.Enable)}, - {ID: "Slack.Enable", Type: ConfigTypeBoolean, DisplayName: "Slack.Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Slack.Enable)}, - {ID: "Twilio.Enable", Type: ConfigTypeBoolean, DisplayName: "Twilio.Enable", Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, + {ID: "General.ApplicationName", Type: ConfigTypeString, DisplayName: "Application Name", Description: "The name used in messaging and page titles. Defaults to \"GoAlert\".", Value: cfg.General.ApplicationName}, + {ID: "General.PublicURL", Type: ConfigTypeString, DisplayName: "Public URL", Description: "Publicly routable URL for UI links and API calls.", Value: cfg.General.PublicURL}, + {ID: "General.GoogleAnalyticsID", Type: ConfigTypeString, DisplayName: "Google Analytics ID", Description: "", Value: cfg.General.GoogleAnalyticsID}, + {ID: "General.NotificationDisclaimer", Type: ConfigTypeString, DisplayName: "Notification Disclaimer", Description: "Disclaimer text for receiving pre-recorded notifications (appears on profile page).", Value: cfg.General.NotificationDisclaimer}, + {ID: "General.MessageBundles", Type: ConfigTypeBoolean, DisplayName: "Message Bundles", Description: "Enables bundling status updates and alert notifications. Also allows 'ack/close all' responses to bundled alerts.", Value: fmt.Sprintf("%t", cfg.General.MessageBundles)}, + {ID: "General.ShortURL", Type: ConfigTypeString, DisplayName: "Short URL", Description: "If set, messages will contain a shorter URL using this as a prefix (e.g. http://example.com). It should point to GoAlert and can be the same as the PublicURL.", Value: cfg.General.ShortURL}, + {ID: "General.DisableSMSLinks", Type: ConfigTypeBoolean, DisplayName: "Disable SMS Links", Description: "If set, SMS messages will not contain a URL pointing to GoAlert.", Value: fmt.Sprintf("%t", cfg.General.DisableSMSLinks)}, + {ID: "General.DisableLabelCreation", Type: ConfigTypeBoolean, DisplayName: "Disable Label Creation", Description: "Disables the ability to create new labels for services.", Value: fmt.Sprintf("%t", cfg.General.DisableLabelCreation)}, + {ID: "General.DisableCalendarSubscriptions", Type: ConfigTypeBoolean, DisplayName: "Disable Calendar Subscriptions", Description: "If set, disables all active calendar subscriptions as well as the ability to create new calendar subscriptions.", Value: fmt.Sprintf("%t", cfg.General.DisableCalendarSubscriptions)}, + {ID: "Maintenance.AlertCleanupDays", Type: ConfigTypeInteger, DisplayName: "Alert Cleanup Days", Description: "Closed alerts will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertCleanupDays)}, + {ID: "Maintenance.APIKeyExpireDays", Type: ConfigTypeInteger, DisplayName: "API Key Expire Days", Description: "Unused calendar API keys will be disabled after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.APIKeyExpireDays)}, + {ID: "Auth.DisableBasic", Type: ConfigTypeBoolean, DisplayName: "Disable Basic", Description: "Disallow username/password login.", Value: fmt.Sprintf("%t", cfg.Auth.DisableBasic)}, + {ID: "GitHub.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enable GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.Enable)}, + {ID: "OIDC.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enable OpenID Connect authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.Enable)}, + {ID: "Mailgun.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Mailgun.Enable)}, + {ID: "Slack.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Slack.Enable)}, + {ID: "Twilio.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, {ID: "Twilio.FromNumber", Type: ConfigTypeString, DisplayName: "From Number or Messenger SID", Description: "The Twilio number or Messenger SID to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, - {ID: "SMTP.Enable", Type: ConfigTypeBoolean, DisplayName: "SMTP.Enable", Description: "Enables email as a contact method.", Value: fmt.Sprintf("%t", cfg.SMTP.Enable)}, - {ID: "SMTP.From", Type: ConfigTypeString, DisplayName: "SMTP.From", Description: "The email address messages should be sent from.", Value: cfg.SMTP.From}, - {ID: "Webhook.Enable", Type: ConfigTypeBoolean, DisplayName: "Webhook.Enable", Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, - {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, DisplayName: "Webhook.AllowedURLs", Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, - {ID: "Feedback.Enable", Type: ConfigTypeBoolean, DisplayName: "Feedback.Enable", Description: "Enables Feedback link in nav bar.", Value: fmt.Sprintf("%t", cfg.Feedback.Enable)}, - {ID: "Feedback.OverrideURL", Type: ConfigTypeString, DisplayName: "Feedback.OverrideURL", Description: "Use a custom URL for Feedback link in nav bar.", Value: cfg.Feedback.OverrideURL}, + {ID: "SMTP.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables email as a contact method.", Value: fmt.Sprintf("%t", cfg.SMTP.Enable)}, + {ID: "SMTP.From", Type: ConfigTypeString, DisplayName: "From", Description: "The email address messages should be sent from.", Value: cfg.SMTP.From}, + {ID: "Webhook.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, + {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, DisplayName: "Allowed UR Ls", Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, + {ID: "Feedback.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables Feedback link in nav bar.", Value: fmt.Sprintf("%t", cfg.Feedback.Enable)}, + {ID: "Feedback.OverrideURL", Type: ConfigTypeString, DisplayName: "Override URL", Description: "Use a custom URL for Feedback link in nav bar.", Value: cfg.Feedback.OverrideURL}, } } diff --git a/graphql2/models_gen.go b/graphql2/models_gen.go index b06dd5d683..9178ea33c7 100644 --- a/graphql2/models_gen.go +++ b/graphql2/models_gen.go @@ -74,8 +74,9 @@ type ClearTemporarySchedulesInput struct { } type ConfigHint struct { - ID string `json:"id"` - Value string `json:"value"` + ID string `json:"id"` + DisplayName string `json:"displayName"` + Value string `json:"value"` } type ConfigValue struct { diff --git a/web/src/schema.d.ts b/web/src/schema.d.ts index 1e21162d4e..aca7335e4f 100644 --- a/web/src/schema.d.ts +++ b/web/src/schema.d.ts @@ -74,6 +74,7 @@ export interface ConfigValue { export interface ConfigHint { id: string + displayName: string value: string } From 4749dfcf0bae310a844661d6988152b588fa36c2 Mon Sep 17 00:00:00 2001 From: KatieMSB Date: Wed, 15 Sep 2021 11:10:33 -0500 Subject: [PATCH 14/32] run gofmt --- devtools/configparams/run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/configparams/run.go b/devtools/configparams/run.go index 397907d461..1e4ae50ad4 100644 --- a/devtools/configparams/run.go +++ b/devtools/configparams/run.go @@ -131,7 +131,7 @@ func ApplyConfigValues(cfg config.Config, vals []ConfigValueInput) (config.Confi type field struct { ID, DisplayName, Type, Desc, Value string - Public, Password bool + Public, Password bool } func main() { From b6096b7c0b5a781dbb8a4d18ebc9d482810242f9 Mon Sep 17 00:00:00 2001 From: dctalbot Date: Wed, 15 Sep 2021 11:10:45 -0500 Subject: [PATCH 15/32] fix: test debounced value for validation --- web/src/app/util/TelTextField.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/web/src/app/util/TelTextField.tsx b/web/src/app/util/TelTextField.tsx index bc822debfe..d1f7e7060b 100644 --- a/web/src/app/util/TelTextField.tsx +++ b/web/src/app/util/TelTextField.tsx @@ -43,18 +43,25 @@ export default function TelTextField(props: TelTextFieldProps): JSX.Element { return () => clearTimeout(t) }, [value]) + const reTel = /^\+\d+/ + const reSID = /^MG[a-zA-Z0-9]+/ const onlyTel = inputTypes.length === 1 && inputTypes[0] === 'tel' const onlySID = inputTypes.length === 1 && inputTypes[0] === 'sid' - const isSID = inputTypes.includes('sid') && value.match(/^MG[a-zA-Z0-9]+$/) + const isSID = (s: string): boolean => { + return inputTypes.includes('sid') && reSID.test(s) + } const skipValidation = (): boolean => { if (!debouncedValue || props.disabled || !inputTypes.includes('tel')) { return true } - if (onlyTel && !value.match(/(^\+)[0-9]+$/)) { + if (onlyTel && !reTel.test(debouncedValue)) { + return true + } + if (isSID(debouncedValue)) { return true } - if (onlySID || isSID) { + if (onlySID) { return true } return false @@ -71,7 +78,7 @@ export default function TelTextField(props: TelTextFieldProps): JSX.Element { const valid = Boolean(data?.phoneNumberInfo?.valid) let adorn - if (value === '' || isSID || props.disabled) { + if (value === '' || isSID(value) || props.disabled) { // no adornment } else if (valid) { adorn = From 583debcb366949190945b57c1de9687a8125126a Mon Sep 17 00:00:00 2001 From: KatieMSB Date: Wed, 15 Sep 2021 12:20:38 -0500 Subject: [PATCH 16/32] refine regex pattern and handle special case --- devtools/configparams/run.go | 10 ++++++++-- graphql2/mapconfig.go | 6 +++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/devtools/configparams/run.go b/devtools/configparams/run.go index 1e4ae50ad4..48a6ae518e 100644 --- a/devtools/configparams/run.go +++ b/devtools/configparams/run.go @@ -213,10 +213,16 @@ func printType(prefix string, v reflect.Type, displayName string, info string, p } func printDisplayName(key, displayName string) string { + // format reformats string with spaces, i.e. GoogleAnalyticsID -> Google Analytics ID format := func(str string) string { var formattedStr []string - pattern := regexp.MustCompile("(^[^A-Z]*|[A-Z]*)([A-Z][^A-Z]+|$)") + // pattern groups words in string by capital letters + pattern := regexp.MustCompile("([A-Z]*)([A-Z][^A-Z]+|$)") for _, subStr := range pattern.FindAllStringSubmatch(str, -1) { + if subStr[0] == "URLs" { + formattedStr = append(formattedStr, subStr[0]) + continue + } if subStr[1] != "" { formattedStr = append(formattedStr, subStr[1]) } @@ -224,7 +230,7 @@ func printDisplayName(key, displayName string) string { formattedStr = append(formattedStr, subStr[2]) } } - return strings.Title(strings.Join(formattedStr, " ")) + return strings.Join(formattedStr, " ") } if displayName == "" { diff --git a/graphql2/mapconfig.go b/graphql2/mapconfig.go index b6d807dabd..b17a7c53c8 100644 --- a/graphql2/mapconfig.go +++ b/graphql2/mapconfig.go @@ -36,7 +36,7 @@ func MapConfigValues(cfg config.Config) []ConfigValue { {ID: "General.EnableV1GraphQL", Type: ConfigTypeBoolean, DisplayName: "Enable V1 Graph QL", Description: "Enables the deprecated /v1/graphql endpoint (replaced by /api/graphql).", Value: fmt.Sprintf("%t", cfg.General.EnableV1GraphQL)}, {ID: "Maintenance.AlertCleanupDays", Type: ConfigTypeInteger, DisplayName: "Alert Cleanup Days", Description: "Closed alerts will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertCleanupDays)}, {ID: "Maintenance.APIKeyExpireDays", Type: ConfigTypeInteger, DisplayName: "API Key Expire Days", Description: "Unused calendar API keys will be disabled after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.APIKeyExpireDays)}, - {ID: "Auth.RefererURLs", Type: ConfigTypeStringList, DisplayName: "Referer UR Ls", Description: "Allowed referer URLs for auth and redirects.", Value: strings.Join(cfg.Auth.RefererURLs, "\n")}, + {ID: "Auth.RefererURLs", Type: ConfigTypeStringList, DisplayName: "Referer URLs", Description: "Allowed referer URLs for auth and redirects.", Value: strings.Join(cfg.Auth.RefererURLs, "\n")}, {ID: "Auth.DisableBasic", Type: ConfigTypeBoolean, DisplayName: "Disable Basic", Description: "Disallow username/password login.", Value: fmt.Sprintf("%t", cfg.Auth.DisableBasic)}, {ID: "GitHub.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enable GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.Enable)}, {ID: "GitHub.NewUsers", Type: ConfigTypeBoolean, DisplayName: "New Users", Description: "Allow new user creation via GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.NewUsers)}, @@ -77,7 +77,7 @@ func MapConfigValues(cfg config.Config) []ConfigValue { {ID: "SMTP.Username", Type: ConfigTypeString, DisplayName: "Username", Description: "Username for authentication.", Value: cfg.SMTP.Username}, {ID: "SMTP.Password", Type: ConfigTypeString, DisplayName: "Password", Description: "Password for authentication.", Value: cfg.SMTP.Password, Password: true}, {ID: "Webhook.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, - {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, DisplayName: "Allowed UR Ls", Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, + {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, DisplayName: "Allowed URLs", Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, {ID: "Feedback.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables Feedback link in nav bar.", Value: fmt.Sprintf("%t", cfg.Feedback.Enable)}, {ID: "Feedback.OverrideURL", Type: ConfigTypeString, DisplayName: "Override URL", Description: "Use a custom URL for Feedback link in nav bar.", Value: cfg.Feedback.OverrideURL}, } @@ -107,7 +107,7 @@ func MapPublicConfigValues(cfg config.Config) []ConfigValue { {ID: "SMTP.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables email as a contact method.", Value: fmt.Sprintf("%t", cfg.SMTP.Enable)}, {ID: "SMTP.From", Type: ConfigTypeString, DisplayName: "From", Description: "The email address messages should be sent from.", Value: cfg.SMTP.From}, {ID: "Webhook.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, - {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, DisplayName: "Allowed UR Ls", Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, + {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, DisplayName: "Allowed URLs", Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, {ID: "Feedback.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables Feedback link in nav bar.", Value: fmt.Sprintf("%t", cfg.Feedback.Enable)}, {ID: "Feedback.OverrideURL", Type: ConfigTypeString, DisplayName: "Override URL", Description: "Use a custom URL for Feedback link in nav bar.", Value: cfg.Feedback.OverrideURL}, } From 072528b4822fab542baf2e23c295dd0e412dc76c Mon Sep 17 00:00:00 2001 From: KatieMSB Date: Wed, 15 Sep 2021 13:59:23 -0500 Subject: [PATCH 17/32] messenger sid -> twilio messaging service sid --- config/config.go | 2 +- graphql2/mapconfig.go | 4 ++-- web/src/app/admin/AdminSMSSend.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/config.go b/config/config.go index 18c956168d..e8d1b9b380 100644 --- a/config/config.go +++ b/config/config.go @@ -94,7 +94,7 @@ type Config struct { AccountSID string AuthToken string `password:"true" info:"The primary Auth Token for Twilio. Must be primary (not secondary) for request valiation."` - FromNumber string `displayName:"From Number or Messenger SID" public:"true" info:"The Twilio number or Messenger SID to use for outgoing notifications."` + FromNumber string `displayName:"From Number or Twilio Messaging Service SID" public:"true" info:"The Twilio number or Messaging Service SID to use for outgoing notifications."` DisableTwoWaySMS bool `info:"Disables SMS reply codes for alert messages."` SMSCarrierLookup bool `info:"Perform carrier lookup of SMS contact methods (required for SMSFromNumberOverride). Extra charges may apply."` diff --git a/graphql2/mapconfig.go b/graphql2/mapconfig.go index b17a7c53c8..c167853a27 100644 --- a/graphql2/mapconfig.go +++ b/graphql2/mapconfig.go @@ -65,7 +65,7 @@ func MapConfigValues(cfg config.Config) []ConfigValue { {ID: "Twilio.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, {ID: "Twilio.AccountSID", Type: ConfigTypeString, DisplayName: "Account SID", Description: "", Value: cfg.Twilio.AccountSID}, {ID: "Twilio.AuthToken", Type: ConfigTypeString, DisplayName: "Auth Token", Description: "The primary Auth Token for Twilio. Must be primary (not secondary) for request valiation.", Value: cfg.Twilio.AuthToken, Password: true}, - {ID: "Twilio.FromNumber", Type: ConfigTypeString, DisplayName: "From Number or Messenger SID", Description: "The Twilio number or Messenger SID to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, + {ID: "Twilio.FromNumber", Type: ConfigTypeString, DisplayName: "From Number or Twilio Messaging Service SID", Description: "The Twilio number or Messaging Service SID to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, {ID: "Twilio.DisableTwoWaySMS", Type: ConfigTypeBoolean, DisplayName: "Disable Two Way SMS", Description: "Disables SMS reply codes for alert messages.", Value: fmt.Sprintf("%t", cfg.Twilio.DisableTwoWaySMS)}, {ID: "Twilio.SMSCarrierLookup", Type: ConfigTypeBoolean, DisplayName: "SMS Carrier Lookup", Description: "Perform carrier lookup of SMS contact methods (required for SMSFromNumberOverride). Extra charges may apply.", Value: fmt.Sprintf("%t", cfg.Twilio.SMSCarrierLookup)}, {ID: "Twilio.SMSFromNumberOverride", Type: ConfigTypeStringList, DisplayName: "SMS From Number Override", Description: "List of 'carrier=number' pairs, SMS messages to numbers of the provided carrier string (exact match) will use the alternate From Number.", Value: strings.Join(cfg.Twilio.SMSFromNumberOverride, "\n")}, @@ -103,7 +103,7 @@ func MapPublicConfigValues(cfg config.Config) []ConfigValue { {ID: "Mailgun.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Mailgun.Enable)}, {ID: "Slack.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Slack.Enable)}, {ID: "Twilio.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, - {ID: "Twilio.FromNumber", Type: ConfigTypeString, DisplayName: "From Number or Messenger SID", Description: "The Twilio number or Messenger SID to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, + {ID: "Twilio.FromNumber", Type: ConfigTypeString, DisplayName: "From Number or Twilio Messaging Service SID", Description: "The Twilio number or Messaging Service SID to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, {ID: "SMTP.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables email as a contact method.", Value: fmt.Sprintf("%t", cfg.SMTP.Enable)}, {ID: "SMTP.From", Type: ConfigTypeString, DisplayName: "From", Description: "The email address messages should be sent from.", Value: cfg.SMTP.From}, {ID: "Webhook.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, diff --git a/web/src/app/admin/AdminSMSSend.tsx b/web/src/app/admin/AdminSMSSend.tsx index b6c8265c0b..c1be27ab70 100644 --- a/web/src/app/admin/AdminSMSSend.tsx +++ b/web/src/app/admin/AdminSMSSend.tsx @@ -69,7 +69,7 @@ export default function AdminSMSSend(): JSX.Element { onChange={(e) => setFromNumber(e.target.value)} value={fromNumber} fullWidth - label='From Number or SID' + label='From Number or Twilio Messaging Service SID' inputTypes={['tel', 'sid']} /> From e2f8fe72a9ef7998b6ced9616f742634f1ccc234 Mon Sep 17 00:00:00 2001 From: KatieMSB Date: Thu, 16 Sep 2021 13:23:11 -0500 Subject: [PATCH 18/32] fix config validation --- config/config.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/config/config.go b/config/config.go index e8d1b9b380..8a91246f0f 100644 --- a/config/config.go +++ b/config/config.go @@ -402,7 +402,9 @@ func (cfg Config) Validate() error { err = validate.Many(err, validate.AbsoluteURL("GitHub.EnterpriseURL", cfg.GitHub.EnterpriseURL)) } if cfg.Twilio.FromNumber != "" { - err = validate.Many(err, validate.Phone("Twilio.FromNumber", cfg.Twilio.FromNumber)) + if validate.Phone("Twilio.FromNumber", cfg.Twilio.FromNumber) != nil && validate.SID("Twilio.FromNumber", cfg.Twilio.FromNumber) != nil { + err = validation.NewFieldError("Twilio.FromNumber", "is not a valid phone number or alphanumeric sender ID.") + } } if cfg.Mailgun.EmailDomain != "" { err = validate.Many(err, validate.Email("Mailgun.EmailDomain", "example@"+cfg.Mailgun.EmailDomain)) From 59d09269d2c6b2e1ba0af0a9e2b2684d1d9178ff Mon Sep 17 00:00:00 2001 From: KatieMSB Date: Thu, 16 Sep 2021 14:01:04 -0500 Subject: [PATCH 19/32] remove sid support for config From Number --- config/config.go | 6 ++---- graphql2/mapconfig.go | 4 ++-- web/src/app/admin/AdminFieldComponents.tsx | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/config/config.go b/config/config.go index 8a91246f0f..997410719a 100644 --- a/config/config.go +++ b/config/config.go @@ -94,7 +94,7 @@ type Config struct { AccountSID string AuthToken string `password:"true" info:"The primary Auth Token for Twilio. Must be primary (not secondary) for request valiation."` - FromNumber string `displayName:"From Number or Twilio Messaging Service SID" public:"true" info:"The Twilio number or Messaging Service SID to use for outgoing notifications."` + FromNumber string `public:"true" info:"The Twilio number to use for outgoing notifications."` DisableTwoWaySMS bool `info:"Disables SMS reply codes for alert messages."` SMSCarrierLookup bool `info:"Perform carrier lookup of SMS contact methods (required for SMSFromNumberOverride). Extra charges may apply."` @@ -402,9 +402,7 @@ func (cfg Config) Validate() error { err = validate.Many(err, validate.AbsoluteURL("GitHub.EnterpriseURL", cfg.GitHub.EnterpriseURL)) } if cfg.Twilio.FromNumber != "" { - if validate.Phone("Twilio.FromNumber", cfg.Twilio.FromNumber) != nil && validate.SID("Twilio.FromNumber", cfg.Twilio.FromNumber) != nil { - err = validation.NewFieldError("Twilio.FromNumber", "is not a valid phone number or alphanumeric sender ID.") - } + err = validate.Many(err, validate.Phone("Twilio.FromNumber", cfg.Twilio.FromNumber)) } if cfg.Mailgun.EmailDomain != "" { err = validate.Many(err, validate.Email("Mailgun.EmailDomain", "example@"+cfg.Mailgun.EmailDomain)) diff --git a/graphql2/mapconfig.go b/graphql2/mapconfig.go index c167853a27..d2dd5bcf56 100644 --- a/graphql2/mapconfig.go +++ b/graphql2/mapconfig.go @@ -65,7 +65,7 @@ func MapConfigValues(cfg config.Config) []ConfigValue { {ID: "Twilio.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, {ID: "Twilio.AccountSID", Type: ConfigTypeString, DisplayName: "Account SID", Description: "", Value: cfg.Twilio.AccountSID}, {ID: "Twilio.AuthToken", Type: ConfigTypeString, DisplayName: "Auth Token", Description: "The primary Auth Token for Twilio. Must be primary (not secondary) for request valiation.", Value: cfg.Twilio.AuthToken, Password: true}, - {ID: "Twilio.FromNumber", Type: ConfigTypeString, DisplayName: "From Number or Twilio Messaging Service SID", Description: "The Twilio number or Messaging Service SID to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, + {ID: "Twilio.FromNumber", Type: ConfigTypeString, DisplayName: "From Number", Description: "The Twilio number to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, {ID: "Twilio.DisableTwoWaySMS", Type: ConfigTypeBoolean, DisplayName: "Disable Two Way SMS", Description: "Disables SMS reply codes for alert messages.", Value: fmt.Sprintf("%t", cfg.Twilio.DisableTwoWaySMS)}, {ID: "Twilio.SMSCarrierLookup", Type: ConfigTypeBoolean, DisplayName: "SMS Carrier Lookup", Description: "Perform carrier lookup of SMS contact methods (required for SMSFromNumberOverride). Extra charges may apply.", Value: fmt.Sprintf("%t", cfg.Twilio.SMSCarrierLookup)}, {ID: "Twilio.SMSFromNumberOverride", Type: ConfigTypeStringList, DisplayName: "SMS From Number Override", Description: "List of 'carrier=number' pairs, SMS messages to numbers of the provided carrier string (exact match) will use the alternate From Number.", Value: strings.Join(cfg.Twilio.SMSFromNumberOverride, "\n")}, @@ -103,7 +103,7 @@ func MapPublicConfigValues(cfg config.Config) []ConfigValue { {ID: "Mailgun.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Mailgun.Enable)}, {ID: "Slack.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Slack.Enable)}, {ID: "Twilio.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, - {ID: "Twilio.FromNumber", Type: ConfigTypeString, DisplayName: "From Number or Twilio Messaging Service SID", Description: "The Twilio number or Messaging Service SID to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, + {ID: "Twilio.FromNumber", Type: ConfigTypeString, DisplayName: "From Number", Description: "The Twilio number to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, {ID: "SMTP.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables email as a contact method.", Value: fmt.Sprintf("%t", cfg.SMTP.Enable)}, {ID: "SMTP.From", Type: ConfigTypeString, DisplayName: "From", Description: "The email address messages should be sent from.", Value: cfg.SMTP.From}, {ID: "Webhook.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, diff --git a/web/src/app/admin/AdminFieldComponents.tsx b/web/src/app/admin/AdminFieldComponents.tsx index 6f103b8ef5..05882968c1 100644 --- a/web/src/app/admin/AdminFieldComponents.tsx +++ b/web/src/app/admin/AdminFieldComponents.tsx @@ -41,7 +41,7 @@ export function StringInput(props: InputProps): JSX.Element { onChange(e.target.value)} {...rest} - inputTypes={['tel', 'sid']} + inputTypes={['tel']} /> ) } From f916ccfd1881d6cb129a7740f21feecdd81bcee3 Mon Sep 17 00:00:00 2001 From: KatieMSB Date: Thu, 16 Sep 2021 15:44:21 -0500 Subject: [PATCH 20/32] undo changes to config display names --- devtools/configparams/run.go | 48 ++------ graphql2/generated.go | 102 +---------------- graphql2/mapconfig.go | 170 ++++++++++++++--------------- graphql2/models_gen.go | 6 +- graphql2/schema.graphql | 2 - web/src/app/admin/AdminConfig.tsx | 8 +- web/src/app/admin/AdminSection.tsx | 10 +- web/src/schema.d.ts | 2 - 8 files changed, 109 insertions(+), 239 deletions(-) diff --git a/devtools/configparams/run.go b/devtools/configparams/run.go index 48a6ae518e..e7a42d08cf 100644 --- a/devtools/configparams/run.go +++ b/devtools/configparams/run.go @@ -5,7 +5,6 @@ import ( "fmt" "os" "reflect" - "regexp" "strconv" "strings" "text/template" @@ -40,7 +39,7 @@ import ( func MapConfigHints(cfg config.Hints) []ConfigHint { return []ConfigHint{ {{- range .HintFields }} - {ID: {{quote .ID}}, DisplayName: {{quote .DisplayName}}, Value: {{.Value}}}, + {ID: {{quote .ID}}, Value: {{.Value}}}, {{- end}} } } @@ -49,7 +48,7 @@ func MapConfigHints(cfg config.Hints) []ConfigHint { func MapConfigValues(cfg config.Config) []ConfigValue { return []ConfigValue{ {{- range .ConfigFields }} - {ID: {{quote .ID}},Type: {{.Type}}, DisplayName: {{quote .DisplayName}}, Description: {{quote .Desc}}, Value: {{.Value}}{{if .Password}}, Password: true{{end}}}, + {ID: {{quote .ID}},Type: {{.Type}}, Description: {{quote .Desc}}, Value: {{.Value}}{{if .Password}}, Password: true{{end}}}, {{- end}} } } @@ -59,7 +58,7 @@ func MapPublicConfigValues(cfg config.Config) []ConfigValue { return []ConfigValue{ {{- range .ConfigFields }} {{- if .Public}} - {ID: {{quote .ID}}, Type: {{.Type}}, DisplayName: {{quote .DisplayName}}, Description: {{quote .Desc}}, Value: {{.Value}}{{if .Password}}, Password: true{{end}}}, + {ID: {{quote .ID}}, Type: {{.Type}}, Description: {{quote .Desc}}, Value: {{.Value}}{{if .Password}}, Password: true{{end}}}, {{- end}} {{- end}} } @@ -130,7 +129,7 @@ func ApplyConfigValues(cfg config.Config, vals []ConfigValueInput) (config.Confi `)) type field struct { - ID, DisplayName, Type, Desc, Value string + ID, Type, Desc, Value string Public, Password bool } @@ -156,8 +155,8 @@ package graphql2`) ConfigFields []field HintFields []field } - input.ConfigFields = printType("", reflect.TypeOf(config.Config{}), "", "", false, false) - input.HintFields = printType("", reflect.TypeOf(config.Hints{}), "", "", false, false) + input.ConfigFields = printType("", reflect.TypeOf(config.Config{}), "", false, false) + input.HintFields = printType("", reflect.TypeOf(config.Hints{}), "", false, false) err := tmpl.Execute(w, input) if err != nil { @@ -170,9 +169,9 @@ func printField(prefix string, f reflect.StructField) []field { if f.Type.Kind() == reflect.Slice && f.Type.Elem().Kind() == reflect.Struct { fPrefix = prefix + f.Name + "[]." } - return printType(fPrefix, f.Type, f.Tag.Get("displayName"), f.Tag.Get("info"), f.Tag.Get("public") == "true", f.Tag.Get("password") == "true") + return printType(fPrefix, f.Type, f.Tag.Get("info"), f.Tag.Get("public") == "true", f.Tag.Get("password") == "true") } -func printType(prefix string, v reflect.Type, displayName string, info string, public, pass bool) []field { +func printType(prefix string, v reflect.Type, details string, public, pass bool) []field { var f []field key := strings.TrimSuffix(prefix, ".") @@ -208,35 +207,6 @@ func printType(prefix string, v reflect.Type, displayName string, info string, p panic(fmt.Sprintf("not implemented for type %T", v.Kind())) } - f = append(f, field{ID: key, Type: typ, DisplayName: printDisplayName(key, displayName), Desc: info, Value: value, Public: public, Password: pass}) + f = append(f, field{ID: key, Type: typ, Desc: details, Value: value, Public: public, Password: pass}) return f } - -func printDisplayName(key, displayName string) string { - // format reformats string with spaces, i.e. GoogleAnalyticsID -> Google Analytics ID - format := func(str string) string { - var formattedStr []string - // pattern groups words in string by capital letters - pattern := regexp.MustCompile("([A-Z]*)([A-Z][^A-Z]+|$)") - for _, subStr := range pattern.FindAllStringSubmatch(str, -1) { - if subStr[0] == "URLs" { - formattedStr = append(formattedStr, subStr[0]) - continue - } - if subStr[1] != "" { - formattedStr = append(formattedStr, subStr[1]) - } - if subStr[2] != "" { - formattedStr = append(formattedStr, subStr[2]) - } - } - return strings.Join(formattedStr, " ") - } - - if displayName == "" { - fieldKey := strings.Split(key, ".") - displayName = format(fieldKey[len(fieldKey)-1]) - } - - return displayName -} diff --git a/graphql2/generated.go b/graphql2/generated.go index 3b42b9cc03..dd4c6dc8e9 100644 --- a/graphql2/generated.go +++ b/graphql2/generated.go @@ -134,14 +134,12 @@ type ComplexityRoot struct { } ConfigHint struct { - DisplayName func(childComplexity int) int - ID func(childComplexity int) int - Value func(childComplexity int) int + ID func(childComplexity int) int + Value func(childComplexity int) int } ConfigValue struct { Description func(childComplexity int) int - DisplayName func(childComplexity int) int ID func(childComplexity int) int Password func(childComplexity int) int Type func(childComplexity int) int @@ -922,13 +920,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.AuthSubjectConnection.PageInfo(childComplexity), true - case "ConfigHint.displayName": - if e.complexity.ConfigHint.DisplayName == nil { - break - } - - return e.complexity.ConfigHint.DisplayName(childComplexity), true - case "ConfigHint.id": if e.complexity.ConfigHint.ID == nil { break @@ -950,13 +941,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ConfigValue.Description(childComplexity), true - case "ConfigValue.displayName": - if e.complexity.ConfigValue.DisplayName == nil { - break - } - - return e.complexity.ConfigValue.DisplayName(childComplexity), true - case "ConfigValue.id": if e.complexity.ConfigValue.ID == nil { break @@ -3330,7 +3314,6 @@ input SystemLimitInput { type ConfigValue { id: String! - displayName: String! description: String! value: String! type: ConfigType! @@ -3338,7 +3321,6 @@ type ConfigValue { } type ConfigHint { id: String! - displayName: String! value: String! } enum ConfigType { @@ -6519,41 +6501,6 @@ func (ec *executionContext) _ConfigHint_id(ctx context.Context, field graphql.Co return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _ConfigHint_displayName(ctx context.Context, field graphql.CollectedField, obj *ConfigHint) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "ConfigHint", - Field: field, - Args: nil, - IsMethod: false, - IsResolver: false, - } - - ctx = graphql.WithFieldContext(ctx, fc) - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.DisplayName, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(string) - fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) -} - func (ec *executionContext) _ConfigHint_value(ctx context.Context, field graphql.CollectedField, obj *ConfigHint) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -6624,41 +6571,6 @@ func (ec *executionContext) _ConfigValue_id(ctx context.Context, field graphql.C return ec.marshalNString2string(ctx, field.Selections, res) } -func (ec *executionContext) _ConfigValue_displayName(ctx context.Context, field graphql.CollectedField, obj *ConfigValue) (ret graphql.Marshaler) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - fc := &graphql.FieldContext{ - Object: "ConfigValue", - Field: field, - Args: nil, - IsMethod: false, - IsResolver: false, - } - - ctx = graphql.WithFieldContext(ctx, fc) - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - return obj.DisplayName, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.(string) - fc.Result = res - return ec.marshalNString2string(ctx, field.Selections, res) -} - func (ec *executionContext) _ConfigValue_description(ctx context.Context, field graphql.CollectedField, obj *ConfigValue) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -20215,11 +20127,6 @@ func (ec *executionContext) _ConfigHint(ctx context.Context, sel ast.SelectionSe if out.Values[i] == graphql.Null { invalids++ } - case "displayName": - out.Values[i] = ec._ConfigHint_displayName(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } case "value": out.Values[i] = ec._ConfigHint_value(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -20252,11 +20159,6 @@ func (ec *executionContext) _ConfigValue(ctx context.Context, sel ast.SelectionS if out.Values[i] == graphql.Null { invalids++ } - case "displayName": - out.Values[i] = ec._ConfigValue_displayName(ctx, field, obj) - if out.Values[i] == graphql.Null { - invalids++ - } case "description": out.Values[i] = ec._ConfigValue_description(ctx, field, obj) if out.Values[i] == graphql.Null { diff --git a/graphql2/mapconfig.go b/graphql2/mapconfig.go index d2dd5bcf56..8222f6d79e 100644 --- a/graphql2/mapconfig.go +++ b/graphql2/mapconfig.go @@ -13,103 +13,103 @@ import ( func MapConfigHints(cfg config.Hints) []ConfigHint { return []ConfigHint{ - {ID: "GitHub.AuthCallbackURL", DisplayName: "Auth Callback URL", Value: cfg.GitHub.AuthCallbackURL}, - {ID: "OIDC.RedirectURL", DisplayName: "Redirect URL", Value: cfg.OIDC.RedirectURL}, - {ID: "Mailgun.ForwardURL", DisplayName: "Forward URL", Value: cfg.Mailgun.ForwardURL}, - {ID: "Twilio.MessageWebhookURL", DisplayName: "Message Webhook URL", Value: cfg.Twilio.MessageWebhookURL}, - {ID: "Twilio.VoiceWebhookURL", DisplayName: "Voice Webhook URL", Value: cfg.Twilio.VoiceWebhookURL}, + {ID: "GitHub.AuthCallbackURL", Value: cfg.GitHub.AuthCallbackURL}, + {ID: "OIDC.RedirectURL", Value: cfg.OIDC.RedirectURL}, + {ID: "Mailgun.ForwardURL", Value: cfg.Mailgun.ForwardURL}, + {ID: "Twilio.MessageWebhookURL", Value: cfg.Twilio.MessageWebhookURL}, + {ID: "Twilio.VoiceWebhookURL", Value: cfg.Twilio.VoiceWebhookURL}, } } // MapConfigValues will map a Config struct into a flat list of ConfigValue structs. func MapConfigValues(cfg config.Config) []ConfigValue { return []ConfigValue{ - {ID: "General.ApplicationName", Type: ConfigTypeString, DisplayName: "Application Name", Description: "The name used in messaging and page titles. Defaults to \"GoAlert\".", Value: cfg.General.ApplicationName}, - {ID: "General.PublicURL", Type: ConfigTypeString, DisplayName: "Public URL", Description: "Publicly routable URL for UI links and API calls.", Value: cfg.General.PublicURL}, - {ID: "General.GoogleAnalyticsID", Type: ConfigTypeString, DisplayName: "Google Analytics ID", Description: "", Value: cfg.General.GoogleAnalyticsID}, - {ID: "General.NotificationDisclaimer", Type: ConfigTypeString, DisplayName: "Notification Disclaimer", Description: "Disclaimer text for receiving pre-recorded notifications (appears on profile page).", Value: cfg.General.NotificationDisclaimer}, - {ID: "General.MessageBundles", Type: ConfigTypeBoolean, DisplayName: "Message Bundles", Description: "Enables bundling status updates and alert notifications. Also allows 'ack/close all' responses to bundled alerts.", Value: fmt.Sprintf("%t", cfg.General.MessageBundles)}, - {ID: "General.ShortURL", Type: ConfigTypeString, DisplayName: "Short URL", Description: "If set, messages will contain a shorter URL using this as a prefix (e.g. http://example.com). It should point to GoAlert and can be the same as the PublicURL.", Value: cfg.General.ShortURL}, - {ID: "General.DisableSMSLinks", Type: ConfigTypeBoolean, DisplayName: "Disable SMS Links", Description: "If set, SMS messages will not contain a URL pointing to GoAlert.", Value: fmt.Sprintf("%t", cfg.General.DisableSMSLinks)}, - {ID: "General.DisableLabelCreation", Type: ConfigTypeBoolean, DisplayName: "Disable Label Creation", Description: "Disables the ability to create new labels for services.", Value: fmt.Sprintf("%t", cfg.General.DisableLabelCreation)}, - {ID: "General.DisableCalendarSubscriptions", Type: ConfigTypeBoolean, DisplayName: "Disable Calendar Subscriptions", Description: "If set, disables all active calendar subscriptions as well as the ability to create new calendar subscriptions.", Value: fmt.Sprintf("%t", cfg.General.DisableCalendarSubscriptions)}, - {ID: "General.EnableV1GraphQL", Type: ConfigTypeBoolean, DisplayName: "Enable V1 Graph QL", Description: "Enables the deprecated /v1/graphql endpoint (replaced by /api/graphql).", Value: fmt.Sprintf("%t", cfg.General.EnableV1GraphQL)}, - {ID: "Maintenance.AlertCleanupDays", Type: ConfigTypeInteger, DisplayName: "Alert Cleanup Days", Description: "Closed alerts will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertCleanupDays)}, - {ID: "Maintenance.APIKeyExpireDays", Type: ConfigTypeInteger, DisplayName: "API Key Expire Days", Description: "Unused calendar API keys will be disabled after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.APIKeyExpireDays)}, - {ID: "Auth.RefererURLs", Type: ConfigTypeStringList, DisplayName: "Referer URLs", Description: "Allowed referer URLs for auth and redirects.", Value: strings.Join(cfg.Auth.RefererURLs, "\n")}, - {ID: "Auth.DisableBasic", Type: ConfigTypeBoolean, DisplayName: "Disable Basic", Description: "Disallow username/password login.", Value: fmt.Sprintf("%t", cfg.Auth.DisableBasic)}, - {ID: "GitHub.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enable GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.Enable)}, - {ID: "GitHub.NewUsers", Type: ConfigTypeBoolean, DisplayName: "New Users", Description: "Allow new user creation via GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.NewUsers)}, - {ID: "GitHub.ClientID", Type: ConfigTypeString, DisplayName: "Client ID", Description: "", Value: cfg.GitHub.ClientID}, - {ID: "GitHub.ClientSecret", Type: ConfigTypeString, DisplayName: "Client Secret", Description: "", Value: cfg.GitHub.ClientSecret, Password: true}, - {ID: "GitHub.AllowedUsers", Type: ConfigTypeStringList, DisplayName: "Allowed Users", Description: "Allow any of the listed GitHub usernames to authenticate. Use '*' to allow any user.", Value: strings.Join(cfg.GitHub.AllowedUsers, "\n")}, - {ID: "GitHub.AllowedOrgs", Type: ConfigTypeStringList, DisplayName: "Allowed Orgs", Description: "Allow any member of any listed GitHub org (or team, using the format 'org/team') to authenticate.", Value: strings.Join(cfg.GitHub.AllowedOrgs, "\n")}, - {ID: "GitHub.EnterpriseURL", Type: ConfigTypeString, DisplayName: "Enterprise URL", Description: "GitHub URL (without /api) when used with GitHub Enterprise.", Value: cfg.GitHub.EnterpriseURL}, - {ID: "OIDC.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enable OpenID Connect authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.Enable)}, - {ID: "OIDC.NewUsers", Type: ConfigTypeBoolean, DisplayName: "New Users", Description: "Allow new user creation via OIDC authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.NewUsers)}, - {ID: "OIDC.OverrideName", Type: ConfigTypeString, DisplayName: "Override Name", Description: "Set the name/label on the login page to something other than OIDC.", Value: cfg.OIDC.OverrideName}, - {ID: "OIDC.IssuerURL", Type: ConfigTypeString, DisplayName: "Issuer URL", Description: "", Value: cfg.OIDC.IssuerURL}, - {ID: "OIDC.ClientID", Type: ConfigTypeString, DisplayName: "Client ID", Description: "", Value: cfg.OIDC.ClientID}, - {ID: "OIDC.ClientSecret", Type: ConfigTypeString, DisplayName: "Client Secret", Description: "", Value: cfg.OIDC.ClientSecret, Password: true}, - {ID: "OIDC.Scopes", Type: ConfigTypeString, DisplayName: "Scopes", Description: "Requested scopes for authentication. If left blank, openid, profile, and email will be used.", Value: cfg.OIDC.Scopes}, - {ID: "OIDC.UserInfoEmailPath", Type: ConfigTypeString, DisplayName: "User Info Email Path", Description: "JMESPath expression to find email address in UserInfo. If set, the email claim will be ignored in favor of this. (suggestion: email).", Value: cfg.OIDC.UserInfoEmailPath}, - {ID: "OIDC.UserInfoEmailVerifiedPath", Type: ConfigTypeString, DisplayName: "User Info Email Verified Path", Description: "JMESPath expression to find email verification state in UserInfo. If set, the email_verified claim will be ignored in favor of this. (suggestion: email_verified).", Value: cfg.OIDC.UserInfoEmailVerifiedPath}, - {ID: "OIDC.UserInfoNamePath", Type: ConfigTypeString, DisplayName: "User Info Name Path", Description: "JMESPath expression to find full name in UserInfo. If set, the name claim will be ignored in favor of this. (suggestion: name || cn || join(' ', [firstname, lastname]))", Value: cfg.OIDC.UserInfoNamePath}, - {ID: "Mailgun.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Mailgun.Enable)}, - {ID: "Mailgun.APIKey", Type: ConfigTypeString, DisplayName: "API Key", Description: "", Value: cfg.Mailgun.APIKey, Password: true}, - {ID: "Mailgun.EmailDomain", Type: ConfigTypeString, DisplayName: "Email Domain", Description: "The TO address for all incoming alerts.", Value: cfg.Mailgun.EmailDomain}, - {ID: "Slack.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Slack.Enable)}, - {ID: "Slack.ClientID", Type: ConfigTypeString, DisplayName: "Client ID", Description: "", Value: cfg.Slack.ClientID}, - {ID: "Slack.ClientSecret", Type: ConfigTypeString, DisplayName: "Client Secret", Description: "", Value: cfg.Slack.ClientSecret, Password: true}, - {ID: "Slack.AccessToken", Type: ConfigTypeString, DisplayName: "Access Token", Description: "Slack app bot user OAuth access token (should start with xoxb-).", Value: cfg.Slack.AccessToken, Password: true}, - {ID: "Twilio.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, - {ID: "Twilio.AccountSID", Type: ConfigTypeString, DisplayName: "Account SID", Description: "", Value: cfg.Twilio.AccountSID}, - {ID: "Twilio.AuthToken", Type: ConfigTypeString, DisplayName: "Auth Token", Description: "The primary Auth Token for Twilio. Must be primary (not secondary) for request valiation.", Value: cfg.Twilio.AuthToken, Password: true}, - {ID: "Twilio.FromNumber", Type: ConfigTypeString, DisplayName: "From Number", Description: "The Twilio number to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, - {ID: "Twilio.DisableTwoWaySMS", Type: ConfigTypeBoolean, DisplayName: "Disable Two Way SMS", Description: "Disables SMS reply codes for alert messages.", Value: fmt.Sprintf("%t", cfg.Twilio.DisableTwoWaySMS)}, - {ID: "Twilio.SMSCarrierLookup", Type: ConfigTypeBoolean, DisplayName: "SMS Carrier Lookup", Description: "Perform carrier lookup of SMS contact methods (required for SMSFromNumberOverride). Extra charges may apply.", Value: fmt.Sprintf("%t", cfg.Twilio.SMSCarrierLookup)}, - {ID: "Twilio.SMSFromNumberOverride", Type: ConfigTypeStringList, DisplayName: "SMS From Number Override", Description: "List of 'carrier=number' pairs, SMS messages to numbers of the provided carrier string (exact match) will use the alternate From Number.", Value: strings.Join(cfg.Twilio.SMSFromNumberOverride, "\n")}, - {ID: "SMTP.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables email as a contact method.", Value: fmt.Sprintf("%t", cfg.SMTP.Enable)}, - {ID: "SMTP.From", Type: ConfigTypeString, DisplayName: "From", Description: "The email address messages should be sent from.", Value: cfg.SMTP.From}, - {ID: "SMTP.Address", Type: ConfigTypeString, DisplayName: "Address", Description: "The server address to use for sending email. Port is optional.", Value: cfg.SMTP.Address}, - {ID: "SMTP.DisableTLS", Type: ConfigTypeBoolean, DisplayName: "Disable TLS", Description: "Disables TLS on the connection (STARTTLS will still be used if supported).", Value: fmt.Sprintf("%t", cfg.SMTP.DisableTLS)}, - {ID: "SMTP.SkipVerify", Type: ConfigTypeBoolean, DisplayName: "Skip Verify", Description: "Disables certificate validation for TLS/STARTTLS (insecure).", Value: fmt.Sprintf("%t", cfg.SMTP.SkipVerify)}, - {ID: "SMTP.Username", Type: ConfigTypeString, DisplayName: "Username", Description: "Username for authentication.", Value: cfg.SMTP.Username}, - {ID: "SMTP.Password", Type: ConfigTypeString, DisplayName: "Password", Description: "Password for authentication.", Value: cfg.SMTP.Password, Password: true}, - {ID: "Webhook.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, - {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, DisplayName: "Allowed URLs", Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, - {ID: "Feedback.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables Feedback link in nav bar.", Value: fmt.Sprintf("%t", cfg.Feedback.Enable)}, - {ID: "Feedback.OverrideURL", Type: ConfigTypeString, DisplayName: "Override URL", Description: "Use a custom URL for Feedback link in nav bar.", Value: cfg.Feedback.OverrideURL}, + {ID: "General.ApplicationName", Type: ConfigTypeString, Description: "The name used in messaging and page titles. Defaults to \"GoAlert\".", Value: cfg.General.ApplicationName}, + {ID: "General.PublicURL", Type: ConfigTypeString, Description: "Publicly routable URL for UI links and API calls.", Value: cfg.General.PublicURL}, + {ID: "General.GoogleAnalyticsID", Type: ConfigTypeString, Description: "", Value: cfg.General.GoogleAnalyticsID}, + {ID: "General.NotificationDisclaimer", Type: ConfigTypeString, Description: "Disclaimer text for receiving pre-recorded notifications (appears on profile page).", Value: cfg.General.NotificationDisclaimer}, + {ID: "General.MessageBundles", Type: ConfigTypeBoolean, Description: "Enables bundling status updates and alert notifications. Also allows 'ack/close all' responses to bundled alerts.", Value: fmt.Sprintf("%t", cfg.General.MessageBundles)}, + {ID: "General.ShortURL", Type: ConfigTypeString, Description: "If set, messages will contain a shorter URL using this as a prefix (e.g. http://example.com). It should point to GoAlert and can be the same as the PublicURL.", Value: cfg.General.ShortURL}, + {ID: "General.DisableSMSLinks", Type: ConfigTypeBoolean, Description: "If set, SMS messages will not contain a URL pointing to GoAlert.", Value: fmt.Sprintf("%t", cfg.General.DisableSMSLinks)}, + {ID: "General.DisableLabelCreation", Type: ConfigTypeBoolean, Description: "Disables the ability to create new labels for services.", Value: fmt.Sprintf("%t", cfg.General.DisableLabelCreation)}, + {ID: "General.DisableCalendarSubscriptions", Type: ConfigTypeBoolean, Description: "If set, disables all active calendar subscriptions as well as the ability to create new calendar subscriptions.", Value: fmt.Sprintf("%t", cfg.General.DisableCalendarSubscriptions)}, + {ID: "General.EnableV1GraphQL", Type: ConfigTypeBoolean, Description: "Enables the deprecated /v1/graphql endpoint (replaced by /api/graphql).", Value: fmt.Sprintf("%t", cfg.General.EnableV1GraphQL)}, + {ID: "Maintenance.AlertCleanupDays", Type: ConfigTypeInteger, Description: "Closed alerts will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertCleanupDays)}, + {ID: "Maintenance.APIKeyExpireDays", Type: ConfigTypeInteger, Description: "Unused calendar API keys will be disabled after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.APIKeyExpireDays)}, + {ID: "Auth.RefererURLs", Type: ConfigTypeStringList, Description: "Allowed referer URLs for auth and redirects.", Value: strings.Join(cfg.Auth.RefererURLs, "\n")}, + {ID: "Auth.DisableBasic", Type: ConfigTypeBoolean, Description: "Disallow username/password login.", Value: fmt.Sprintf("%t", cfg.Auth.DisableBasic)}, + {ID: "GitHub.Enable", Type: ConfigTypeBoolean, Description: "Enable GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.Enable)}, + {ID: "GitHub.NewUsers", Type: ConfigTypeBoolean, Description: "Allow new user creation via GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.NewUsers)}, + {ID: "GitHub.ClientID", Type: ConfigTypeString, Description: "", Value: cfg.GitHub.ClientID}, + {ID: "GitHub.ClientSecret", Type: ConfigTypeString, Description: "", Value: cfg.GitHub.ClientSecret, Password: true}, + {ID: "GitHub.AllowedUsers", Type: ConfigTypeStringList, Description: "Allow any of the listed GitHub usernames to authenticate. Use '*' to allow any user.", Value: strings.Join(cfg.GitHub.AllowedUsers, "\n")}, + {ID: "GitHub.AllowedOrgs", Type: ConfigTypeStringList, Description: "Allow any member of any listed GitHub org (or team, using the format 'org/team') to authenticate.", Value: strings.Join(cfg.GitHub.AllowedOrgs, "\n")}, + {ID: "GitHub.EnterpriseURL", Type: ConfigTypeString, Description: "GitHub URL (without /api) when used with GitHub Enterprise.", Value: cfg.GitHub.EnterpriseURL}, + {ID: "OIDC.Enable", Type: ConfigTypeBoolean, Description: "Enable OpenID Connect authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.Enable)}, + {ID: "OIDC.NewUsers", Type: ConfigTypeBoolean, Description: "Allow new user creation via OIDC authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.NewUsers)}, + {ID: "OIDC.OverrideName", Type: ConfigTypeString, Description: "Set the name/label on the login page to something other than OIDC.", Value: cfg.OIDC.OverrideName}, + {ID: "OIDC.IssuerURL", Type: ConfigTypeString, Description: "", Value: cfg.OIDC.IssuerURL}, + {ID: "OIDC.ClientID", Type: ConfigTypeString, Description: "", Value: cfg.OIDC.ClientID}, + {ID: "OIDC.ClientSecret", Type: ConfigTypeString, Description: "", Value: cfg.OIDC.ClientSecret, Password: true}, + {ID: "OIDC.Scopes", Type: ConfigTypeString, Description: "Requested scopes for authentication. If left blank, openid, profile, and email will be used.", Value: cfg.OIDC.Scopes}, + {ID: "OIDC.UserInfoEmailPath", Type: ConfigTypeString, Description: "JMESPath expression to find email address in UserInfo. If set, the email claim will be ignored in favor of this. (suggestion: email).", Value: cfg.OIDC.UserInfoEmailPath}, + {ID: "OIDC.UserInfoEmailVerifiedPath", Type: ConfigTypeString, Description: "JMESPath expression to find email verification state in UserInfo. If set, the email_verified claim will be ignored in favor of this. (suggestion: email_verified).", Value: cfg.OIDC.UserInfoEmailVerifiedPath}, + {ID: "OIDC.UserInfoNamePath", Type: ConfigTypeString, Description: "JMESPath expression to find full name in UserInfo. If set, the name claim will be ignored in favor of this. (suggestion: name || cn || join(' ', [firstname, lastname]))", Value: cfg.OIDC.UserInfoNamePath}, + {ID: "Mailgun.Enable", Type: ConfigTypeBoolean, Description: "", Value: fmt.Sprintf("%t", cfg.Mailgun.Enable)}, + {ID: "Mailgun.APIKey", Type: ConfigTypeString, Description: "", Value: cfg.Mailgun.APIKey, Password: true}, + {ID: "Mailgun.EmailDomain", Type: ConfigTypeString, Description: "The TO address for all incoming alerts.", Value: cfg.Mailgun.EmailDomain}, + {ID: "Slack.Enable", Type: ConfigTypeBoolean, Description: "", Value: fmt.Sprintf("%t", cfg.Slack.Enable)}, + {ID: "Slack.ClientID", Type: ConfigTypeString, Description: "", Value: cfg.Slack.ClientID}, + {ID: "Slack.ClientSecret", Type: ConfigTypeString, Description: "", Value: cfg.Slack.ClientSecret, Password: true}, + {ID: "Slack.AccessToken", Type: ConfigTypeString, Description: "Slack app bot user OAuth access token (should start with xoxb-).", Value: cfg.Slack.AccessToken, Password: true}, + {ID: "Twilio.Enable", Type: ConfigTypeBoolean, Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, + {ID: "Twilio.AccountSID", Type: ConfigTypeString, Description: "", Value: cfg.Twilio.AccountSID}, + {ID: "Twilio.AuthToken", Type: ConfigTypeString, Description: "The primary Auth Token for Twilio. Must be primary (not secondary) for request valiation.", Value: cfg.Twilio.AuthToken, Password: true}, + {ID: "Twilio.FromNumber", Type: ConfigTypeString, Description: "The Twilio number to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, + {ID: "Twilio.DisableTwoWaySMS", Type: ConfigTypeBoolean, Description: "Disables SMS reply codes for alert messages.", Value: fmt.Sprintf("%t", cfg.Twilio.DisableTwoWaySMS)}, + {ID: "Twilio.SMSCarrierLookup", Type: ConfigTypeBoolean, Description: "Perform carrier lookup of SMS contact methods (required for SMSFromNumberOverride). Extra charges may apply.", Value: fmt.Sprintf("%t", cfg.Twilio.SMSCarrierLookup)}, + {ID: "Twilio.SMSFromNumberOverride", Type: ConfigTypeStringList, Description: "List of 'carrier=number' pairs, SMS messages to numbers of the provided carrier string (exact match) will use the alternate From Number.", Value: strings.Join(cfg.Twilio.SMSFromNumberOverride, "\n")}, + {ID: "SMTP.Enable", Type: ConfigTypeBoolean, Description: "Enables email as a contact method.", Value: fmt.Sprintf("%t", cfg.SMTP.Enable)}, + {ID: "SMTP.From", Type: ConfigTypeString, Description: "The email address messages should be sent from.", Value: cfg.SMTP.From}, + {ID: "SMTP.Address", Type: ConfigTypeString, Description: "The server address to use for sending email. Port is optional.", Value: cfg.SMTP.Address}, + {ID: "SMTP.DisableTLS", Type: ConfigTypeBoolean, Description: "Disables TLS on the connection (STARTTLS will still be used if supported).", Value: fmt.Sprintf("%t", cfg.SMTP.DisableTLS)}, + {ID: "SMTP.SkipVerify", Type: ConfigTypeBoolean, Description: "Disables certificate validation for TLS/STARTTLS (insecure).", Value: fmt.Sprintf("%t", cfg.SMTP.SkipVerify)}, + {ID: "SMTP.Username", Type: ConfigTypeString, Description: "Username for authentication.", Value: cfg.SMTP.Username}, + {ID: "SMTP.Password", Type: ConfigTypeString, Description: "Password for authentication.", Value: cfg.SMTP.Password, Password: true}, + {ID: "Webhook.Enable", Type: ConfigTypeBoolean, Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, + {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, + {ID: "Feedback.Enable", Type: ConfigTypeBoolean, Description: "Enables Feedback link in nav bar.", Value: fmt.Sprintf("%t", cfg.Feedback.Enable)}, + {ID: "Feedback.OverrideURL", Type: ConfigTypeString, Description: "Use a custom URL for Feedback link in nav bar.", Value: cfg.Feedback.OverrideURL}, } } // MapPublicConfigValues will map a Config struct into a flat list of ConfigValue structs. func MapPublicConfigValues(cfg config.Config) []ConfigValue { return []ConfigValue{ - {ID: "General.ApplicationName", Type: ConfigTypeString, DisplayName: "Application Name", Description: "The name used in messaging and page titles. Defaults to \"GoAlert\".", Value: cfg.General.ApplicationName}, - {ID: "General.PublicURL", Type: ConfigTypeString, DisplayName: "Public URL", Description: "Publicly routable URL for UI links and API calls.", Value: cfg.General.PublicURL}, - {ID: "General.GoogleAnalyticsID", Type: ConfigTypeString, DisplayName: "Google Analytics ID", Description: "", Value: cfg.General.GoogleAnalyticsID}, - {ID: "General.NotificationDisclaimer", Type: ConfigTypeString, DisplayName: "Notification Disclaimer", Description: "Disclaimer text for receiving pre-recorded notifications (appears on profile page).", Value: cfg.General.NotificationDisclaimer}, - {ID: "General.MessageBundles", Type: ConfigTypeBoolean, DisplayName: "Message Bundles", Description: "Enables bundling status updates and alert notifications. Also allows 'ack/close all' responses to bundled alerts.", Value: fmt.Sprintf("%t", cfg.General.MessageBundles)}, - {ID: "General.ShortURL", Type: ConfigTypeString, DisplayName: "Short URL", Description: "If set, messages will contain a shorter URL using this as a prefix (e.g. http://example.com). It should point to GoAlert and can be the same as the PublicURL.", Value: cfg.General.ShortURL}, - {ID: "General.DisableSMSLinks", Type: ConfigTypeBoolean, DisplayName: "Disable SMS Links", Description: "If set, SMS messages will not contain a URL pointing to GoAlert.", Value: fmt.Sprintf("%t", cfg.General.DisableSMSLinks)}, - {ID: "General.DisableLabelCreation", Type: ConfigTypeBoolean, DisplayName: "Disable Label Creation", Description: "Disables the ability to create new labels for services.", Value: fmt.Sprintf("%t", cfg.General.DisableLabelCreation)}, - {ID: "General.DisableCalendarSubscriptions", Type: ConfigTypeBoolean, DisplayName: "Disable Calendar Subscriptions", Description: "If set, disables all active calendar subscriptions as well as the ability to create new calendar subscriptions.", Value: fmt.Sprintf("%t", cfg.General.DisableCalendarSubscriptions)}, - {ID: "Maintenance.AlertCleanupDays", Type: ConfigTypeInteger, DisplayName: "Alert Cleanup Days", Description: "Closed alerts will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertCleanupDays)}, - {ID: "Maintenance.APIKeyExpireDays", Type: ConfigTypeInteger, DisplayName: "API Key Expire Days", Description: "Unused calendar API keys will be disabled after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.APIKeyExpireDays)}, - {ID: "Auth.DisableBasic", Type: ConfigTypeBoolean, DisplayName: "Disable Basic", Description: "Disallow username/password login.", Value: fmt.Sprintf("%t", cfg.Auth.DisableBasic)}, - {ID: "GitHub.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enable GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.Enable)}, - {ID: "OIDC.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enable OpenID Connect authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.Enable)}, - {ID: "Mailgun.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Mailgun.Enable)}, - {ID: "Slack.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "", Value: fmt.Sprintf("%t", cfg.Slack.Enable)}, - {ID: "Twilio.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, - {ID: "Twilio.FromNumber", Type: ConfigTypeString, DisplayName: "From Number", Description: "The Twilio number to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, - {ID: "SMTP.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables email as a contact method.", Value: fmt.Sprintf("%t", cfg.SMTP.Enable)}, - {ID: "SMTP.From", Type: ConfigTypeString, DisplayName: "From", Description: "The email address messages should be sent from.", Value: cfg.SMTP.From}, - {ID: "Webhook.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, - {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, DisplayName: "Allowed URLs", Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, - {ID: "Feedback.Enable", Type: ConfigTypeBoolean, DisplayName: "Enable", Description: "Enables Feedback link in nav bar.", Value: fmt.Sprintf("%t", cfg.Feedback.Enable)}, - {ID: "Feedback.OverrideURL", Type: ConfigTypeString, DisplayName: "Override URL", Description: "Use a custom URL for Feedback link in nav bar.", Value: cfg.Feedback.OverrideURL}, + {ID: "General.ApplicationName", Type: ConfigTypeString, Description: "The name used in messaging and page titles. Defaults to \"GoAlert\".", Value: cfg.General.ApplicationName}, + {ID: "General.PublicURL", Type: ConfigTypeString, Description: "Publicly routable URL for UI links and API calls.", Value: cfg.General.PublicURL}, + {ID: "General.GoogleAnalyticsID", Type: ConfigTypeString, Description: "", Value: cfg.General.GoogleAnalyticsID}, + {ID: "General.NotificationDisclaimer", Type: ConfigTypeString, Description: "Disclaimer text for receiving pre-recorded notifications (appears on profile page).", Value: cfg.General.NotificationDisclaimer}, + {ID: "General.MessageBundles", Type: ConfigTypeBoolean, Description: "Enables bundling status updates and alert notifications. Also allows 'ack/close all' responses to bundled alerts.", Value: fmt.Sprintf("%t", cfg.General.MessageBundles)}, + {ID: "General.ShortURL", Type: ConfigTypeString, Description: "If set, messages will contain a shorter URL using this as a prefix (e.g. http://example.com). It should point to GoAlert and can be the same as the PublicURL.", Value: cfg.General.ShortURL}, + {ID: "General.DisableSMSLinks", Type: ConfigTypeBoolean, Description: "If set, SMS messages will not contain a URL pointing to GoAlert.", Value: fmt.Sprintf("%t", cfg.General.DisableSMSLinks)}, + {ID: "General.DisableLabelCreation", Type: ConfigTypeBoolean, Description: "Disables the ability to create new labels for services.", Value: fmt.Sprintf("%t", cfg.General.DisableLabelCreation)}, + {ID: "General.DisableCalendarSubscriptions", Type: ConfigTypeBoolean, Description: "If set, disables all active calendar subscriptions as well as the ability to create new calendar subscriptions.", Value: fmt.Sprintf("%t", cfg.General.DisableCalendarSubscriptions)}, + {ID: "Maintenance.AlertCleanupDays", Type: ConfigTypeInteger, Description: "Closed alerts will be deleted after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.AlertCleanupDays)}, + {ID: "Maintenance.APIKeyExpireDays", Type: ConfigTypeInteger, Description: "Unused calendar API keys will be disabled after this many days (0 means disable cleanup).", Value: fmt.Sprintf("%d", cfg.Maintenance.APIKeyExpireDays)}, + {ID: "Auth.DisableBasic", Type: ConfigTypeBoolean, Description: "Disallow username/password login.", Value: fmt.Sprintf("%t", cfg.Auth.DisableBasic)}, + {ID: "GitHub.Enable", Type: ConfigTypeBoolean, Description: "Enable GitHub authentication.", Value: fmt.Sprintf("%t", cfg.GitHub.Enable)}, + {ID: "OIDC.Enable", Type: ConfigTypeBoolean, Description: "Enable OpenID Connect authentication.", Value: fmt.Sprintf("%t", cfg.OIDC.Enable)}, + {ID: "Mailgun.Enable", Type: ConfigTypeBoolean, Description: "", Value: fmt.Sprintf("%t", cfg.Mailgun.Enable)}, + {ID: "Slack.Enable", Type: ConfigTypeBoolean, Description: "", Value: fmt.Sprintf("%t", cfg.Slack.Enable)}, + {ID: "Twilio.Enable", Type: ConfigTypeBoolean, Description: "Enables sending and processing of Voice and SMS messages through the Twilio notification provider.", Value: fmt.Sprintf("%t", cfg.Twilio.Enable)}, + {ID: "Twilio.FromNumber", Type: ConfigTypeString, Description: "The Twilio number to use for outgoing notifications.", Value: cfg.Twilio.FromNumber}, + {ID: "SMTP.Enable", Type: ConfigTypeBoolean, Description: "Enables email as a contact method.", Value: fmt.Sprintf("%t", cfg.SMTP.Enable)}, + {ID: "SMTP.From", Type: ConfigTypeString, Description: "The email address messages should be sent from.", Value: cfg.SMTP.From}, + {ID: "Webhook.Enable", Type: ConfigTypeBoolean, Description: "Enables webhook as a contact method.", Value: fmt.Sprintf("%t", cfg.Webhook.Enable)}, + {ID: "Webhook.AllowedURLs", Type: ConfigTypeStringList, Description: "If set, allows webhooks for these domains only.", Value: strings.Join(cfg.Webhook.AllowedURLs, "\n")}, + {ID: "Feedback.Enable", Type: ConfigTypeBoolean, Description: "Enables Feedback link in nav bar.", Value: fmt.Sprintf("%t", cfg.Feedback.Enable)}, + {ID: "Feedback.OverrideURL", Type: ConfigTypeString, Description: "Use a custom URL for Feedback link in nav bar.", Value: cfg.Feedback.OverrideURL}, } } diff --git a/graphql2/models_gen.go b/graphql2/models_gen.go index 9178ea33c7..9ae64e17e0 100644 --- a/graphql2/models_gen.go +++ b/graphql2/models_gen.go @@ -74,14 +74,12 @@ type ClearTemporarySchedulesInput struct { } type ConfigHint struct { - ID string `json:"id"` - DisplayName string `json:"displayName"` - Value string `json:"value"` + ID string `json:"id"` + Value string `json:"value"` } type ConfigValue struct { ID string `json:"id"` - DisplayName string `json:"displayName"` Description string `json:"description"` Value string `json:"value"` Type ConfigType `json:"type"` diff --git a/graphql2/schema.graphql b/graphql2/schema.graphql index 30888e15ed..109a07dbe5 100644 --- a/graphql2/schema.graphql +++ b/graphql2/schema.graphql @@ -136,7 +136,6 @@ input SystemLimitInput { type ConfigValue { id: String! - displayName: String! description: String! value: String! type: ConfigType! @@ -144,7 +143,6 @@ type ConfigValue { } type ConfigHint { id: String! - displayName: String! value: String! } enum ConfigType { diff --git a/web/src/app/admin/AdminConfig.tsx b/web/src/app/admin/AdminConfig.tsx index 6c3485da1b..fba25a800e 100644 --- a/web/src/app/admin/AdminConfig.tsx +++ b/web/src/app/admin/AdminConfig.tsx @@ -29,7 +29,6 @@ const query = gql` query getConfig { config(all: true) { id - displayName description password type @@ -37,7 +36,6 @@ const query = gql` } configHints { id - displayName value } } @@ -124,6 +122,8 @@ export default function AdminConfig(): JSX.Element { .groupBy((f: ConfigHint) => f.id.split('.')[0]) .value() + const hintName = (id: string): string => startCase(id.split('.')[1]) + const handleExpandChange = (id: string) => () => setSection(id === section ? false : id) @@ -224,7 +224,7 @@ export default function AdminConfig(): JSX.Element { ) .map((f: ConfigValue) => ({ id: f.id, - displayName: f.displayName, + label: formatHeading(_.last(f.id.split('.'))), description: f.description, password: f.password, type: f.type, @@ -236,7 +236,7 @@ export default function AdminConfig(): JSX.Element { hintGroups[groupID].map((h: ConfigHint) => ( void } @@ -74,7 +78,7 @@ export default function AdminSection(props: AdminSectionProps): JSX.Element { /> )} - {fields.map((f: ConfigValue, idx: number) => { + {fields.map((f: FieldProps, idx: number) => { const Field = components[f.type] return (
diff --git a/web/src/schema.d.ts b/web/src/schema.d.ts index aca7335e4f..654123ddfa 100644 --- a/web/src/schema.d.ts +++ b/web/src/schema.d.ts @@ -65,7 +65,6 @@ export interface SystemLimitInput { export interface ConfigValue { id: string - displayName: string description: string value: string type: ConfigType @@ -74,7 +73,6 @@ export interface ConfigValue { export interface ConfigHint { id: string - displayName: string value: string } From e20cd56c90f9238ff44d22d778c6c047728d1162 Mon Sep 17 00:00:00 2001 From: KatieMSB Date: Thu, 16 Sep 2021 15:45:12 -0500 Subject: [PATCH 21/32] run gofmt --- devtools/configparams/run.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devtools/configparams/run.go b/devtools/configparams/run.go index e7a42d08cf..b7033906df 100644 --- a/devtools/configparams/run.go +++ b/devtools/configparams/run.go @@ -130,7 +130,7 @@ func ApplyConfigValues(cfg config.Config, vals []ConfigValueInput) (config.Confi type field struct { ID, Type, Desc, Value string - Public, Password bool + Public, Password bool } func main() { From dd97346a69281b6154988be226f8a850a814eca4 Mon Sep 17 00:00:00 2001 From: KatieMSB Date: Thu, 16 Sep 2021 16:08:44 -0500 Subject: [PATCH 22/32] fix formatting --- devtools/configparams/run.go | 2 +- graphql2/mapconfig.go | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/devtools/configparams/run.go b/devtools/configparams/run.go index b7033906df..1f5ba66bd2 100644 --- a/devtools/configparams/run.go +++ b/devtools/configparams/run.go @@ -48,7 +48,7 @@ func MapConfigHints(cfg config.Hints) []ConfigHint { func MapConfigValues(cfg config.Config) []ConfigValue { return []ConfigValue{ {{- range .ConfigFields }} - {ID: {{quote .ID}},Type: {{.Type}}, Description: {{quote .Desc}}, Value: {{.Value}}{{if .Password}}, Password: true{{end}}}, + {ID: {{quote .ID}}, Type: {{.Type}}, Description: {{quote .Desc}}, Value: {{.Value}}{{if .Password}}, Password: true{{end}}}, {{- end}} } } diff --git a/graphql2/mapconfig.go b/graphql2/mapconfig.go index 8222f6d79e..7b39422ce9 100644 --- a/graphql2/mapconfig.go +++ b/graphql2/mapconfig.go @@ -3,10 +3,6 @@ package graphql2 import ( - "fmt" - "strconv" - "strings" - "github.com/target/goalert/config" "github.com/target/goalert/validation" ) @@ -127,7 +123,7 @@ func ApplyConfigValues(cfg config.Config, vals []ConfigValueInput) (config.Confi } val, err := strconv.ParseInt(v, 10, 64) if err != nil { - return 0, validation.NewFieldError("\""+id+"\".Value", "integer value invalid: "+err.Error()) + return 0, validation.NewFieldError("\""+id+"\".Value", "integer value invalid: " + err.Error()) } return int(val), nil } From 4a736ba6d312a2dfaf2e9d205de08e2ca6dbe683 Mon Sep 17 00:00:00 2001 From: dctalbot Date: Wed, 22 Sep 2021 10:42:24 -0500 Subject: [PATCH 23/32] strip out non-alphanum chars --- graphql2/mapconfig.go | 6 +++++- web/src/app/admin/AdminToolbox.tsx | 9 ++------- web/src/app/util/TelTextField.tsx | 10 +++++++--- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/graphql2/mapconfig.go b/graphql2/mapconfig.go index 7b39422ce9..8222f6d79e 100644 --- a/graphql2/mapconfig.go +++ b/graphql2/mapconfig.go @@ -3,6 +3,10 @@ package graphql2 import ( + "fmt" + "strconv" + "strings" + "github.com/target/goalert/config" "github.com/target/goalert/validation" ) @@ -123,7 +127,7 @@ func ApplyConfigValues(cfg config.Config, vals []ConfigValueInput) (config.Confi } val, err := strconv.ParseInt(v, 10, 64) if err != nil { - return 0, validation.NewFieldError("\""+id+"\".Value", "integer value invalid: " + err.Error()) + return 0, validation.NewFieldError("\""+id+"\".Value", "integer value invalid: "+err.Error()) } return int(val), nil } diff --git a/web/src/app/admin/AdminToolbox.tsx b/web/src/app/admin/AdminToolbox.tsx index b2394e95fe..ea514295b3 100644 --- a/web/src/app/admin/AdminToolbox.tsx +++ b/web/src/app/admin/AdminToolbox.tsx @@ -11,11 +11,6 @@ const useStyles = makeStyles((theme) => ({ justifyContent: 'center', }, }, - gridItem: { - [theme.breakpoints.up('md')]: { - maxWidth: '65%', - }, - }, groupTitle: { fontSize: '1.1rem', }, @@ -29,7 +24,7 @@ export default function AdminToolbox(): JSX.Element { return ( - + - + Date: Wed, 22 Sep 2021 10:45:51 -0500 Subject: [PATCH 24/32] update validation for from values --- graphql2/graphqlapp/toolbox.go | 10 ++-------- validation/validate/twiliofromvalue.go | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 validation/validate/twiliofromvalue.go diff --git a/graphql2/graphqlapp/toolbox.go b/graphql2/graphqlapp/toolbox.go index 0ffc82c433..b68687ac0a 100644 --- a/graphql2/graphqlapp/toolbox.go +++ b/graphql2/graphqlapp/toolbox.go @@ -1,14 +1,13 @@ package graphqlapp import ( - context "context" + "context" "fmt" "net/url" "github.com/target/goalert/graphql2" "github.com/target/goalert/notification/twilio" "github.com/target/goalert/permission" - "github.com/target/goalert/validation" "github.com/target/goalert/validation/validate" "github.com/ttacon/libphonenumber" ) @@ -23,14 +22,9 @@ func (a *Mutation) DebugSendSms(ctx context.Context, input graphql2.DebugSendSMS return nil, err } - var fromErr error - if validate.Phone("From", input.From) != nil && validate.SID("From", input.From) != nil { - fromErr = validation.NewFieldError("From", "is not a valid phone number or alphanumeric sender ID.") - } - err = validate.Many( validate.Phone("To", input.To), - fromErr, + validate.TwilioFromValue("From", input.From), validate.Text("Body", input.Body, 1, 1000), ) if err != nil { diff --git a/validation/validate/twiliofromvalue.go b/validation/validate/twiliofromvalue.go new file mode 100644 index 0000000000..cbe47532ff --- /dev/null +++ b/validation/validate/twiliofromvalue.go @@ -0,0 +1,17 @@ +package validate + +import ( + "github.com/target/goalert/validation" +) + +// TwlioFromValue will validate a from value as either a phone number, or messenger SID +func TwilioFromValue(fname, value string) error { + phoneErr := Phone(fname, value) + sidErr := SID(fname, value) + + if phoneErr != nil && sidErr != nil { + return validation.NewFieldError("From", "is not a valid phone number or alphanumeric sender ID.") + } + + return nil +} \ No newline at end of file From dd6386cd633ab12289d5262025abad59eaca418f Mon Sep 17 00:00:00 2001 From: KatieMSB Date: Wed, 22 Sep 2021 11:15:20 -0500 Subject: [PATCH 25/32] sid -> twiliomessagesid --- validation/validate/twiliofromvalue.go | 2 +- validation/validate/{sid.go => twiliomessagesid.go} | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename validation/validate/{sid.go => twiliomessagesid.go} (59%) diff --git a/validation/validate/twiliofromvalue.go b/validation/validate/twiliofromvalue.go index cbe47532ff..41933c3201 100644 --- a/validation/validate/twiliofromvalue.go +++ b/validation/validate/twiliofromvalue.go @@ -7,7 +7,7 @@ import ( // TwlioFromValue will validate a from value as either a phone number, or messenger SID func TwilioFromValue(fname, value string) error { phoneErr := Phone(fname, value) - sidErr := SID(fname, value) + sidErr := TwilioMessageSID(fname, value) if phoneErr != nil && sidErr != nil { return validation.NewFieldError("From", "is not a valid phone number or alphanumeric sender ID.") diff --git a/validation/validate/sid.go b/validation/validate/twiliomessagesid.go similarity index 59% rename from validation/validate/sid.go rename to validation/validate/twiliomessagesid.go index 68ff233ed8..92aa1c8ebe 100644 --- a/validation/validate/sid.go +++ b/validation/validate/twiliomessagesid.go @@ -6,8 +6,8 @@ import ( "github.com/target/goalert/validation" ) -// SID will validate an SID, returning a FieldError if invalid. -func SID(fname, value string) error { +// TwilioMessageSID will validate an Message SID, returning a FieldError if invalid. +func TwilioMessageSID(fname, value string) error { if !strings.HasPrefix(value, "MG") { return validation.NewFieldError(fname, "must begin with MG") From 66654ec61d3a26e114178f2cc6abe5ee44d90262 Mon Sep 17 00:00:00 2001 From: KatieMSB Date: Wed, 22 Sep 2021 13:59:07 -0500 Subject: [PATCH 26/32] update validation --- .../validate/{twiliomessagesid.go => messagingservicesid.go} | 5 ++--- validation/validate/twiliofromvalue.go | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) rename validation/validate/{twiliomessagesid.go => messagingservicesid.go} (56%) diff --git a/validation/validate/twiliomessagesid.go b/validation/validate/messagingservicesid.go similarity index 56% rename from validation/validate/twiliomessagesid.go rename to validation/validate/messagingservicesid.go index 92aa1c8ebe..7b5cbc9ce0 100644 --- a/validation/validate/twiliomessagesid.go +++ b/validation/validate/messagingservicesid.go @@ -6,9 +6,8 @@ import ( "github.com/target/goalert/validation" ) -// TwilioMessageSID will validate an Message SID, returning a FieldError if invalid. -func TwilioMessageSID(fname, value string) error { - +// MessagingServiceSID will validate an Messaging Service SID, returning a FieldError if invalid. +func MessagingServiceSID(fname, value string) error { if !strings.HasPrefix(value, "MG") { return validation.NewFieldError(fname, "must begin with MG") } diff --git a/validation/validate/twiliofromvalue.go b/validation/validate/twiliofromvalue.go index 41933c3201..bc9192c7e1 100644 --- a/validation/validate/twiliofromvalue.go +++ b/validation/validate/twiliofromvalue.go @@ -7,9 +7,10 @@ import ( // TwlioFromValue will validate a from value as either a phone number, or messenger SID func TwilioFromValue(fname, value string) error { phoneErr := Phone(fname, value) - sidErr := TwilioMessageSID(fname, value) + sidErr := MessagingServiceSID(fname, value) + asciiErr := ASCII(fname, value, 2, 64) - if phoneErr != nil && sidErr != nil { + if phoneErr != nil && sidErr != nil && asciiErr != nil { return validation.NewFieldError("From", "is not a valid phone number or alphanumeric sender ID.") } From 1e8d4bb76def1bc9e16a69d31fc2c80f753cb776 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Wed, 22 Sep 2021 14:17:52 -0500 Subject: [PATCH 27/32] don't assign From value until call/sms is processed --- devtools/mocktwilio/sms.go | 16 +++++++++++++++- devtools/mocktwilio/voicecall.go | 28 +++++++++++++++++++++++----- notification/twilio/call.go | 2 ++ notification/twilio/message.go | 2 ++ 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/devtools/mocktwilio/sms.go b/devtools/mocktwilio/sms.go index 7f67e2a716..5b1474c658 100644 --- a/devtools/mocktwilio/sms.go +++ b/devtools/mocktwilio/sms.go @@ -64,7 +64,6 @@ func (s *Server) sendSMS(fromValue, to, body, statusURL, destURL string) (*SMS, s: s, msg: twilio.Message{ To: to, - From: fromNumber, Status: twilio.MessageStatusAccepted, SID: s.id("SM"), }, @@ -76,6 +75,12 @@ func (s *Server) sendSMS(fromValue, to, body, statusURL, destURL string) (*SMS, doneCh: make(chan struct{}), } + if strings.HasPrefix(fromValue, "MG") { + sms.msg.MessagingServiceSID = fromValue + } else { + sms.msg.From = fromValue + } + s.mx.Lock() s.messages[sms.msg.SID] = sms s.mx.Unlock() @@ -131,6 +136,15 @@ func (s *Server) serveMessageStatus(w http.ResponseWriter, req *http.Request) { func (sms *SMS) updateStatus(stat twilio.MessageStatus) { sms.mx.Lock() sms.msg.Status = stat + switch stat { + case twilio.MessageStatusAccepted, twilio.MessageStatusQueued: + default: + if sms.msg.MessagingServiceSID == "" { + break + } + + sms.msg.From = sms.s.getFromNumber(sms.msg.MessagingServiceSID) + } sms.mx.Unlock() if sms.statusURL == "" { diff --git a/devtools/mocktwilio/voicecall.go b/devtools/mocktwilio/voicecall.go index 15b7ee446d..01aac420fc 100644 --- a/devtools/mocktwilio/voicecall.go +++ b/devtools/mocktwilio/voicecall.go @@ -150,9 +150,10 @@ func (s *Server) serveNewCall(w http.ResponseWriter, req *http.Request) { hangupCh: make(chan struct{}), } - vc.call.From = s.getFromNumber(req.FormValue("From")) + fromValue := req.FormValue("From") + fromNumber := s.getFromNumber(fromValue) s.mx.RLock() - _, hasCallback := s.callbacks["VOICE:"+vc.call.From] + _, hasCallback := s.callbacks["VOICE:"+fromNumber] s.mx.RUnlock() if !hasCallback { apiError(400, w, &twilio.Exception{ @@ -160,6 +161,13 @@ func (s *Server) serveNewCall(w http.ResponseWriter, req *http.Request) { }) return } + + if strings.HasPrefix(fromValue, "MG") { + vc.call.MessagingServiceSID = fromValue + } else { + vc.call.From = fromValue + } + vc.s = s vc.call.To = req.FormValue("To") vc.call.SID = s.id("CA") @@ -211,10 +219,20 @@ func (vc *VoiceCall) updateStatus(stat twilio.CallStatus) { // move to queued vc.mx.Lock() vc.call.Status = stat - if stat == twilio.CallStatusInProgress { - vc.callStart = time.Now() + switch stat { + case twilio.CallStatusQueued, twilio.CallStatusInitiated: + default: + if vc.call.MessagingServiceSID == "" { + break + } + + vc.call.From = vc.s.getFromNumber(vc.call.MessagingServiceSID) } - if stat == twilio.CallStatusCompleted { + + switch stat { + case twilio.CallStatusInProgress: + vc.callStart = time.Now() + case twilio.CallStatusCompleted: vc.call.CallDuration = time.Since(vc.callStart) } *vc.call.SequenceNumber++ diff --git a/notification/twilio/call.go b/notification/twilio/call.go index 51e4615aaa..819c38f79c 100644 --- a/notification/twilio/call.go +++ b/notification/twilio/call.go @@ -55,6 +55,8 @@ type Call struct { CallDuration time.Duration ErrorMessage *string ErrorCode *CallErrorCode + + MessagingServiceSID string `json:"messaging_service_sid"` } func (call *Call) sentMessage() *notification.SentMessage { diff --git a/notification/twilio/message.go b/notification/twilio/message.go index 5247a0ee35..13658f02e4 100644 --- a/notification/twilio/message.go +++ b/notification/twilio/message.go @@ -67,6 +67,8 @@ type Message struct { Status MessageStatus ErrorCode *MessageErrorCode ErrorMessage *string + + MessagingServiceSID string `json:"messaging_service_sid"` } func (msg *Message) sentMessage() *notification.SentMessage { From d299309f30ef3d3ed0f1ac08ad73b178150c0564 Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Wed, 22 Sep 2021 14:27:04 -0500 Subject: [PATCH 28/32] use single validation func --- validation/validate/messagingservicesid.go | 16 ---------------- validation/validate/twiliofromvalue.go | 17 +++++++++-------- 2 files changed, 9 insertions(+), 24 deletions(-) delete mode 100644 validation/validate/messagingservicesid.go diff --git a/validation/validate/messagingservicesid.go b/validation/validate/messagingservicesid.go deleted file mode 100644 index 7b5cbc9ce0..0000000000 --- a/validation/validate/messagingservicesid.go +++ /dev/null @@ -1,16 +0,0 @@ -package validate - -import ( - "strings" - - "github.com/target/goalert/validation" -) - -// MessagingServiceSID will validate an Messaging Service SID, returning a FieldError if invalid. -func MessagingServiceSID(fname, value string) error { - if !strings.HasPrefix(value, "MG") { - return validation.NewFieldError(fname, "must begin with MG") - } - - return nil -} diff --git a/validation/validate/twiliofromvalue.go b/validation/validate/twiliofromvalue.go index bc9192c7e1..bdad569756 100644 --- a/validation/validate/twiliofromvalue.go +++ b/validation/validate/twiliofromvalue.go @@ -1,18 +1,19 @@ package validate import ( + "strings" + "github.com/target/goalert/validation" ) // TwlioFromValue will validate a from value as either a phone number, or messenger SID func TwilioFromValue(fname, value string) error { - phoneErr := Phone(fname, value) - sidErr := MessagingServiceSID(fname, value) - asciiErr := ASCII(fname, value, 2, 64) - - if phoneErr != nil && sidErr != nil && asciiErr != nil { - return validation.NewFieldError("From", "is not a valid phone number or alphanumeric sender ID.") + switch { + case strings.HasPrefix(value, "+"): + return Phone(fname, value) + case strings.HasPrefix(value, "MG"): + return ASCII(fname, value, 2, 64) } - return nil -} \ No newline at end of file + return validation.NewFieldError(fname, "must be a valid phone number or alphanumeric sender ID") +} From 120b4e402fc93e597c52d895da7e66d7414e5f1c Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Wed, 22 Sep 2021 14:28:24 -0500 Subject: [PATCH 29/32] update validation func comment --- validation/validate/twiliofromvalue.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validation/validate/twiliofromvalue.go b/validation/validate/twiliofromvalue.go index bdad569756..0cb1eeab07 100644 --- a/validation/validate/twiliofromvalue.go +++ b/validation/validate/twiliofromvalue.go @@ -6,7 +6,7 @@ import ( "github.com/target/goalert/validation" ) -// TwlioFromValue will validate a from value as either a phone number, or messenger SID +// TwlioFromValue will validate a from value as either a phone number, or messaging service SID starting with 'MG'. func TwilioFromValue(fname, value string) error { switch { case strings.HasPrefix(value, "+"): From fa679cd055713c442dab4baa011dcd033b2b266c Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Wed, 22 Sep 2021 14:35:55 -0500 Subject: [PATCH 30/32] don't require `type` --- web/src/app/admin/AdminFieldComponents.tsx | 2 +- web/src/app/admin/AdminSection.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/web/src/app/admin/AdminFieldComponents.tsx b/web/src/app/admin/AdminFieldComponents.tsx index 05882968c1..eb30ee94c8 100644 --- a/web/src/app/admin/AdminFieldComponents.tsx +++ b/web/src/app/admin/AdminFieldComponents.tsx @@ -9,7 +9,7 @@ import VisibilityOff from '@material-ui/icons/VisibilityOff' import TelTextField from '../util/TelTextField' interface InputProps { - type: string + type?: string name: string value: string password?: boolean diff --git a/web/src/app/admin/AdminSection.tsx b/web/src/app/admin/AdminSection.tsx index 0eb5ad68c0..b203f3ca47 100644 --- a/web/src/app/admin/AdminSection.tsx +++ b/web/src/app/admin/AdminSection.tsx @@ -97,7 +97,6 @@ export default function AdminSection(props: AdminSectionProps): JSX.Element { />
Date: Wed, 22 Sep 2021 15:45:04 -0500 Subject: [PATCH 31/32] swap field types for FromValue --- web/src/app/admin/AdminFieldComponents.tsx | 8 +- web/src/app/admin/AdminSMSSend.tsx | 5 +- web/src/app/users/UserContactMethodForm.tsx | 2 +- web/src/app/util/FromValueField.tsx | 86 ++++++++++++++ web/src/app/util/TelTextField.tsx | 120 ++++++++------------ web/src/cypress/integration/profile.ts | 2 +- 6 files changed, 140 insertions(+), 83 deletions(-) create mode 100644 web/src/app/util/FromValueField.tsx diff --git a/web/src/app/admin/AdminFieldComponents.tsx b/web/src/app/admin/AdminFieldComponents.tsx index eb30ee94c8..1aedd7fed5 100644 --- a/web/src/app/admin/AdminFieldComponents.tsx +++ b/web/src/app/admin/AdminFieldComponents.tsx @@ -37,13 +37,7 @@ export function StringInput(props: InputProps): JSX.Element { } if (props.name === 'Twilio.FromNumber') { - return ( - onChange(e.target.value)} - {...rest} - inputTypes={['tel']} - /> - ) + return onChange(e.target.value)} {...rest} /> } return ( - setFromNumber(e.target.value)} value={fromNumber} fullWidth - label='From Number or Twilio Messaging Service SID' - inputTypes={['tel', 'sid']} /> diff --git a/web/src/app/users/UserContactMethodForm.tsx b/web/src/app/users/UserContactMethodForm.tsx index b2f79beb7a..2827c030d7 100644 --- a/web/src/app/users/UserContactMethodForm.tsx +++ b/web/src/app/users/UserContactMethodForm.tsx @@ -42,7 +42,7 @@ function renderPhoneField(edit: boolean): JSX.Element { return ( { + if (props.value.startsWith('M')) { + setPhoneMode(false) + } else if (props.value.startsWith('+')) { + setPhoneMode(true) + } + }, [props.value]) + + if (!phoneMode) { + return ( + { + if (!props.onChange) return + + e.target.value = e.target.value.trim().toLowerCase() + + if (e.target.value === 'm') { + e.target.value = 'M' + } else if (e.target.value === 'mg') { + e.target.value = 'MG' + } else if (e.target.value.startsWith('mg')) { + e.target.value = 'MG' + e.target.value.replace(/[^0-9a-f]/g, '') + } else { + e.target.value = '' + } + + props.onChange(e) + }} + helperText={ + } + onClick={(_e: unknown) => { + setPhoneMode(true) + if (!props.onChange) return + + const e = _e as React.ChangeEvent + e.target.value = '' + props.onChange(e) + }} + > + Use a phone number + + } + /> + ) + } + + return ( + } + onClick={(_e: unknown) => { + setPhoneMode(false) + if (!props.onChange) return + + const e = _e as React.ChangeEvent + e.target.value = '' + props.onChange(e) + }} + > + Use a Messaging Service SID + + } + /> + ) +} diff --git a/web/src/app/util/TelTextField.tsx b/web/src/app/util/TelTextField.tsx index ba3723a508..3e4dad765a 100644 --- a/web/src/app/util/TelTextField.tsx +++ b/web/src/app/util/TelTextField.tsx @@ -1,7 +1,9 @@ import React, { useEffect, useState } from 'react' import { useQuery, gql } from '@apollo/client' import TextField, { TextFieldProps } from '@material-ui/core/TextField' +import { InputProps } from '@material-ui/core/Input' import { Check, Close } from '@material-ui/icons' +import _ from 'lodash' import InputAdornment from '@material-ui/core/InputAdornment' import { makeStyles } from '@material-ui/core' import { DEBOUNCE_DELAY } from '../config' @@ -24,113 +26,89 @@ const useStyles = makeStyles({ }, }) -type InputType = 'tel' | 'sid' -type TelTextFieldProps = TextFieldProps & { - value: string - inputTypes?: InputType[] -} - -export default function TelTextField(props: TelTextFieldProps): JSX.Element { - const { inputTypes = ['tel'], value = '', ...textFieldProps } = props +export default function TelTextField( + props: TextFieldProps & { value: string }, +): JSX.Element { const classes = useStyles() - const [debouncedValue, setDebouncedValue] = useState('') + const [phoneNumber, setPhoneNumber] = useState('') // debounce to set the phone number useEffect(() => { const t = setTimeout(() => { - setDebouncedValue(value) + setPhoneNumber(props.value) }, DEBOUNCE_DELAY) return () => clearTimeout(t) - }, [value]) - - const reTel = /^\+\d+/ - const reSID = /^MG[a-zA-Z0-9]+/ - const onlyTel = inputTypes.length === 1 && inputTypes[0] === 'tel' - const onlySID = inputTypes.length === 1 && inputTypes[0] === 'sid' - const isSID = (s: string): boolean => { - return inputTypes.includes('sid') && reSID.test(s) - } - - const skipValidation = (): boolean => { - if (!debouncedValue || props.disabled || !inputTypes.includes('tel')) { - return true - } - if (onlyTel && !reTel.test(debouncedValue)) { - return true - } - if (isSID(debouncedValue)) { - return true - } - if (onlySID) { - return true - } - return false - } + }, [props.value]) - // validate the input value + // check validation of the input phoneNumber through graphql const { data } = useQuery(isValidNumber, { pollInterval: 0, - variables: { number: debouncedValue }, + variables: { number: '+' + phoneNumber }, fetchPolicy: 'cache-first', - skip: skipValidation(), + skip: !phoneNumber || props.disabled, }) - const valid = Boolean(data?.phoneNumberInfo?.valid) + // fetch validation + const valid = _.get(data, 'phoneNumberInfo.valid', null) let adorn - if (value === '' || isSID(value) || props.disabled) { - // no adornment + if (!props.value) { + // no adornment if empty } else if (valid) { adorn = - } else { + } else if (valid === false) { adorn = } - // add live validation icon to the right of the textfield - const InputProps = adorn - ? { - endAdornment: {adorn}, - ...props.InputProps, - } - : props.InputProps + let iprops: Partial + iprops = { + startAdornment: ( + + + + + ), + } - const getHelperText = (): TextFieldProps['helperText'] => { - if (props.helperText) { - return props.helperText + // if has inputProps from parent commponent, spread it in the iprops + if (props.InputProps !== undefined) { + iprops = { + ...iprops, + ...props.InputProps, } - if (onlyTel) { - return 'Include country code e.g. +1 (USA), +91 (India), +44 (UK)' - } - if (inputTypes.includes('tel')) { - return 'For phone numbers, include country code e.g. +1 (USA), +91 (India), +44 (UK)' + } + + // add live validation icon to the right of the textfield as an endAdornment + if (adorn) { + iprops = { + endAdornment: {adorn}, + ...iprops, } - return '' } - const handleChange = (e: React.ChangeEvent): void => { + // remove unwanted character + function handleChange(e: React.ChangeEvent): void { if (!props.onChange) return if (!e.target.value) return props.onChange(e) - const isLikeTel = /^[+\d(]/.test(e.target.value.trimLeft()) - - if (onlyTel || isLikeTel) { - e.target.value = '+' + e.target.value.replace(/[^0-9]/g, '') - } else { - e.target.value = e.target.value.replace(/[^a-zA-Z0-9]/g, '') - } + // ignore SID being pasted in + if (e.target.value.toLowerCase().startsWith('mg')) return + e.target.value = '+' + e.target.value.replace(/[^0-9]/g, '') return props.onChange(e) } return ( ) } diff --git a/web/src/cypress/integration/profile.ts b/web/src/cypress/integration/profile.ts index d923d60717..d3e5db0619 100644 --- a/web/src/cypress/integration/profile.ts +++ b/web/src/cypress/integration/profile.ts @@ -243,7 +243,7 @@ function testProfile(): void { countryCodeCheck('UK', '+44', '7911123456', '+44 7911 123456') it('should not allow fake country codes', () => { - const value = '+810' + c.integer({ min: 3000000, max: 3999999 }) + const value = '810' + c.integer({ min: 3000000, max: 3999999 }) const name = 'CM SM ' + c.word({ length: 8 }) const type = c.pickone(['SMS', 'VOICE']) From 0e27f2b368787051008a7feba27b05fe06dc89dd Mon Sep 17 00:00:00 2001 From: Nathaniel Caza Date: Wed, 22 Sep 2021 15:52:06 -0500 Subject: [PATCH 32/32] clarify default FromValueField mode --- web/src/app/util/FromValueField.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/web/src/app/util/FromValueField.tsx b/web/src/app/util/FromValueField.tsx index b5b28bb9c4..7aa4b462bd 100644 --- a/web/src/app/util/FromValueField.tsx +++ b/web/src/app/util/FromValueField.tsx @@ -7,13 +7,12 @@ import ToggleIcon from '@material-ui/icons/CompareArrows' export default function FromValueField( props: TextFieldProps & { value: string }, ): JSX.Element { - const [phoneMode, setPhoneMode] = useState(!props.value.startsWith('M')) + const [phoneMode, setPhoneMode] = useState( + props.value === '' || props.value.startsWith('+'), + ) useEffect(() => { - if (props.value.startsWith('M')) { - setPhoneMode(false) - } else if (props.value.startsWith('+')) { - setPhoneMode(true) - } + if (props.value === '') return // don't change phone mode if empty + setPhoneMode(props.value.startsWith('+')) }, [props.value]) if (!phoneMode) {