Self-review

This commit is contained in:
binwiederhier 2023-05-17 10:39:15 -04:00
parent 79a3259c86
commit ac029c389e
16 changed files with 201 additions and 133 deletions

View File

@ -856,8 +856,8 @@ billing-contact: "phil@example.com"
```
## Phone calls
ntfy supports phone calls via [Twilio](https://www.twilio.com/) as a phone call provider. If phone calls are enabled,
users can verify and add a phone number, and then receive phone calls when publish a message with the `X-Call` header.
ntfy supports phone calls via [Twilio](https://www.twilio.com/) as a call provider. If phone calls are enabled,
users can verify and add a phone number, and then receive phone calls when publishing a message using the `X-Call` header.
See [publishing page](publish.md#phone-calls) for more details.
To enable Twilio integration, sign up with [Twilio](https://www.twilio.com/), purchase a phone number (Toll free numbers

View File

@ -2709,7 +2709,7 @@ You may also simply pass `yes` as a value to pick the first of your verified pho
On ntfy.sh, this feature is only supported to [ntfy Pro](https://ntfy.sh/app) plans.
<figure markdown>
![e-mail publishing](static/img/web-phone-verify.png)
![phone number verification](static/img/web-phone-verify.png)
<figcaption>Phone number verification in the <a href="https://ntfy.sh/account">web app</a></figcaption>
</figure>

2
go.mod
View File

@ -33,7 +33,7 @@ require (
require (
cloud.google.com/go v0.110.2 // indirect
cloud.google.com/go/compute v1.19.2 // indirect
cloud.google.com/go/compute v1.19.3 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.0.1 // indirect
cloud.google.com/go/longrunning v0.4.2 // indirect

2
go.sum
View File

@ -4,6 +4,8 @@ cloud.google.com/go v0.110.2 h1:sdFPBr6xG9/wkBbfhmUz/JmZC7X6LavQgcrVINrKiVA=
cloud.google.com/go v0.110.2/go.mod h1:k04UEeEtb6ZBRTv3dZz4CeJC3jKGxyhl0sAiVVquxiw=
cloud.google.com/go/compute v1.19.2 h1:GbJtPo8OKVHbVep8jvM57KidbYHxeE68LOVqouNLrDY=
cloud.google.com/go/compute v1.19.2/go.mod h1:5f5a+iC1IriXYauaQ0EyQmEAEq9CGRnV5xJSQSlTV08=
cloud.google.com/go/compute v1.19.3 h1:DcTwsFgGev/wV5+q8o2fzgcHOaac+DKGC91ZlvpsQds=
cloud.google.com/go/compute v1.19.3/go.mod h1:qxvISKp/gYnXkSAD1ppcSOveRAmzxicEv/JlizULFrI=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
cloud.google.com/go/firestore v1.9.0 h1:IBlRyxgGySXu5VuW0RgGFlTtLukSnNkpDiEOMkQkmpA=

View File

@ -198,6 +198,30 @@ func TestLog_LevelOverride_ManyOnSameField(t *testing.T) {
require.Equal(t, "", File())
}
func TestLog_FieldIf(t *testing.T) {
t.Cleanup(resetState)
var out bytes.Buffer
SetOutput(&out)
SetLevel(DebugLevel)
SetFormat(JSONFormat)
Time(time.Unix(11, 0).UTC()).
FieldIf("trace_field", "manager", TraceLevel). // This is not logged
Field("tag", "manager").
Debug("trace_field is not logged")
SetLevel(TraceLevel)
Time(time.Unix(12, 0).UTC()).
FieldIf("trace_field", "manager", TraceLevel). // Now it is logged
Field("tag", "manager").
Debug("trace_field is logged")
expected := `{"time":"1970-01-01T00:00:11Z","level":"DEBUG","message":"trace_field is not logged","tag":"manager"}
{"time":"1970-01-01T00:00:12Z","level":"DEBUG","message":"trace_field is logged","tag":"manager","trace_field":"manager"}
`
require.Equal(t, expected, out.String())
}
func TestLog_UsingStdLogger_JSON(t *testing.T) {
t.Cleanup(resetState)

View File

@ -108,11 +108,12 @@ 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}
errHTTPBadRequestPhoneCallsDisabled = &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/config/#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}
errHTTPBadRequestPhoneNumberVerifyChannelInvalid = &errHTTP{40036, http.StatusBadRequest, "invalid request: verification channel must be 'sms' or 'call'", "https://ntfy.sh/docs/publish/#phone-calls", nil}
errHTTPBadRequestDelayNoCall = &errHTTP{40037, http.StatusBadRequest, "delayed call notifications are not supported", "", 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}

View File

@ -937,6 +937,9 @@ func (s *Server) parsePublishParams(r *http.Request, m *message) (cache bool, fi
if email != "" {
return false, false, "", "", false, errHTTPBadRequestDelayNoEmail // we cannot store the email address (yet)
}
if call != "" {
return false, false, "", "", false, errHTTPBadRequestDelayNoCall // we cannot store the phone number (yet)
}
delay, err := util.ParseFutureTime(delayStr, time.Now())
if err != nil {
return false, false, "", "", false, errHTTPBadRequestDelayCannotParse

View File

@ -581,7 +581,7 @@ func (s *Server) handleAccountPhoneNumberDelete(w http.ResponseWriter, r *http.R
return errHTTPBadRequestPhoneNumberInvalid
}
logvr(v, r).Tag(tagAccount).Field("phone_number", req.Number).Debug("Deleting phone number")
if err := s.userManager.DeletePhoneNumber(u.ID, req.Number); err != nil {
if err := s.userManager.RemovePhoneNumber(u.ID, req.Number); err != nil {
return err
}
return s.writeJSON(w, newSuccessResponse())

View File

@ -1190,7 +1190,20 @@ func TestServer_PublishDelayedEmail_Fail(t *testing.T) {
"E-Mail": "test@example.com",
"Delay": "20 min",
})
require.Equal(t, 400, response.Code)
require.Equal(t, 40003, toHTTPError(t, response.Body.String()).Code)
}
func TestServer_PublishDelayedCall_Fail(t *testing.T) {
c := newTestConfig(t)
c.TwilioAccount = "AC1234567890"
c.TwilioAuthToken = "AAEAA1234567890"
c.TwilioFromNumber = "+1234567890"
s := newTestServer(t, c)
response := request(t, s, "PUT", "/mytopic", "fail", map[string]string{
"Call": "yes",
"Delay": "20 min",
})
require.Equal(t, 40037, toHTTPError(t, response.Body.String()).Code)
}
func TestServer_PublishEmailNoMailer_Fail(t *testing.T) {

View File

@ -43,7 +43,7 @@ func TestServer_Twilio_Call_Add_Verify_Call_Delete_Success(t *testing.T) {
require.Nil(t, err)
require.Equal(t, "/2010-04-01/Accounts/AC1234567890/Calls.json", r.URL.Path)
require.Equal(t, "Basic QUMxMjM0NTY3ODkwOkFBRUFBMTIzNDU2Nzg5MA==", r.Header.Get("Authorization"))
require.Equal(t, "From=%2B1234567890&To=%2B12223334444&Twiml=%0A%3CResponse%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay+loop%3D%223%22%3E%0A%09%09You+have+a+notification+from+notify+on+topic+mytopic.+Message%3A%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09hi+there%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09End+message.%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09This+message+was+sent+by+user+phil.+It+will+be+repeated+up+to+three+times.%0A%09%09%3Cbreak+time%3D%223s%22%2F%3E%0A%09%3C%2FSay%3E%0A%09%3CSay%3EGoodbye.%3C%2FSay%3E%0A%3C%2FResponse%3E", string(body))
require.Equal(t, "From=%2B1234567890&To=%2B12223334444&Twiml=%0A%3CResponse%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay+loop%3D%223%22%3E%0A%09%09You+have+a+message+from+notify+on+topic+mytopic.+Message%3A%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09hi+there%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09End+of+message.%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09This+message+was+sent+by+user+phil.+It+will+be+repeated+three+times.%0A%09%09To+unsubscribe+from+calls+like+this%2C+remove+your+phone+number+in+the+notify+web+app.%0A%09%09%3Cbreak+time%3D%223s%22%2F%3E%0A%09%3C%2FSay%3E%0A%09%3CSay%3EGoodbye.%3C%2FSay%3E%0A%3C%2FResponse%3E", string(body))
called.Store(true)
}))
defer twilioCallsServer.Close()
@ -69,7 +69,7 @@ func TestServer_Twilio_Call_Add_Verify_Call_Delete_Success(t *testing.T) {
require.Nil(t, err)
// Send verification code for phone number
response := request(t, s, "PUT", "/v1/account/phone/verify", `{"number":"+12223334444"}`, map[string]string{
response := request(t, s, "PUT", "/v1/account/phone/verify", `{"number":"+12223334444","channel":"sms"}`, map[string]string{
"authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, response.Code)
@ -122,7 +122,7 @@ func TestServer_Twilio_Call_Success(t *testing.T) {
require.Nil(t, err)
require.Equal(t, "/2010-04-01/Accounts/AC1234567890/Calls.json", r.URL.Path)
require.Equal(t, "Basic QUMxMjM0NTY3ODkwOkFBRUFBMTIzNDU2Nzg5MA==", r.Header.Get("Authorization"))
require.Equal(t, "From=%2B1234567890&To=%2B11122233344&Twiml=%0A%3CResponse%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay+loop%3D%223%22%3E%0A%09%09You+have+a+notification+from+notify+on+topic+mytopic.+Message%3A%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09hi+there%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09End+message.%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09This+message+was+sent+by+user+phil.+It+will+be+repeated+up+to+three+times.%0A%09%09%3Cbreak+time%3D%223s%22%2F%3E%0A%09%3C%2FSay%3E%0A%09%3CSay%3EGoodbye.%3C%2FSay%3E%0A%3C%2FResponse%3E", string(body))
require.Equal(t, "From=%2B1234567890&To=%2B11122233344&Twiml=%0A%3CResponse%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay+loop%3D%223%22%3E%0A%09%09You+have+a+message+from+notify+on+topic+mytopic.+Message%3A%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09hi+there%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09End+of+message.%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09This+message+was+sent+by+user+phil.+It+will+be+repeated+three+times.%0A%09%09To+unsubscribe+from+calls+like+this%2C+remove+your+phone+number+in+the+notify+web+app.%0A%09%09%3Cbreak+time%3D%223s%22%2F%3E%0A%09%3C%2FSay%3E%0A%09%3CSay%3EGoodbye.%3C%2FSay%3E%0A%3C%2FResponse%3E", string(body))
called.Store(true)
}))
defer twilioServer.Close()
@ -167,7 +167,7 @@ func TestServer_Twilio_Call_Success_With_Yes(t *testing.T) {
require.Nil(t, err)
require.Equal(t, "/2010-04-01/Accounts/AC1234567890/Calls.json", r.URL.Path)
require.Equal(t, "Basic QUMxMjM0NTY3ODkwOkFBRUFBMTIzNDU2Nzg5MA==", r.Header.Get("Authorization"))
require.Equal(t, "From=%2B1234567890&To=%2B11122233344&Twiml=%0A%3CResponse%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay+loop%3D%223%22%3E%0A%09%09You+have+a+notification+from+notify+on+topic+mytopic.+Message%3A%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09hi+there%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09End+message.%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09This+message+was+sent+by+user+phil.+It+will+be+repeated+up+to+three+times.%0A%09%09%3Cbreak+time%3D%223s%22%2F%3E%0A%09%3C%2FSay%3E%0A%09%3CSay%3EGoodbye.%3C%2FSay%3E%0A%3C%2FResponse%3E", string(body))
require.Equal(t, "From=%2B1234567890&To=%2B11122233344&Twiml=%0A%3CResponse%3E%0A%09%3CPause+length%3D%221%22%2F%3E%0A%09%3CSay+loop%3D%223%22%3E%0A%09%09You+have+a+message+from+notify+on+topic+mytopic.+Message%3A%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09hi+there%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09End+of+message.%0A%09%09%3Cbreak+time%3D%221s%22%2F%3E%0A%09%09This+message+was+sent+by+user+phil.+It+will+be+repeated+three+times.%0A%09%09To+unsubscribe+from+calls+like+this%2C+remove+your+phone+number+in+the+notify+web+app.%0A%09%09%3Cbreak+time%3D%223s%22%2F%3E%0A%09%3C%2FSay%3E%0A%09%3CSay%3EGoodbye.%3C%2FSay%3E%0A%3C%2FResponse%3E", string(body))
called.Store(true)
}))
defer twilioServer.Close()

View File

@ -318,7 +318,7 @@ type apiAccountPhoneNumberVerifyRequest struct {
type apiAccountPhoneNumberAddRequest struct {
Number string `json:"number"`
Code string `json:"code,omitempty"`
Code string `json:"code"` // Only set when adding a phone number
}
type apiAccountTier struct {

View File

@ -117,7 +117,6 @@ const (
PRIMARY KEY (user_id, phone_number),
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
);
CREATE UNIQUE INDEX idx_user_phone_number ON user_phone (phone_number);
CREATE TABLE IF NOT EXISTS schemaVersion (
id INT PRIMARY KEY,
version INT NOT NULL
@ -420,7 +419,6 @@ const (
PRIMARY KEY (user_id, phone_number),
FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE CASCADE
);
CREATE UNIQUE INDEX idx_user_phone_number ON user_phone (phone_number);
`
)
@ -694,8 +692,8 @@ func (a *Manager) AddPhoneNumber(userID string, phoneNumber string) error {
return nil
}
// DeletePhoneNumber deletes a phone number from the user with the given user ID
func (a *Manager) DeletePhoneNumber(userID string, phoneNumber string) error {
// RemovePhoneNumber deletes a phone number from the user with the given user ID
func (a *Manager) RemovePhoneNumber(userID string, phoneNumber string) error {
_, err := a.db.Exec(deletePhoneNumberQuery, userID, phoneNumber)
return err
}

View File

@ -893,6 +893,38 @@ func TestManager_Tier_Change_And_Reset(t *testing.T) {
require.Nil(t, a.ResetTier("phil"))
}
func TestUser_PhoneNumberAddListRemove(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
phil, err := a.User("phil")
require.Nil(t, err)
require.Nil(t, a.AddPhoneNumber(phil.ID, "+1234567890"))
phoneNumbers, err := a.PhoneNumbers(phil.ID)
require.Nil(t, err)
require.Equal(t, 1, len(phoneNumbers))
require.Equal(t, "+1234567890", phoneNumbers[0])
require.Nil(t, a.RemovePhoneNumber(phil.ID, "+1234567890"))
phoneNumbers, err = a.PhoneNumbers(phil.ID)
require.Nil(t, err)
require.Equal(t, 0, len(phoneNumbers))
}
func TestUser_PhoneNumberAdd_Multiple_Users_Same_Number(t *testing.T) {
a := newTestManager(t, PermissionDenyAll)
require.Nil(t, a.AddUser("phil", "phil", RoleUser))
require.Nil(t, a.AddUser("ben", "ben", RoleUser))
phil, err := a.User("phil")
require.Nil(t, err)
ben, err := a.User("ben")
require.Nil(t, err)
require.Nil(t, a.AddPhoneNumber(phil.ID, "+1234567890"))
require.Nil(t, a.AddPhoneNumber(ben.ID, "+1234567890"))
}
func TestSqliteCache_Migration_From1(t *testing.T) {
filename := filepath.Join(t.TempDir(), "user.db")
db, err := sql.Open("sqlite3", filename)

202
web/package-lock.json generated
View File

@ -3134,14 +3134,14 @@
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A=="
},
"node_modules/@mui/base": {
"version": "5.0.0-beta.0",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.0.tgz",
"integrity": "sha512-ap+juKvt8R8n3cBqd/pGtZydQ4v2I/hgJKnvJRGjpSh3RvsvnDHO4rXov8MHQlH6VqpOekwgilFLGxMZjNTucA==",
"version": "5.0.0-beta.1",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.1.tgz",
"integrity": "sha512-xrkDCeu3JQE+JjJUnJnOrdQJMXwKhbV4AW+FRjMIj5i9cHK3BAuatG/iqbf1M+jklVWLk0KdbgioKwK+03aYbA==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"@emotion/is-prop-valid": "^1.2.0",
"@mui/types": "^7.2.4",
"@mui/utils": "^5.12.3",
"@mui/utils": "^5.13.1",
"@popperjs/core": "^2.11.7",
"clsx": "^1.2.1",
"prop-types": "^15.8.1",
@ -3166,9 +3166,9 @@
}
},
"node_modules/@mui/core-downloads-tracker": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.0.tgz",
"integrity": "sha512-5nXz2k8Rv2ZjtQY6kXirJVyn2+ODaQuAJmXSJtLDUQDKWp3PFUj6j3bILqR0JGOs9R5ejgwz3crLKsl6GwjwkQ==",
"version": "5.13.1",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.1.tgz",
"integrity": "sha512-qDHtNDO72NcBQMhaWBt9EZMvNiO+OXjPg5Sdk/6LgRDw6Zr3HdEZ5n2FJ/qtYsaT/okGyCuQavQkcZCOCEVf/g==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui"
@ -3200,16 +3200,16 @@
}
},
"node_modules/@mui/material": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.13.0.tgz",
"integrity": "sha512-ckS+9tCpAzpdJdaTF+btF0b6mF9wbXg/EVKtnoAWYi0UKXoXBAVvEUMNpLGA5xdpCdf+A6fPbVUEHs9TsfU+Yw==",
"version": "5.13.1",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.13.1.tgz",
"integrity": "sha512-qSnbJZer8lIuDYFDv19/t3s0AXYY9SxcOdhCnGvetRSfOG4gy3TkiFXNCdW5OLNveTieiMpOuv46eXUmE3ZA6A==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"@mui/base": "5.0.0-beta.0",
"@mui/core-downloads-tracker": "^5.13.0",
"@mui/system": "^5.12.3",
"@mui/base": "5.0.0-beta.1",
"@mui/core-downloads-tracker": "^5.13.1",
"@mui/system": "^5.13.1",
"@mui/types": "^7.2.4",
"@mui/utils": "^5.12.3",
"@mui/utils": "^5.13.1",
"@types/react-transition-group": "^4.4.6",
"clsx": "^1.2.1",
"csstype": "^3.1.2",
@ -3244,12 +3244,12 @@
}
},
"node_modules/@mui/private-theming": {
"version": "5.12.3",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.12.3.tgz",
"integrity": "sha512-o1e7Z1Bp27n4x2iUHhegV4/Jp6H3T6iBKHJdLivS5GbwsuAE/5l4SnZ+7+K+e5u9TuhwcAKZLkjvqzkDe8zqfA==",
"version": "5.13.1",
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.13.1.tgz",
"integrity": "sha512-HW4npLUD9BAkVppOUZHeO1FOKUJWAwbpy0VQoGe3McUYTlck1HezGHQCfBQ5S/Nszi7EViqiimECVl9xi+/WjQ==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"@mui/utils": "^5.12.3",
"@mui/utils": "^5.13.1",
"prop-types": "^15.8.1"
},
"engines": {
@ -3301,15 +3301,15 @@
}
},
"node_modules/@mui/system": {
"version": "5.12.3",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.12.3.tgz",
"integrity": "sha512-JB/6sypHqeJCqwldWeQ1MKkijH829EcZAKKizxbU2MJdxGG5KSwZvTBa5D9qiJUA1hJFYYupjiuy9ZdJt6rV6w==",
"version": "5.13.1",
"resolved": "https://registry.npmjs.org/@mui/system/-/system-5.13.1.tgz",
"integrity": "sha512-BsDUjhiO6ZVAvzKhnWBHLZ5AtPJcdT+62VjnRLyA4isboqDKLg4fmYIZXq51yndg/soDK9RkY5lYZwEDku13Ow==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"@mui/private-theming": "^5.12.3",
"@mui/private-theming": "^5.13.1",
"@mui/styled-engine": "^5.12.3",
"@mui/types": "^7.2.4",
"@mui/utils": "^5.12.3",
"@mui/utils": "^5.13.1",
"clsx": "^1.2.1",
"csstype": "^3.1.2",
"prop-types": "^15.8.1"
@ -3353,13 +3353,13 @@
}
},
"node_modules/@mui/utils": {
"version": "5.12.3",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.12.3.tgz",
"integrity": "sha512-D/Z4Ub3MRl7HiUccid7sQYclTr24TqUAQFFlxHQF8FR177BrCTQ0JJZom7EqYjZCdXhwnSkOj2ph685MSKNtIA==",
"version": "5.13.1",
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.13.1.tgz",
"integrity": "sha512-6lXdWwmlUbEU2jUI8blw38Kt+3ly7xkmV9ljzY4Q20WhsJMWiNry9CX8M+TaP/HbtuyR8XKsdMgQW7h7MM3n3A==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"@types/prop-types": "^15.7.5",
"@types/react-is": "^16.7.1 || ^17.0.0",
"@types/react-is": "^18.2.0",
"prop-types": "^15.8.1",
"react-is": "^18.2.0"
},
@ -4016,9 +4016,9 @@
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
},
"node_modules/@types/node": {
"version": "20.1.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.4.tgz",
"integrity": "sha512-At4pvmIOki8yuwLtd7BNHl3CiWNbtclUbNtScGx4OHfBd4/oWoJC8KRCIxXwkdndzhxOsPXihrsOoydxBjlE9Q=="
"version": "20.1.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.7.tgz",
"integrity": "sha512-WCuw/o4GSwDGMoonES8rcvwsig77dGCMbZDrZr2x4ZZiNW4P/gcoZXe/0twgtobcTkmg9TuKflxYL/DuwDyJzg=="
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
@ -4061,21 +4061,11 @@
}
},
"node_modules/@types/react-is": {
"version": "17.0.4",
"resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.4.tgz",
"integrity": "sha512-FLzd0K9pnaEvKz4D1vYxK9JmgQPiGk1lu23o1kqGsLeT0iPbRSF7b76+S5T9fD8aRa0B8bY7I/3DebEj+1ysBA==",
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/@types/react-is/-/react-is-18.2.0.tgz",
"integrity": "sha512-1vz2yObaQkLL7YFe/pme2cpvDsCwI1WXIfL+5eLz0MI9gFG24Re16RzUsI8t9XZn9ZWvgLNDrJBmrqXJO7GNQQ==",
"dependencies": {
"@types/react": "^17"
}
},
"node_modules/@types/react-is/node_modules/@types/react": {
"version": "17.0.59",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.59.tgz",
"integrity": "sha512-gSON5zWYIGyoBcycCE75E9+r6dCC2dHdsrVkOEiIYNU5+Q28HcBAuqvDuxHcCbMfHBHdeT5Tva/AFn3rnMKE4g==",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
"@types/react": "*"
}
},
"node_modules/@types/react-transition-group": {
@ -4175,14 +4165,14 @@
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA=="
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.59.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.5.tgz",
"integrity": "sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg==",
"version": "5.59.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.6.tgz",
"integrity": "sha512-sXtOgJNEuRU5RLwPUb1jxtToZbgvq3M6FPpY4QENxoOggK+UpTxUBpj6tD8+Qh2g46Pi9We87E+eHnUw8YcGsw==",
"dependencies": {
"@eslint-community/regexpp": "^4.4.0",
"@typescript-eslint/scope-manager": "5.59.5",
"@typescript-eslint/type-utils": "5.59.5",
"@typescript-eslint/utils": "5.59.5",
"@typescript-eslint/scope-manager": "5.59.6",
"@typescript-eslint/type-utils": "5.59.6",
"@typescript-eslint/utils": "5.59.6",
"debug": "^4.3.4",
"grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0",
@ -4208,11 +4198,11 @@
}
},
"node_modules/@typescript-eslint/experimental-utils": {
"version": "5.59.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.59.5.tgz",
"integrity": "sha512-ArcSSBifznsKNA/p4h2w3Olt/T8AZf3bNglxD8OnuTsSDJbRpjPPmI8qpr6ijyvk1J/T3GMJHwRIluS/Kuz9kA==",
"version": "5.59.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.59.6.tgz",
"integrity": "sha512-UIVfEaaHggOuhgqdpFlFQ7IN9UFMCiBR/N7uPBUyUlwNdJzYfAu9m4wbOj0b59oI/HSPW1N63Q7lsvfwTQY13w==",
"dependencies": {
"@typescript-eslint/utils": "5.59.5"
"@typescript-eslint/utils": "5.59.6"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -4226,13 +4216,13 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "5.59.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.5.tgz",
"integrity": "sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==",
"version": "5.59.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.6.tgz",
"integrity": "sha512-7pCa6al03Pv1yf/dUg/s1pXz/yGMUBAw5EeWqNTFiSueKvRNonze3hma3lhdsOrQcaOXhbk5gKu2Fludiho9VA==",
"dependencies": {
"@typescript-eslint/scope-manager": "5.59.5",
"@typescript-eslint/types": "5.59.5",
"@typescript-eslint/typescript-estree": "5.59.5",
"@typescript-eslint/scope-manager": "5.59.6",
"@typescript-eslint/types": "5.59.6",
"@typescript-eslint/typescript-estree": "5.59.6",
"debug": "^4.3.4"
},
"engines": {
@ -4252,12 +4242,12 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.59.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.5.tgz",
"integrity": "sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==",
"version": "5.59.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.6.tgz",
"integrity": "sha512-gLbY3Le9Dxcb8KdpF0+SJr6EQ+hFGYFl6tVY8VxLPFDfUZC7BHFw+Vq7bM5lE9DwWPfx4vMWWTLGXgpc0mAYyQ==",
"dependencies": {
"@typescript-eslint/types": "5.59.5",
"@typescript-eslint/visitor-keys": "5.59.5"
"@typescript-eslint/types": "5.59.6",
"@typescript-eslint/visitor-keys": "5.59.6"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -4268,12 +4258,12 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "5.59.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.5.tgz",
"integrity": "sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg==",
"version": "5.59.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.6.tgz",
"integrity": "sha512-A4tms2Mp5yNvLDlySF+kAThV9VTBPCvGf0Rp8nl/eoDX9Okun8byTKoj3fJ52IJitjWOk0fKPNQhXEB++eNozQ==",
"dependencies": {
"@typescript-eslint/typescript-estree": "5.59.5",
"@typescript-eslint/utils": "5.59.5",
"@typescript-eslint/typescript-estree": "5.59.6",
"@typescript-eslint/utils": "5.59.6",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
@ -4294,9 +4284,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "5.59.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.5.tgz",
"integrity": "sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==",
"version": "5.59.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.6.tgz",
"integrity": "sha512-tH5lBXZI7T2MOUgOWFdVNUILsI02shyQvfzG9EJkoONWugCG77NDDa1EeDGw7oJ5IvsTAAGVV8I3Tk2PNu9QfA==",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
@ -4306,12 +4296,12 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "5.59.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.5.tgz",
"integrity": "sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==",
"version": "5.59.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.6.tgz",
"integrity": "sha512-vW6JP3lMAs/Tq4KjdI/RiHaaJSO7IUsbkz17it/Rl9Q+WkQ77EOuOnlbaU8kKfVIOJxMhnRiBG+olE7f3M16DA==",
"dependencies": {
"@typescript-eslint/types": "5.59.5",
"@typescript-eslint/visitor-keys": "5.59.5",
"@typescript-eslint/types": "5.59.6",
"@typescript-eslint/visitor-keys": "5.59.6",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@ -4332,16 +4322,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "5.59.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.5.tgz",
"integrity": "sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA==",
"version": "5.59.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.6.tgz",
"integrity": "sha512-vzaaD6EXbTS29cVH0JjXBdzMt6VBlv+hE31XktDRMX1j3462wZCJa7VzO2AxXEXcIl8GQqZPcOPuW/Z1tZVogg==",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@types/json-schema": "^7.0.9",
"@types/semver": "^7.3.12",
"@typescript-eslint/scope-manager": "5.59.5",
"@typescript-eslint/types": "5.59.5",
"@typescript-eslint/typescript-estree": "5.59.5",
"@typescript-eslint/scope-manager": "5.59.6",
"@typescript-eslint/types": "5.59.6",
"@typescript-eslint/typescript-estree": "5.59.6",
"eslint-scope": "^5.1.1",
"semver": "^7.3.7"
},
@ -4377,11 +4367,11 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "5.59.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.5.tgz",
"integrity": "sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA==",
"version": "5.59.6",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.6.tgz",
"integrity": "sha512-zEfbFLzB9ETcEJ4HZEEsCR9HHeNku5/Qw1jSS5McYJv5BR+ftYXwFFAH5Al+xkGaZEqowMwl7uoJjQb1YSPF8Q==",
"dependencies": {
"@typescript-eslint/types": "5.59.5",
"@typescript-eslint/types": "5.59.6",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
@ -4956,9 +4946,9 @@
}
},
"node_modules/axe-core": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz",
"integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==",
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.1.tgz",
"integrity": "sha512-sCXXUhA+cljomZ3ZAwb8i1p3oOlkABzPy08ZDAoGcYuvtBPlQ1Ytde129ArXyHWDhfeewq7rlx9F+cUx2SSlkg==",
"engines": {
"node": ">=4"
}
@ -5511,9 +5501,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001487",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001487.tgz",
"integrity": "sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==",
"version": "1.0.30001488",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001488.tgz",
"integrity": "sha512-NORIQuuL4xGpIy6iCCQGN4iFjlBXtfKWIenlUuyZJumLRIindLb7wXM+GO8erEhb7vXfcnf4BAg2PrSDN5TNLQ==",
"funding": [
{
"type": "opencollective",
@ -6749,9 +6739,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.394",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.394.tgz",
"integrity": "sha512-0IbC2cfr8w5LxTz+nmn2cJTGafsK9iauV2r5A5scfzyovqLrxuLoxOHE5OBobP3oVIggJT+0JfKnw9sm87c8Hw=="
"version": "1.4.397",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.397.tgz",
"integrity": "sha512-jwnPxhh350Q/aMatQia31KAIQdhEsYS0fFZ0BQQlN9tfvOEwShu6ZNwI4kL/xBabjcB/nTy6lSt17kNIluJZ8Q=="
},
"node_modules/emittery": {
"version": "0.8.1",
@ -9146,9 +9136,9 @@
}
},
"node_modules/is-core-module": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz",
"integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==",
"version": "2.12.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz",
"integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==",
"dependencies": {
"has": "^1.0.3"
},
@ -13879,9 +13869,9 @@
}
},
"node_modules/postcss-selector-parser": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.12.tgz",
"integrity": "sha512-NdxGCAZdRrwVI1sy59+Wzrh+pMMHxapGnpfenDVlMEXoOcvt4pGE0JLK9YY2F5dLxcFYA/YbVQKhcGU+FtSYQg==",
"version": "6.0.13",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
"integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
"dependencies": {
"cssesc": "^3.0.0",
"util-deprecate": "^1.0.2"
@ -15976,9 +15966,9 @@
}
},
"node_modules/terser": {
"version": "5.17.3",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.17.3.tgz",
"integrity": "sha512-AudpAZKmZHkG9jueayypz4duuCFJMMNGRMwaPvQKWfxKedh8Z2x3OCoDqIIi1xx5+iwx1u6Au8XQcc9Lke65Yg==",
"version": "5.17.4",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.17.4.tgz",
"integrity": "sha512-jcEKZw6UPrgugz/0Tuk/PVyLAPfMBJf5clnGueo45wTweoV8yh7Q7PEkhkJ5uuUbC7zAxEcG3tqNr1bstkQ8nw==",
"dependencies": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",

View File

@ -1,14 +1,16 @@
import {
accountBillingPortalUrl,
accountBillingSubscriptionUrl,
accountPasswordUrl, accountPhoneUrl, accountPhoneVerifyUrl,
accountPasswordUrl,
accountPhoneUrl,
accountPhoneVerifyUrl,
accountReservationSingleUrl,
accountReservationUrl,
accountSettingsUrl,
accountSubscriptionSingleUrl,
accountSubscriptionUrl,
accountTokenUrl,
accountUrl, maybeWithBearerAuth,
accountUrl,
maybeWithBearerAuth,
tiersUrl,
withBasicAuth,
withBearerAuth
@ -18,7 +20,7 @@ import subscriptionManager from "./SubscriptionManager";
import i18n from "i18next";
import prefs from "./Prefs";
import routes from "../components/routes";
import {fetchOrThrow, throwAppError, UnauthorizedError} from "./errors";
import {fetchOrThrow, UnauthorizedError} from "./errors";
const delayMillis = 45000; // 45 seconds
const intervalMillis = 900000; // 15 minutes

View File

@ -1,13 +1,17 @@
import * as React from 'react';
import {useContext, useState} from 'react';
import {
Alert, ButtonGroup,
Alert,
CardActions,
CardContent, Chip,
FormControl, FormControlLabel, InputLabel,
CardContent,
Chip,
FormControl,
FormControlLabel,
LinearProgress,
Link,
Portal, Radio, RadioGroup,
Portal,
Radio,
RadioGroup,
Select,
Snackbar,
Stack,
@ -47,14 +51,12 @@ import {AccountContext} from "./App";
import DialogFooter from "./DialogFooter";
import {Paragraph} from "./styles";
import CloseIcon from "@mui/icons-material/Close";
import {Check, ContentCopy, DeleteForever, Public} from "@mui/icons-material";
import {ContentCopy, 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()) {
@ -427,6 +429,7 @@ const AddPhoneNumberDialog = (props) => {
const handleCancel = () => {
if (verificationCodeSent) {
setVerificationCodeSent(false);
setCode("");
} else {
props.onClose();
}