diff --git a/server/errors.go b/server/errors.go
index ee5223bf..a42641b4 100644
--- a/server/errors.go
+++ b/server/errors.go
@@ -108,8 +108,10 @@ var (
errHTTPBadRequestBillingSubscriptionExists = &errHTTP{40029, http.StatusBadRequest, "invalid request: billing subscription already exists", "", nil}
errHTTPBadRequestTierInvalid = &errHTTP{40030, http.StatusBadRequest, "invalid request: tier does not exist", "", nil}
errHTTPBadRequestUserNotFound = &errHTTP{40031, http.StatusBadRequest, "invalid request: user does not exist", "", nil}
- errHTTPBadRequestTwilioDisabled = &errHTTP{40032, http.StatusBadRequest, "invalid request: Calling is disabled", "https://ntfy.sh/docs/publish/#phone-calls", nil}
+ errHTTPBadRequestPhoneCallsDisabled = &errHTTP{40032, http.StatusBadRequest, "invalid request: calling is disabled", "https://ntfy.sh/docs/publish/#phone-calls", nil}
errHTTPBadRequestPhoneNumberInvalid = &errHTTP{40033, http.StatusBadRequest, "invalid request: phone number invalid", "https://ntfy.sh/docs/publish/#phone-calls", nil}
+ errHTTPBadRequestPhoneNumberNotVerified = &errHTTP{40034, http.StatusBadRequest, "invalid request: phone number not verified, or no matching verified numbers found", "https://ntfy.sh/docs/publish/#phone-calls", nil}
+ errHTTPBadRequestAnonymousCallsNotAllowed = &errHTTP{40035, http.StatusBadRequest, "invalid request: anonymous phone calls are not allowed", "https://ntfy.sh/docs/publish/#phone-calls", nil}
errHTTPNotFound = &errHTTP{40401, http.StatusNotFound, "page not found", "", nil}
errHTTPUnauthorized = &errHTTP{40101, http.StatusUnauthorized, "unauthorized", "https://ntfy.sh/docs/publish/#authentication", nil}
errHTTPForbidden = &errHTTP{40301, http.StatusForbidden, "forbidden", "https://ntfy.sh/docs/publish/#authentication", nil}
diff --git a/server/server.go b/server/server.go
index 430fa5cb..08cf08d3 100644
--- a/server/server.go
+++ b/server/server.go
@@ -707,17 +707,14 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*message, e
} else if email != "" && !vrate.EmailAllowed() {
return nil, errHTTPTooManyRequestsLimitEmails.With(t)
} else if call != "" {
- call, err = s.convertPhoneNumber(v.User(), call)
- if err != nil {
- return nil, errHTTPBadRequestInvalidPhoneNumber.With(t)
- }
- if !vrate.CallAllowed() {
+ var httpErr *errHTTP
+ call, httpErr = s.convertPhoneNumber(v.User(), call)
+ if httpErr != nil {
+ return nil, httpErr.With(t)
+ } else if !vrate.CallAllowed() {
return nil, errHTTPTooManyRequestsLimitCalls.With(t)
}
}
-
- // FIXME check allowed phone numbers
-
if m.PollID != "" {
m = newPollRequestMessage(t.ID, m.PollID)
}
@@ -741,6 +738,7 @@ func (s *Server) handlePublishInternal(r *http.Request, v *visitor) (*message, e
"message_firebase": firebase,
"message_unifiedpush": unifiedpush,
"message_email": email,
+ "message_call": call,
})
if ev.IsTrace() {
ev.Field("message_body", util.MaybeMarshalJSON(m)).Trace("Received message")
@@ -913,7 +911,7 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
}
call = readParam(r, "x-call", "call")
if call != "" && s.config.TwilioAccount == "" {
- return false, false, "", "", false, errHTTPBadRequestTwilioDisabled
+ return false, false, "", "", false, errHTTPBadRequestPhoneCallsDisabled
} else if call != "" && !isBoolValue(call) && !phoneNumberRegex.MatchString(call) {
return false, false, "", "", false, errHTTPBadRequestPhoneNumberInvalid
}
diff --git a/server/server_twilio.go b/server/server_twilio.go
index 128ae5ef..2c3d0a3e 100644
--- a/server/server_twilio.go
+++ b/server/server_twilio.go
@@ -31,14 +31,27 @@ const (
`
)
-func (s *Server) convertPhoneNumber(u *user.User, phoneNumber string) (string, error) {
+func (s *Server) convertPhoneNumber(u *user.User, phoneNumber string) (string, *errHTTP) {
if u == nil {
- return "", fmt.Errorf("user is nil")
+ return "", errHTTPBadRequestAnonymousCallsNotAllowed
}
- if s.config.TwilioPhoneNumberConverter == nil {
+ phoneNumbers, err := s.userManager.PhoneNumbers(u.ID)
+ if err != nil {
+ return "", errHTTPInternalError
+ } else if len(phoneNumbers) == 0 {
+ return "", errHTTPBadRequestPhoneNumberNotVerified
+ }
+ if toBool(phoneNumber) {
+ return phoneNumbers[0], nil
+ } else if util.Contains(phoneNumbers, phoneNumber) {
return phoneNumber, nil
}
- return s.config.TwilioPhoneNumberConverter(u, phoneNumber)
+ for _, p := range phoneNumbers {
+ if p == phoneNumber {
+ return phoneNumber, nil
+ }
+ }
+ return "", errHTTPBadRequestPhoneNumberNotVerified
}
func (s *Server) callPhone(v *visitor, r *http.Request, m *message, to string) {
diff --git a/web/src/components/Account.js b/web/src/components/Account.js
index 706ac02a..28d24a38 100644
--- a/web/src/components/Account.js
+++ b/web/src/components/Account.js
@@ -359,6 +359,14 @@ const PhoneNumbers = () => {
return null;
}
+ if (account?.limits.calls === 0) {
+ return (
+ {t("account_basics_phone_numbers_title")}{config.enable_payments && }>} description={t("account_basics_phone_numbers_description")}>
+ {t("account_usage_calls_none")}
+
+ )
+ }
+
return (