From 2c81773d01094be3e4fbe10add54c166469acb37 Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Tue, 16 May 2023 22:27:48 -0400 Subject: [PATCH] Add call verification --- docs/config.md | 4 +-- docs/publish.md | 48 ++++++++++++++++---------- docs/static/img/web-phone-verify.png | Bin 0 -> 22959 bytes go.sum | 18 ---------- server/errors.go | 1 + server/server_account.go | 13 +++---- server/server_twilio.go | 13 +++---- server/types.go | 9 +++-- web/public/static/langs/en.json | 7 ++-- web/src/app/AccountApi.js | 5 +-- web/src/components/Account.js | 49 +++++++++++++++++---------- 11 files changed, 93 insertions(+), 74 deletions(-) create mode 100644 docs/static/img/web-phone-verify.png diff --git a/docs/config.md b/docs/config.md index 353a9d03..d6f6e408 100644 --- a/docs/config.md +++ b/docs/config.md @@ -868,8 +868,8 @@ are the easiest), and then configure the following options: * `twilio-from-number` is the outgoing phone number you purchased, e.g. +18775132586 * `twilio-verify-service` is the Twilio Verify service SID, e.g. VA12345beefbeef67890beefbeef122586 -After you have configured phone calls, create a [tier](#tiers) with a call limit, and then assign it to a user. -Users may then use the `X-Call` header to receive a phone call when publishing a message. +After you have configured phone calls, create a [tier](#tiers) with a call limit (e.g. `ntfy tier create --call-limit=10 ...`), +and then assign it to a user. Users may then use the `X-Call` header to receive a phone call when publishing a message. ## Rate limiting !!! info diff --git a/docs/publish.md b/docs/publish.md index 98f3e876..3cca6fc6 100644 --- a/docs/publish.md +++ b/docs/publish.md @@ -2702,16 +2702,26 @@ You can use ntfy to call a phone and **read the message out loud using text-to-s Similar to email notifications, this can be useful to blast-notify yourself on all possible channels, or to notify people that do not have the ntfy app installed on their phone. -**Phone numbers have to be previously verified** (via the web app), so this feature is **only available to authenticated users**. -To forward a message as a voice call, pass a phone number in the `X-Call` header (or its alias: `Call`), prefixed with a -plus sign and the country code, e.g. `+12223334444`. You may also simply pass `yes` as a value to pick the first of your -verified phone numbers. +**Phone numbers have to be previously verified** (via the [web app](https://ntfy.sh/account)), so this feature is +**only available to authenticated users** (no anonymous phone calls). To forward a message as a voice call, pass a phone +number in the `X-Call` header (or its alias: `Call`), prefixed with a plus sign and the country code, e.g. `+12223334444`. +You may also simply pass `yes` as a value to pick the first of your verified phone numbers. +On ntfy.sh, this feature is only supported to [ntfy Pro](https://ntfy.sh/app) plans. + +
+ ![e-mail publishing](static/img/web-phone-verify.png) +
Phone number verification in the web app
+
+ +As of today, the text-to-speed voice used will only support English. If there is demand for other languages, we'll +be happy to add support for that. Please [open an issue on GitHub](https://github.com/binwiederhier/ntfy/issues). !!! info - As of today, the text-to-speed voice used will only support English. If there is demand for other languages, we'll - be happy to add support for that. Please [open an issue on GitHub](https://github.com/binwiederhier/ntfy/issues). + You are responsible for the message content, and **you must abide by the [Twilio Acceptable Use Policy](https://www.twilio.com/en-us/legal/aup)**. + This particularly means that you must not use this feature to send unsolicited messages, or messages that are illegal or + violate the rights of others. Please read the policy for details. Failure to do so may result in your account being suspended or terminated. -On ntfy.sh, this feature is only supported to [ntfy Pro](https://ntfy.sh/app) plans. +Here's how you use it: === "Command line (curl)" ``` @@ -3431,17 +3441,18 @@ There are a few limitations to the API to prevent abuse and to keep the server h are configurable via the server side [rate limiting settings](config.md#rate-limiting). Most of these limits you won't run into, but just in case, let's list them all: -| Limit | Description | -|---------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| **Message length** | Each message can be up to 4,096 bytes long. Longer messages are treated as [attachments](#attachments). | -| **Requests** | By default, the server is configured to allow 60 requests per visitor at once, and then refills the your allowed requests bucket at a rate of one request per 5 seconds. | -| **Daily messages** | By default, the number of messages is governed by the request limits. This can be overridden. On ntfy.sh, the daily message limit is 250. | -| **E-mails** | By default, the server is configured to allow sending 16 e-mails per visitor at once, and then refills the your allowed e-mail bucket at a rate of one per hour. On ntfy.sh, the daily limit is 5. | -| **Subscription limit** | By default, the server allows each visitor to keep 30 connections to the server open. | -| **Attachment size limit** | By default, the server allows attachments up to 15 MB in size, up to 100 MB in total per visitor and up to 5 GB across all visitors. On ntfy.sh, the attachment size limit is 2 MB, and the per-visitor total is 20 MB. | -| **Attachment expiry** | By default, the server deletes attachments after 3 hours and thereby frees up space from the total visitor attachment limit. | -| **Attachment bandwidth** | By default, the server allows 500 MB of GET/PUT/POST traffic for attachments per visitor in a 24 hour period. Traffic exceeding that is rejected. On ntfy.sh, the daily bandwidth limit is 200 MB. | -| **Total number of topics** | By default, the server is configured to allow 15,000 topics. The ntfy.sh server has higher limits though. | +| Limit | Description | +|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **Message length** | Each message can be up to 4,096 bytes long. Longer messages are treated as [attachments](#attachments). | +| **Requests** | By default, the server is configured to allow 60 requests per visitor at once, and then refills the your allowed requests bucket at a rate of one request per 5 seconds. | +| **Daily messages** | By default, the number of messages is governed by the request limits. This can be overridden. On ntfy.sh, the daily message limit is 250. | +| **E-mails** | By default, the server is configured to allow sending 16 e-mails per visitor at once, and then refills the your allowed e-mail bucket at a rate of one per hour. On ntfy.sh, the daily limit is 5. | +| **Phone calls** | By default, the server does not allow any phone calls, except for users with a tier that has a call limit. | +| **Subscription limit** | By default, the server allows each visitor to keep 30 connections to the server open. | +| **Attachment size limit** | By default, the server allows attachments up to 15 MB in size, up to 100 MB in total per visitor and up to 5 GB across all visitors. On ntfy.sh, the attachment size limit is 2 MB, and the per-visitor total is 20 MB. | +| **Attachment expiry** | By default, the server deletes attachments after 3 hours and thereby frees up space from the total visitor attachment limit. | +| **Attachment bandwidth** | By default, the server allows 500 MB of GET/PUT/POST traffic for attachments per visitor in a 24 hour period. Traffic exceeding that is rejected. On ntfy.sh, the daily bandwidth limit is 200 MB. | +| **Total number of topics** | By default, the server is configured to allow 15,000 topics. The ntfy.sh server has higher limits though. | These limits can be changed on a per-user basis using [tiers](config.md#tiers). If [payments](config.md#payments) are enabled, a user tier can be changed by purchasing a higher tier. ntfy.sh offers multiple paid tiers, which allows for much hier limits than the ones listed above. @@ -3470,6 +3481,7 @@ table in their canonical form. | `X-Icon` | `Icon` | URL to use as notification [icon](#icons) | | `X-Filename` | `Filename`, `file`, `f` | Optional [attachment](#attachments) filename, as it appears in the client | | `X-Email` | `X-E-Mail`, `Email`, `E-Mail`, `mail`, `e` | E-mail address for [e-mail notifications](#e-mail-notifications) | +| `X-Call` | `Call` | Phone number for [phone calls](#phone-calls) | | `X-Cache` | `Cache` | Allows disabling [message caching](#message-caching) | | `X-Firebase` | `Firebase` | Allows disabling [sending to Firebase](#disable-firebase) | | `X-UnifiedPush` | `UnifiedPush`, `up` | [UnifiedPush](#unifiedpush) publish option, only to be used by UnifiedPush apps | diff --git a/docs/static/img/web-phone-verify.png b/docs/static/img/web-phone-verify.png new file mode 100644 index 0000000000000000000000000000000000000000..335aeef13848541002456ebbf93ebb17669d04e9 GIT binary patch literal 22959 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 h - You have a notification from notify on topic %s. Message: + You have a message from notify on topic %s. Message: %s - End message. + End of message. - This message was sent by user %s. It will be repeated up to three times. + This message was sent by user %s. It will be repeated three times. + To unsubscribe from calls like this, remove your phone number in the notify web app. Goodbye. @@ -97,11 +98,11 @@ func (s *Server) callPhoneInternal(data url.Values) (string, error) { return string(response), nil } -func (s *Server) verifyPhoneNumber(v *visitor, r *http.Request, phoneNumber string) error { - ev := logvr(v, r).Tag(tagTwilio).Field("twilio_to", phoneNumber).Debug("Sending phone verification") +func (s *Server) verifyPhoneNumber(v *visitor, r *http.Request, phoneNumber, channel string) error { + ev := logvr(v, r).Tag(tagTwilio).Field("twilio_to", phoneNumber).Field("twilio_channel", channel).Debug("Sending phone verification") data := url.Values{} data.Set("To", phoneNumber) - data.Set("Channel", "sms") + data.Set("Channel", channel) requestURL := fmt.Sprintf("%s/v2/Services/%s/Verifications", s.config.TwilioVerifyBaseURL, s.config.TwilioVerifyService) req, err := http.NewRequest(http.MethodPost, requestURL, strings.NewReader(data.Encode())) if err != nil { diff --git a/server/types.go b/server/types.go index a1d18926..3b733678 100644 --- a/server/types.go +++ b/server/types.go @@ -311,9 +311,14 @@ type apiAccountTokenResponse struct { Expires int64 `json:"expires,omitempty"` // Unix timestamp } -type apiAccountPhoneNumberRequest struct { +type apiAccountPhoneNumberVerifyRequest struct { + Number string `json:"number"` + Channel string `json:"channel"` +} + +type apiAccountPhoneNumberAddRequest struct { Number string `json:"number"` - Code string `json:"code,omitempty"` // Only supplied in "verify" call + Code string `json:"code,omitempty"` } type apiAccountTier struct { diff --git a/web/public/static/langs/en.json b/web/public/static/langs/en.json index f2120e5f..588a1f9f 100644 --- a/web/public/static/langs/en.json +++ b/web/public/static/langs/en.json @@ -188,17 +188,20 @@ "account_basics_password_dialog_button_submit": "Change password", "account_basics_password_dialog_current_password_incorrect": "Password incorrect", "account_basics_phone_numbers_title": "Phone numbers", - "account_basics_phone_numbers_dialog_description": "To use the call notification feature, you need to add and verify at least one phone number. Adding it will send a verification SMS to your phone.", + "account_basics_phone_numbers_dialog_description": "To use the call notification feature, you need to add and verify at least one phone number. Verification can be done via SMS or a phone call.", "account_basics_phone_numbers_description": "For phone call notifications", "account_basics_phone_numbers_no_phone_numbers_yet": "No phone numbers yet", "account_basics_phone_numbers_copied_to_clipboard": "Phone number copied to clipboard", "account_basics_phone_numbers_dialog_title": "Add phone number", "account_basics_phone_numbers_dialog_number_label": "Phone number", "account_basics_phone_numbers_dialog_number_placeholder": "e.g. +1222333444", - "account_basics_phone_numbers_dialog_send_verification_button": "Send verification", + "account_basics_phone_numbers_dialog_verify_button_sms": "Send SMS", + "account_basics_phone_numbers_dialog_verify_button_call": "Call me", "account_basics_phone_numbers_dialog_code_label": "Verification code", "account_basics_phone_numbers_dialog_code_placeholder": "e.g. 123456", "account_basics_phone_numbers_dialog_check_verification_button": "Confirm code", + "account_basics_phone_numbers_dialog_channel_sms": "SMS", + "account_basics_phone_numbers_dialog_channel_call": "Call", "account_usage_title": "Usage", "account_usage_of_limit": "of {{limit}}", "account_usage_unlimited": "Unlimited", diff --git a/web/src/app/AccountApi.js b/web/src/app/AccountApi.js index b5bfcd29..8908f306 100644 --- a/web/src/app/AccountApi.js +++ b/web/src/app/AccountApi.js @@ -299,14 +299,15 @@ class AccountApi { return await response.json(); // May throw SyntaxError } - async verifyPhoneNumber(phoneNumber) { + async verifyPhoneNumber(phoneNumber, channel) { const url = accountPhoneVerifyUrl(config.base_url); console.log(`[AccountApi] Sending phone verification ${url}`); await fetchOrThrow(url, { method: "PUT", headers: withBearerAuth({}, session.token()), body: JSON.stringify({ - number: phoneNumber + number: phoneNumber, + channel: channel }) }); } diff --git a/web/src/components/Account.js b/web/src/components/Account.js index b4a378e6..b480ea6b 100644 --- a/web/src/components/Account.js +++ b/web/src/components/Account.js @@ -1,13 +1,13 @@ import * as React from 'react'; import {useContext, useState} from 'react'; import { - Alert, + Alert, ButtonGroup, CardActions, CardContent, Chip, - FormControl, + FormControl, FormControlLabel, InputLabel, LinearProgress, Link, - Portal, + Portal, Radio, RadioGroup, Select, Snackbar, Stack, @@ -47,12 +47,14 @@ import {AccountContext} from "./App"; import DialogFooter from "./DialogFooter"; import {Paragraph} from "./styles"; import CloseIcon from "@mui/icons-material/Close"; -import {ContentCopy, Public} from "@mui/icons-material"; +import {Check, ContentCopy, DeleteForever, Public} from "@mui/icons-material"; import MenuItem from "@mui/material/MenuItem"; import DialogContentText from "@mui/material/DialogContentText"; import {IncorrectPasswordError, UnauthorizedError} from "../app/errors"; import {ProChip} from "./SubscriptionPopup"; import AddIcon from "@mui/icons-material/Add"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; const Account = () => { if (!session.exists()) { @@ -408,6 +410,7 @@ const AddPhoneNumberDialog = (props) => { const { t } = useTranslation(); const [error, setError] = useState(""); const [phoneNumber, setPhoneNumber] = useState(""); + const [channel, setChannel] = useState("sms"); const [code, setCode] = useState(""); const [sending, setSending] = useState(false); const [verificationCodeSent, setVerificationCodeSent] = useState(false); @@ -432,7 +435,7 @@ const AddPhoneNumberDialog = (props) => { const verifyPhone = async () => { try { setSending(true); - await accountApi.verifyPhoneNumber(phoneNumber); + await accountApi.verifyPhoneNumber(phoneNumber, channel); setVerificationCodeSent(true); } catch (e) { console.log(`[Account] Error sending verification`, e); @@ -471,18 +474,26 @@ const AddPhoneNumberDialog = (props) => { {t("account_basics_phone_numbers_dialog_description")} {!verificationCodeSent && - setPhoneNumber(ev.target.value)} - fullWidth - inputProps={{ inputMode: 'tel', pattern: '\+[0-9]*' }} - variant="standard" - /> +
+ setPhoneNumber(ev.target.value)} + inputProps={{ inputMode: 'tel', pattern: '\+[0-9]*' }} + variant="standard" + sx={{ flexGrow: 1 }} + /> + + + setChannel(e.target.value)} />} label={t("account_basics_phone_numbers_dialog_channel_sms")} /> + setChannel(e.target.value)} />} label={t("account_basics_phone_numbers_dialog_channel_call")} sx={{ marginRight: 0 }} /> + + +
} {verificationCodeSent && {