Merge branch 'main' into twilio

pull/717/head
binwiederhier 2023-05-15 20:02:51 -04:00
commit 69b01bc468
23 changed files with 2593 additions and 267 deletions

View File

@ -104,6 +104,7 @@ I've added a ⭐ to projects or posts that have a significant following, or had
- [ntfy_on_a_chip](https://github.com/gergepalfi/ntfy_on_a_chip) - ESP8266 and ESP32 client code to communicate with ntfy
- [ntfy-sdk](https://github.com/yukibtc/ntfy-sdk) - ntfy client library to send notifications (Rust)
- [ntfy_ynh](https://github.com/YunoHost-Apps/ntfy_ynh) - ntfy app for YunoHost
- [woodpecker-ntfy](https://codeberg.org/l-x/woodpecker-ntfy)- Woodpecker CI plugin for sending ntfy notfication from a pipeline (Go)
- [drone-ntfy](https://github.com/Clortox/drone-ntfy) - Drone.io plugin for sending ntfy notifications from a pipeline (Shell)
- [ignition-ntfy-module](https://github.com/Kyvis-Labs/ignition-ntfy-module) - Adds support for sending notifications via a ntfy server to Ignition (Java)
- [maubot-ntfy](https://gitlab.com/999eagle/maubot-ntfy) - Matrix bot to subscribe to ntfy topics and send messages to Matrix (Python)

View File

@ -2702,7 +2702,7 @@ 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). To forward a message as a phone call, pass a phone number
Phone numbers have to be previously verified (via the web app). 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 if you only have one verified phone number.

View File

@ -1182,13 +1182,17 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release
**Features:**
* Support for SMS and voice calls using Twilio (no ticket)
* Support for text-to-speech style [phone calls](publish.md#phone-calls) using the `X-Call` header (no ticket)
* Admin API to manage users and ACL, `v1/users` + `v1/users/access` ([#722](https://github.com/binwiederhier/ntfy/issues/722), thanks to [@CreativeWarlock](https://github.com/CreativeWarlock) for sponsoring this ticket)
**Bug fixes + maintenance:**
* Removed old ntfy website from ntfy entirely (no ticket)
* Make emoji lookup for emails more efficient ([#725](https://github.com/binwiederhier/ntfy/pull/725), thanks to [@adamantike](https://github.com/adamantike))
* Fix potential subscriber ID clash ([#712](https://github.com/binwiederhier/ntfy/issues/712), thanks to [@peterbourgon](https://github.com/peterbourgon) for reporting, and [@dropdevrahul](https://github.com/dropdevrahul) for fixing)
* Support for `quoted-printable` in incoming emails ([#719](https://github.com/binwiederhier/ntfy/pull/719), thanks to [@Aerion](https://github.com/Aerion))
* Attachments with filenames that are downloaded using a browser will now download with the proper filename ([#726](https://github.com/binwiederhier/ntfy/issues/726), thanks to [@un99known99](https://github.com/un99known99) for reporting, and [@wunter8](https://github.com/wunter8) for fixing)
* Fix web app i18n issue in account preferences ([#730](https://github.com/binwiederhier/ntfy/issues/730), thanks to [@codebude](https://github.com/codebude) for reporting)
### ntfy Android app v1.16.1 (UNRELEASED)

19
go.mod
View File

@ -14,12 +14,12 @@ require (
github.com/olebedev/when v0.0.0-20221205223600-4d190b02b8d8
github.com/stretchr/testify v1.8.1
github.com/urfave/cli/v2 v2.25.3
golang.org/x/crypto v0.8.0
golang.org/x/oauth2 v0.7.0 // indirect
golang.org/x/crypto v0.9.0
golang.org/x/oauth2 v0.8.0 // indirect
golang.org/x/sync v0.2.0
golang.org/x/term v0.8.0
golang.org/x/time v0.3.0
google.golang.org/api v0.121.0
google.golang.org/api v0.122.0
gopkg.in/yaml.v2 v2.4.0
)
@ -28,15 +28,15 @@ require github.com/pkg/errors v0.9.1 // indirect
require (
firebase.google.com/go/v4 v4.11.0
github.com/prometheus/client_golang v1.15.1
github.com/stripe/stripe-go/v74 v74.17.0
github.com/stripe/stripe-go/v74 v74.18.0
)
require (
cloud.google.com/go v0.110.1 // indirect
cloud.google.com/go/compute v1.19.1 // indirect
cloud.google.com/go v0.110.2 // indirect
cloud.google.com/go/compute v1.19.2 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.0.0 // indirect
cloud.google.com/go/longrunning v0.4.1 // indirect
cloud.google.com/go/iam v1.0.1 // indirect
cloud.google.com/go/longrunning v0.4.2 // indirect
github.com/AlekSi/pointer v1.2.0 // indirect
github.com/MicahParks/keyfunc v1.9.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
@ -57,12 +57,11 @@ require (
github.com/prometheus/client_model v0.4.0 // indirect
github.com/prometheus/common v0.43.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect

49
go.sum
View File

@ -1,19 +1,25 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys=
cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY=
cloud.google.com/go v0.110.1 h1:oDJ19Fu9TX9Xs06iyCw4yifSqZ7JQ8BeuVHcTmWQlOA=
cloud.google.com/go v0.110.1/go.mod h1:uc+V/WjzxQ7vpkxfJhgW4Q4axWXyfAerpQOuSNDZyFw=
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.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY=
cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE=
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/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=
cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE=
cloud.google.com/go/iam v1.0.0 h1:hlQJMovyJJwYjZcTohUH4o1L8Z8kYz+E+W/zktiLCBc=
cloud.google.com/go/iam v1.0.0/go.mod h1:ikbQ4f1r91wTmBmmOtBCOtuEOei6taatNXytzB7Cxew=
cloud.google.com/go/iam v1.0.1 h1:lyeCAU6jpnVNrE9zGQkTl3WgNgK/X+uWwaw0kynZJMU=
cloud.google.com/go/iam v1.0.1/go.mod h1:yR3tmSL8BcZB4bxByRv2jkSIahVmCtfKZwLYGBalRE8=
cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
cloud.google.com/go/longrunning v0.4.2 h1:WDKiiNXFTaQ6qz/G8FCOkuY9kJmOJGY67wPUC1M2RbE=
cloud.google.com/go/longrunning v0.4.2/go.mod h1:OHrnaYyLUV6oqwh0xiS7e5sLQhP1m0QU9R+WhGDMgIQ=
cloud.google.com/go/storage v1.30.1 h1:uOdMxAs8HExqBlnLtnQyP0YkvbiDpdGShGKtx6U/oNM=
cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E=
firebase.google.com/go/v4 v4.11.0 h1:szjBoiF33A2FavRLIDZjW1mw+OsW/XAtHoYNIqWOjRk=
@ -71,7 +77,6 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
@ -94,8 +99,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw=
github.com/google/s2a-go v0.1.2 h1:WVtYAYuYxKeYajAmThMRYWP6K3wXkcqbGHeUgeubUHY=
github.com/google/s2a-go v0.1.2/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM=
github.com/google/s2a-go v0.1.3 h1:FAgZmpLl/SXurPEZyCMPBIiiYeTbqfjlbdnCNTAkbGE=
github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -121,26 +124,17 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.15.0 h1:5fCgGYogn0hFdhyhLbw7hEsWxufKtY9klyvdNfFlFhM=
github.com/prometheus/client_golang v1.15.0/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
github.com/prometheus/common v0.43.0 h1:iq+BVjvYLei5f27wiuNiB1DN6DYQkp1c8Bx0Vykh5us=
github.com/prometheus/common v0.43.0/go.mod h1:NCvr5cQIh3Y/gy73/RdVtC9r8xxrxwJnB+2lB3BxrFc=
github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -153,12 +147,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stripe/stripe-go/v74 v74.15.0 h1:P3ZYrY4CdZeV8Pc/205utqjur+5gcTef+9hgtj8P8IY=
github.com/stripe/stripe-go/v74 v74.15.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
github.com/stripe/stripe-go/v74 v74.17.0 h1:qVWSzmADr6gudznuAcPjB9ewzgxfyIhBCkyTbkxJcCw=
github.com/stripe/stripe-go/v74 v74.17.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw=
github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/stripe/stripe-go/v74 v74.18.0 h1:ImSIoaVkTUozHxa21AhwHYBjwc8fVSJJJB1Q7oaXzIw=
github.com/stripe/stripe-go/v74 v74.18.0/go.mod h1:f9L6LvaXa35ja7eyvP6GQswoaIPaBRvGAimAO+udbBw=
github.com/urfave/cli/v2 v2.25.3 h1:VJkt6wvEBOoSjPFQvOkv6iWIrsJyCrKGtCtxXWwmGeY=
github.com/urfave/cli/v2 v2.25.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
@ -173,6 +165,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -190,23 +184,23 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g=
golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4=
golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8=
golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -217,17 +211,12 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -252,10 +241,10 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.120.0 h1:TTmhTei0mkR+kiBSW2UzZmAbkTaBfUUzfchyXnzG9Hs=
google.golang.org/api v0.120.0/go.mod h1:CrSvlNEFCFLae9ZUtL1z+61+rEBD7J/aCYwVYKZoWFU=
google.golang.org/api v0.121.0 h1:8Oopoo8Vavxx6gt+sgs8s8/X60WBAtKQq6JqnkF+xow=
google.golang.org/api v0.121.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms=
google.golang.org/api v0.122.0 h1:zDobeejm3E7pEG1mNHvdxvjs5XJoCMzyNH+CmwL94Es=
google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
@ -276,8 +265,6 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag=
google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g=
google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag=
google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=

View File

@ -106,8 +106,10 @@ var (
errHTTPBadRequestNotAPaidUser = &errHTTP{40027, http.StatusBadRequest, "invalid request: not a paid user", "", nil}
errHTTPBadRequestBillingRequestInvalid = &errHTTP{40028, http.StatusBadRequest, "invalid request: not a valid billing request", "", nil}
errHTTPBadRequestBillingSubscriptionExists = &errHTTP{40029, http.StatusBadRequest, "invalid request: billing subscription already exists", "", nil}
errHTTPBadRequestTwilioDisabled = &errHTTP{40030, http.StatusBadRequest, "invalid request: Calling is disabled", "https://ntfy.sh/docs/publish/#phone-calls", nil}
errHTTPBadRequestPhoneNumberInvalid = &errHTTP{40031, http.StatusBadRequest, "invalid request: phone number invalid", "https://ntfy.sh/docs/publish/#phone-calls", 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}
errHTTPBadRequestPhoneNumberInvalid = &errHTTP{40033, http.StatusBadRequest, "invalid request: phone number invalid", "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}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -82,6 +82,8 @@ var (
apiHealthPath = "/v1/health"
apiStatsPath = "/v1/stats"
apiTiersPath = "/v1/tiers"
apiUsersPath = "/v1/users"
apiUsersAccessPath = "/v1/users/access"
apiAccountPath = "/v1/account"
apiAccountTokenPath = "/v1/account/token"
apiAccountPasswordPath = "/v1/account/password"
@ -413,6 +415,16 @@ func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visit
return s.handleHealth(w, r, v)
} else if r.Method == http.MethodGet && r.URL.Path == webConfigPath {
return s.ensureWebEnabled(s.handleWebConfig)(w, r, v)
} else if r.Method == http.MethodGet && r.URL.Path == apiUsersPath {
return s.ensureAdmin(s.handleUsersGet)(w, r, v)
} else if r.Method == http.MethodPut && r.URL.Path == apiUsersPath {
return s.ensureAdmin(s.handleUsersAdd)(w, r, v)
} else if r.Method == http.MethodDelete && r.URL.Path == apiUsersPath {
return s.ensureAdmin(s.handleUsersDelete)(w, r, v)
} else if (r.Method == http.MethodPut || r.Method == http.MethodPost) && r.URL.Path == apiUsersAccessPath {
return s.ensureAdmin(s.handleAccessAllow)(w, r, v)
} else if r.Method == http.MethodDelete && r.URL.Path == apiUsersAccessPath {
return s.ensureAdmin(s.handleAccessReset)(w, r, v)
} else if r.Method == http.MethodPost && r.URL.Path == apiAccountPath {
return s.ensureUserManager(s.handleAccountCreate)(w, r, v)
} else if r.Method == http.MethodGet && r.URL.Path == apiAccountPath {
@ -651,6 +663,9 @@ func (s *Server) handleFile(w http.ResponseWriter, r *http.Request, v *visitor)
return err
}
defer f.Close()
if m.Attachment.Name != "" {
w.Header().Set("Content-Disposition", "attachment; filename="+strconv.Quote(m.Attachment.Name))
}
_, err = io.Copy(util.NewContentTypeWriter(w, r.URL.Path), f)
return err
}
@ -1221,7 +1236,7 @@ func (s *Server) handleSubscribeWS(w http.ResponseWriter, r *http.Request, v *vi
}
defer conn.Close()
// Subscription connections can be canceled externally, see topic.CancelSubscribers
// Subscription connections can be canceled externally, see topic.CancelSubscribersExceptUser
cancelCtx, cancel := context.WithCancel(context.Background())
defer cancel()
@ -1463,6 +1478,7 @@ func (s *Server) handleOptions(w http.ResponseWriter, _ *http.Request, _ *visito
return nil
}
// topicFromPath returns the topic from a root path (e.g. /mytopic), creating it if it doesn't exist.
func (s *Server) topicFromPath(path string) (*topic, error) {
parts := strings.Split(path, "/")
if len(parts) < 2 {
@ -1471,6 +1487,7 @@ func (s *Server) topicFromPath(path string) (*topic, error) {
return s.topicFromID(parts[1])
}
// topicsFromPath returns the topic from a root path (e.g. /mytopic,mytopic2), creating it if it doesn't exist.
func (s *Server) topicsFromPath(path string) ([]*topic, string, error) {
parts := strings.Split(path, "/")
if len(parts) < 2 {
@ -1484,6 +1501,7 @@ func (s *Server) topicsFromPath(path string) ([]*topic, string, error) {
return topics, parts[1], nil
}
// topicsFromIDs returns the topics with the given IDs, creating them if they don't exist.
func (s *Server) topicsFromIDs(ids ...string) ([]*topic, error) {
s.mu.Lock()
defer s.mu.Unlock()
@ -1503,6 +1521,7 @@ func (s *Server) topicsFromIDs(ids ...string) ([]*topic, error) {
return topics, nil
}
// topicFromID returns the topic with the given ID, creating it if it doesn't exist.
func (s *Server) topicFromID(id string) (*topic, error) {
topics, err := s.topicsFromIDs(id)
if err != nil {
@ -1511,6 +1530,23 @@ func (s *Server) topicFromID(id string) (*topic, error) {
return topics[0], nil
}
// topicsFromPattern returns a list of topics matching the given pattern, but it does not create them.
func (s *Server) topicsFromPattern(pattern string) ([]*topic, error) {
s.mu.RLock()
defer s.mu.RUnlock()
patternRegexp, err := regexp.Compile("^" + strings.ReplaceAll(pattern, "*", ".*") + "$")
if err != nil {
return nil, err
}
topics := make([]*topic, 0)
for _, t := range s.topics {
if patternRegexp.MatchString(t.ID) {
topics = append(topics, t)
}
}
return topics, nil
}
func (s *Server) runSMTPServer() error {
s.smtpServerBackend = newMailBackend(s.config, s.handle)
s.smtpServer = smtp.NewServer(s.smtpServerBackend)

View File

@ -454,7 +454,7 @@ func (s *Server) handleAccountReservationAdd(w http.ResponseWriter, r *http.Requ
if err != nil {
return err
}
t.CancelSubscribers(u.ID)
t.CancelSubscribersExceptUser(u.ID)
return s.writeJSON(w, newSuccessResponse())
}

View File

@ -0,0 +1,143 @@
package server
import (
"heckel.io/ntfy/user"
"net/http"
)
func (s *Server) handleUsersGet(w http.ResponseWriter, r *http.Request, v *visitor) error {
users, err := s.userManager.Users()
if err != nil {
return err
}
grants, err := s.userManager.AllGrants()
if err != nil {
return err
}
usersResponse := make([]*apiUserResponse, len(users))
for i, u := range users {
tier := ""
if u.Tier != nil {
tier = u.Tier.Code
}
userGrants := make([]*apiUserGrantResponse, len(grants[u.ID]))
for i, g := range grants[u.ID] {
userGrants[i] = &apiUserGrantResponse{
Topic: g.TopicPattern,
Permission: g.Allow.String(),
}
}
usersResponse[i] = &apiUserResponse{
Username: u.Name,
Role: string(u.Role),
Tier: tier,
Grants: userGrants,
}
}
return s.writeJSON(w, usersResponse)
}
func (s *Server) handleUsersAdd(w http.ResponseWriter, r *http.Request, v *visitor) error {
req, err := readJSONWithLimit[apiUserAddRequest](r.Body, jsonBodyBytesLimit, false)
if err != nil {
return err
} else if !user.AllowedUsername(req.Username) || req.Password == "" {
return errHTTPBadRequest.Wrap("username invalid, or password missing")
}
u, err := s.userManager.User(req.Username)
if err != nil && err != user.ErrUserNotFound {
return err
} else if u != nil {
return errHTTPConflictUserExists
}
var tier *user.Tier
if req.Tier != "" {
tier, err = s.userManager.Tier(req.Tier)
if err == user.ErrTierNotFound {
return errHTTPBadRequestTierInvalid
} else if err != nil {
return err
}
}
if err := s.userManager.AddUser(req.Username, req.Password, user.RoleUser); err != nil {
return err
}
if tier != nil {
if err := s.userManager.ChangeTier(req.Username, req.Tier); err != nil {
return err
}
}
return s.writeJSON(w, newSuccessResponse())
}
func (s *Server) handleUsersDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
req, err := readJSONWithLimit[apiUserDeleteRequest](r.Body, jsonBodyBytesLimit, false)
if err != nil {
return err
}
u, err := s.userManager.User(req.Username)
if err == user.ErrUserNotFound {
return errHTTPBadRequestUserNotFound
} else if err != nil {
return err
} else if !u.IsUser() {
return errHTTPUnauthorized.Wrap("can only remove regular users from API")
}
if err := s.userManager.RemoveUser(req.Username); err != nil {
return err
}
if err := s.killUserSubscriber(u, "*"); err != nil { // FIXME super inefficient
return err
}
return s.writeJSON(w, newSuccessResponse())
}
func (s *Server) handleAccessAllow(w http.ResponseWriter, r *http.Request, v *visitor) error {
req, err := readJSONWithLimit[apiAccessAllowRequest](r.Body, jsonBodyBytesLimit, false)
if err != nil {
return err
}
_, err = s.userManager.User(req.Username)
if err == user.ErrUserNotFound {
return errHTTPBadRequestUserNotFound
} else if err != nil {
return err
}
permission, err := user.ParsePermission(req.Permission)
if err != nil {
return errHTTPBadRequestPermissionInvalid
}
if err := s.userManager.AllowAccess(req.Username, req.Topic, permission); err != nil {
return err
}
return s.writeJSON(w, newSuccessResponse())
}
func (s *Server) handleAccessReset(w http.ResponseWriter, r *http.Request, v *visitor) error {
req, err := readJSONWithLimit[apiAccessResetRequest](r.Body, jsonBodyBytesLimit, false)
if err != nil {
return err
}
u, err := s.userManager.User(req.Username)
if err != nil {
return err
}
if err := s.userManager.ResetAccess(req.Username, req.Topic); err != nil {
return err
}
if err := s.killUserSubscriber(u, req.Topic); err != nil { // This may be a pattern
return err
}
return s.writeJSON(w, newSuccessResponse())
}
func (s *Server) killUserSubscriber(u *user.User, topicPattern string) error {
topics, err := s.topicsFromPattern(topicPattern)
if err != nil {
return err
}
for _, t := range topics {
t.CancelSubscriberUser(u.ID)
}
return nil
}

View File

@ -0,0 +1,181 @@
package server
import (
"github.com/stretchr/testify/require"
"heckel.io/ntfy/user"
"heckel.io/ntfy/util"
"sync/atomic"
"testing"
"time"
)
func TestUser_AddRemove(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
// Create admin, tier
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
require.Nil(t, s.userManager.AddTier(&user.Tier{
Code: "tier1",
}))
// Create user via API
rr := request(t, s, "PUT", "/v1/users", `{"username": "ben", "password":"ben"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
// Create user with tier via API
rr = request(t, s, "PUT", "/v1/users", `{"username": "emma", "password":"emma", "tier": "tier1"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
// Check users
users, err := s.userManager.Users()
require.Nil(t, err)
require.Equal(t, 4, len(users))
require.Equal(t, "phil", users[0].Name)
require.Equal(t, "ben", users[1].Name)
require.Equal(t, user.RoleUser, users[1].Role)
require.Nil(t, users[1].Tier)
require.Equal(t, "emma", users[2].Name)
require.Equal(t, user.RoleUser, users[2].Role)
require.Equal(t, "tier1", users[2].Tier.Code)
require.Equal(t, user.Everyone, users[3].Name)
// Delete user via API
rr = request(t, s, "DELETE", "/v1/users", `{"username": "ben"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
}
func TestUser_AddRemove_Failures(t *testing.T) {
s := newTestServer(t, newTestConfigWithAuthFile(t))
defer s.closeDatabases()
// Create admin
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
// Cannot create user with invalid username
rr := request(t, s, "PUT", "/v1/users", `{"username": "not valid", "password":"ben"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 400, rr.Code)
// Cannot create user if user already exists
rr = request(t, s, "PUT", "/v1/users", `{"username": "phil", "password":"phil"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 40901, toHTTPError(t, rr.Body.String()).Code)
// Cannot create user with invalid tier
rr = request(t, s, "PUT", "/v1/users", `{"username": "emma", "password":"emma", "tier": "invalid"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 40030, toHTTPError(t, rr.Body.String()).Code)
// Cannot delete user as non-admin
rr = request(t, s, "DELETE", "/v1/users", `{"username": "ben"}`, map[string]string{
"Authorization": util.BasicAuth("ben", "ben"),
})
require.Equal(t, 401, rr.Code)
// Delete user via API
rr = request(t, s, "DELETE", "/v1/users", `{"username": "ben"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
}
func TestAccess_AllowReset(t *testing.T) {
c := newTestConfigWithAuthFile(t)
c.AuthDefault = user.PermissionDenyAll
s := newTestServer(t, c)
defer s.closeDatabases()
// User and admin
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
// Subscribing not allowed
rr := request(t, s, "GET", "/gold/json?poll=1", "", map[string]string{
"Authorization": util.BasicAuth("ben", "ben"),
})
require.Equal(t, 403, rr.Code)
// Grant access
rr = request(t, s, "POST", "/v1/users/access", `{"username": "ben", "topic":"gold", "permission":"ro"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
// Now subscribing is allowed
rr = request(t, s, "GET", "/gold/json?poll=1", "", map[string]string{
"Authorization": util.BasicAuth("ben", "ben"),
})
require.Equal(t, 200, rr.Code)
// Reset access
rr = request(t, s, "DELETE", "/v1/users/access", `{"username": "ben", "topic":"gold"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
// Subscribing not allowed (again)
rr = request(t, s, "GET", "/gold/json?poll=1", "", map[string]string{
"Authorization": util.BasicAuth("ben", "ben"),
})
require.Equal(t, 403, rr.Code)
}
func TestAccess_AllowReset_NonAdminAttempt(t *testing.T) {
c := newTestConfigWithAuthFile(t)
c.AuthDefault = user.PermissionDenyAll
s := newTestServer(t, c)
defer s.closeDatabases()
// User
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
// Grant access fails, because non-admin
rr := request(t, s, "POST", "/v1/users/access", `{"username": "ben", "topic":"gold", "permission":"ro"}`, map[string]string{
"Authorization": util.BasicAuth("ben", "ben"),
})
require.Equal(t, 401, rr.Code)
}
func TestAccess_AllowReset_KillConnection(t *testing.T) {
c := newTestConfigWithAuthFile(t)
c.AuthDefault = user.PermissionDenyAll
s := newTestServer(t, c)
defer s.closeDatabases()
// User and admin, grant access to "gol*" topics
require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
require.Nil(t, s.userManager.AllowAccess("ben", "gol*", user.PermissionRead)) // Wildcard!
start, timeTaken := time.Now(), atomic.Int64{}
go func() {
rr := request(t, s, "GET", "/gold/json", "", map[string]string{
"Authorization": util.BasicAuth("ben", "ben"),
})
require.Equal(t, 200, rr.Code)
timeTaken.Store(time.Since(start).Milliseconds())
}()
time.Sleep(500 * time.Millisecond)
// Reset access
rr := request(t, s, "DELETE", "/v1/users/access", `{"username": "ben", "topic":"gol*"}`, map[string]string{
"Authorization": util.BasicAuth("phil", "phil"),
})
require.Equal(t, 200, rr.Code)
// Wait for connection to be killed; this will fail if the connection is never killed
waitFor(t, func() bool {
return timeTaken.Load() >= 500
})
}

View File

@ -76,6 +76,15 @@ func (s *Server) ensureUser(next handleFunc) handleFunc {
})
}
func (s *Server) ensureAdmin(next handleFunc) handleFunc {
return s.ensureUserManager(func(w http.ResponseWriter, r *http.Request, v *visitor) error {
if !v.User().IsAdmin() {
return errHTTPUnauthorized
}
return next(w, r, v)
})
}
func (s *Server) ensurePaymentsEnabled(next handleFunc) handleFunc {
return func(w http.ResponseWriter, r *http.Request, v *visitor) error {
if s.config.StripeSecretKey == "" || s.stripe == nil {

View File

@ -4,14 +4,15 @@ import (
_ "embed" // required by go:embed
"encoding/json"
"fmt"
"heckel.io/ntfy/log"
"heckel.io/ntfy/util"
"mime"
"net"
"net/smtp"
"strings"
"sync"
"time"
"heckel.io/ntfy/log"
"heckel.io/ntfy/util"
)
type mailer interface {
@ -131,31 +132,23 @@ This message was sent by {ip} at {time} via {topicURL}`
}
var (
//go:embed "mailer_emoji.json"
//go:embed "mailer_emoji_map.json"
emojisJSON string
)
type emoji struct {
Emoji string `json:"emoji"`
Aliases []string `json:"aliases"`
}
func toEmojis(tags []string) (emojisOut []string, tagsOut []string, err error) {
var emojis []emoji
if err = json.Unmarshal([]byte(emojisJSON), &emojis); err != nil {
var emojiMap map[string]string
if err = json.Unmarshal([]byte(emojisJSON), &emojiMap); err != nil {
return nil, nil, err
}
tagsOut = make([]string, 0)
emojisOut = make([]string, 0)
nextTag:
for _, t := range tags { // TODO Super inefficient; we should just create a .json file with a map
for _, e := range emojis {
if util.Contains(e.Aliases, t) {
emojisOut = append(emojisOut, e.Emoji)
continue nextTag
}
for _, t := range tags {
if emoji, ok := emojiMap[t]; ok {
emojisOut = append(emojisOut, emoji)
} else {
tagsOut = append(tagsOut, t)
}
tagsOut = append(tagsOut, t)
}
return
}

View File

@ -141,24 +141,40 @@ func (t *topic) Keepalive() {
t.lastAccess = time.Now()
}
// CancelSubscribers calls the cancel function for all subscribers, forcing
func (t *topic) CancelSubscribers(exceptUserID string) {
// CancelSubscribersExceptUser calls the cancel function for all subscribers, forcing
func (t *topic) CancelSubscribersExceptUser(exceptUserID string) {
t.mu.Lock()
defer t.mu.Unlock()
for _, s := range t.subscribers {
if s.userID != exceptUserID {
log.
Tag(tagSubscribe).
With(t).
Fields(log.Context{
"user_id": s.userID,
}).
Debug("Canceling subscriber %s", s.userID)
s.cancel()
t.cancelUserSubscriber(s)
}
}
}
// CancelSubscriberUser kills the subscriber with the given user ID
func (t *topic) CancelSubscriberUser(userID string) {
t.mu.RLock()
defer t.mu.RUnlock()
for _, s := range t.subscribers {
if s.userID == userID {
t.cancelUserSubscriber(s)
return
}
}
}
func (t *topic) cancelUserSubscriber(s *topicSubscriber) {
log.
Tag(tagSubscribe).
With(t).
Fields(log.Context{
"user_id": s.userID,
}).
Debug("Canceling subscriber with user ID %s", s.userID)
s.cancel()
}
func (t *topic) Context() log.Context {
t.mu.RLock()
defer t.mu.RUnlock()

View File

@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestTopic_CancelSubscribers(t *testing.T) {
func TestTopic_CancelSubscribersExceptUser(t *testing.T) {
t.Parallel()
subFn := func(v *visitor, msg *message) error {
@ -27,11 +27,34 @@ func TestTopic_CancelSubscribers(t *testing.T) {
to.Subscribe(subFn, "", cancelFn1)
to.Subscribe(subFn, "u_phil", cancelFn2)
to.CancelSubscribers("u_phil")
to.CancelSubscribersExceptUser("u_phil")
require.True(t, canceled1.Load())
require.False(t, canceled2.Load())
}
func TestTopic_CancelSubscribersUser(t *testing.T) {
t.Parallel()
subFn := func(v *visitor, msg *message) error {
return nil
}
canceled1 := atomic.Bool{}
cancelFn1 := func() {
canceled1.Store(true)
}
canceled2 := atomic.Bool{}
cancelFn2 := func() {
canceled2.Store(true)
}
to := newTopic("mytopic")
to.Subscribe(subFn, "u_another", cancelFn1)
to.Subscribe(subFn, "u_phil", cancelFn2)
to.CancelSubscriberUser("u_phil")
require.False(t, canceled1.Load())
require.True(t, canceled2.Load())
}
func TestTopic_Keepalive(t *testing.T) {
t.Parallel()

View File

@ -244,6 +244,40 @@ type apiStatsResponse struct {
MessagesRate float64 `json:"messages_rate"` // Average number of messages per second
}
type apiUserAddRequest struct {
Username string `json:"username"`
Password string `json:"password"`
Tier string `json:"tier"`
// Do not add 'role' here. We don't want to add admins via the API.
}
type apiUserResponse struct {
Username string `json:"username"`
Role string `json:"role"`
Tier string `json:"tier,omitempty"`
Grants []*apiUserGrantResponse `json:"grants,omitempty"`
}
type apiUserGrantResponse struct {
Topic string `json:"topic"` // This may be a pattern
Permission string `json:"permission"`
}
type apiUserDeleteRequest struct {
Username string `json:"username"`
}
type apiAccessAllowRequest struct {
Username string `json:"username"`
Topic string `json:"topic"` // This may be a pattern
Permission string `json:"permission"`
}
type apiAccessResetRequest struct {
Username string `json:"username"`
Topic string `json:"topic"`
}
type apiAccountCreateRequest struct {
Username string `json:"username"`
Password string `json:"password"`

View File

@ -195,6 +195,11 @@ const (
ON CONFLICT (user_id, topic)
DO UPDATE SET read=excluded.read, write=excluded.write, owner_user_id=excluded.owner_user_id
`
selectUserAllAccessQuery = `
SELECT user_id, topic, read, write
FROM user_access
ORDER BY write DESC, read DESC, topic
`
selectUserAccessQuery = `
SELECT topic, read, write
FROM user_access
@ -1050,6 +1055,33 @@ func (a *Manager) readUser(rows *sql.Rows) (*User, error) {
return user, nil
}
// AllGrants returns all user-specific access control entries, mapped to their respective user IDs
func (a *Manager) AllGrants() (map[string][]Grant, error) {
rows, err := a.db.Query(selectUserAllAccessQuery)
if err != nil {
return nil, err
}
defer rows.Close()
grants := make(map[string][]Grant, 0)
for rows.Next() {
var userID, topic string
var read, write bool
if err := rows.Scan(&userID, &topic, &read, &write); err != nil {
return nil, err
} else if err := rows.Err(); err != nil {
return nil, err
}
if _, ok := grants[userID]; !ok {
grants[userID] = make([]Grant, 0)
}
grants[userID] = append(grants[userID], Grant{
TopicPattern: fromSQLWildcard(topic),
Allow: NewPermission(read, write),
})
}
return grants, nil
}
// Grants returns all user-specific access control entries
func (a *Manager) Grants(username string) ([]Grant, error) {
rows, err := a.db.Query(selectUserAccessQuery, username)

371
web/package-lock.json generated
View File

@ -3134,9 +3134,9 @@
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A=="
},
"node_modules/@mui/base": {
"version": "5.0.0-alpha.128",
"resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.128.tgz",
"integrity": "sha512-wub3wxNN+hUp8hzilMlXX3sZrPo75vsy1cXEQpqdTfIFlE9HprP1jlulFiPg5tfPst2OKmygXr2hhmgvAKRrzQ==",
"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==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"@emotion/is-prop-valid": "^1.2.0",
@ -3166,9 +3166,9 @@
}
},
"node_modules/@mui/core-downloads-tracker": {
"version": "5.12.3",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.12.3.tgz",
"integrity": "sha512-yiJZ+knaknPHuRKhRk4L6XiwppwkAahVal3LuYpvBH7GkA2g+D9WLEXOEnNYtVFUggyKf6fWGLGnx0iqzkU5YA==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.13.0.tgz",
"integrity": "sha512-5nXz2k8Rv2ZjtQY6kXirJVyn2+ODaQuAJmXSJtLDUQDKWp3PFUj6j3bILqR0JGOs9R5ejgwz3crLKsl6GwjwkQ==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui"
@ -3200,17 +3200,17 @@
}
},
"node_modules/@mui/material": {
"version": "5.12.3",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.12.3.tgz",
"integrity": "sha512-xNmKlrEN4HsTaKFNLZfc7ie7CXx2YqEeO//hsXZx2p3MGtDdeMr2sV3jC4hsFs57RhQlF79weY7uVvC8xSuVbg==",
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.13.0.tgz",
"integrity": "sha512-ckS+9tCpAzpdJdaTF+btF0b6mF9wbXg/EVKtnoAWYi0UKXoXBAVvEUMNpLGA5xdpCdf+A6fPbVUEHs9TsfU+Yw==",
"dependencies": {
"@babel/runtime": "^7.21.0",
"@mui/base": "5.0.0-alpha.128",
"@mui/core-downloads-tracker": "^5.12.3",
"@mui/base": "5.0.0-beta.0",
"@mui/core-downloads-tracker": "^5.13.0",
"@mui/system": "^5.12.3",
"@mui/types": "^7.2.4",
"@mui/utils": "^5.12.3",
"@types/react-transition-group": "^4.4.5",
"@types/react-transition-group": "^4.4.6",
"clsx": "^1.2.1",
"csstype": "^3.1.2",
"prop-types": "^15.8.1",
@ -3948,9 +3948,9 @@
}
},
"node_modules/@types/express-serve-static-core": {
"version": "4.17.34",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.34.tgz",
"integrity": "sha512-fvr49XlCGoUj2Pp730AItckfjat4WNb0lb3kfrLWffd+RLeoGAMsq7UOy04PAPtoL01uKwcp6u8nhzpgpDYr3w==",
"version": "4.17.35",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz",
"integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==",
"dependencies": {
"@types/node": "*",
"@types/qs": "*",
@ -4016,9 +4016,9 @@
"integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw=="
},
"node_modules/@types/node": {
"version": "20.1.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.0.tgz",
"integrity": "sha512-O+z53uwx64xY7D6roOi4+jApDGFg0qn6WHcxe5QeqjMaTezBO/mxdfFXIVAVVyNWKx84OmPB3L8kbVYOTeN34A=="
"version": "20.1.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.4.tgz",
"integrity": "sha512-At4pvmIOki8yuwLtd7BNHl3CiWNbtclUbNtScGx4OHfBd4/oWoJC8KRCIxXwkdndzhxOsPXihrsOoydxBjlE9Q=="
},
"node_modules/@types/parse-json": {
"version": "4.0.0",
@ -4105,9 +4105,9 @@
"integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ=="
},
"node_modules/@types/semver": {
"version": "7.3.13",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
"integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw=="
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz",
"integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw=="
},
"node_modules/@types/send": {
"version": "0.17.1",
@ -4175,14 +4175,14 @@
"integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA=="
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.2.tgz",
"integrity": "sha512-yVrXupeHjRxLDcPKL10sGQ/QlVrA8J5IYOEWVqk0lJaSZP7X5DfnP7Ns3cc74/blmbipQ1htFNVGsHX6wsYm0A==",
"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==",
"dependencies": {
"@eslint-community/regexpp": "^4.4.0",
"@typescript-eslint/scope-manager": "5.59.2",
"@typescript-eslint/type-utils": "5.59.2",
"@typescript-eslint/utils": "5.59.2",
"@typescript-eslint/scope-manager": "5.59.5",
"@typescript-eslint/type-utils": "5.59.5",
"@typescript-eslint/utils": "5.59.5",
"debug": "^4.3.4",
"grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0",
@ -4208,11 +4208,11 @@
}
},
"node_modules/@typescript-eslint/experimental-utils": {
"version": "5.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.59.2.tgz",
"integrity": "sha512-JLw2UImsjHDuVukpA8Nt+UK7JKE/LQAeV3tU5f7wJo2/NNYVwcakzkWjoYzu/2qzWY/Z9c7zojngNDfecNt92g==",
"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==",
"dependencies": {
"@typescript-eslint/utils": "5.59.2"
"@typescript-eslint/utils": "5.59.5"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -4226,13 +4226,13 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "5.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.2.tgz",
"integrity": "sha512-uq0sKyw6ao1iFOZZGk9F8Nro/8+gfB5ezl1cA06SrqbgJAt0SRoFhb9pXaHvkrxUpZaoLxt8KlovHNk8Gp6/HQ==",
"version": "5.59.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.5.tgz",
"integrity": "sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==",
"dependencies": {
"@typescript-eslint/scope-manager": "5.59.2",
"@typescript-eslint/types": "5.59.2",
"@typescript-eslint/typescript-estree": "5.59.2",
"@typescript-eslint/scope-manager": "5.59.5",
"@typescript-eslint/types": "5.59.5",
"@typescript-eslint/typescript-estree": "5.59.5",
"debug": "^4.3.4"
},
"engines": {
@ -4252,12 +4252,12 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "5.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.2.tgz",
"integrity": "sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==",
"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==",
"dependencies": {
"@typescript-eslint/types": "5.59.2",
"@typescript-eslint/visitor-keys": "5.59.2"
"@typescript-eslint/types": "5.59.5",
"@typescript-eslint/visitor-keys": "5.59.5"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -4268,12 +4268,12 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "5.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.2.tgz",
"integrity": "sha512-b1LS2phBOsEy/T381bxkkywfQXkV1dWda/z0PhnIy3bC5+rQWQDS7fk9CSpcXBccPY27Z6vBEuaPBCKCgYezyQ==",
"version": "5.59.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.5.tgz",
"integrity": "sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg==",
"dependencies": {
"@typescript-eslint/typescript-estree": "5.59.2",
"@typescript-eslint/utils": "5.59.2",
"@typescript-eslint/typescript-estree": "5.59.5",
"@typescript-eslint/utils": "5.59.5",
"debug": "^4.3.4",
"tsutils": "^3.21.0"
},
@ -4294,9 +4294,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "5.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.2.tgz",
"integrity": "sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==",
"version": "5.59.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.5.tgz",
"integrity": "sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==",
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
@ -4306,12 +4306,12 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "5.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.2.tgz",
"integrity": "sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==",
"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==",
"dependencies": {
"@typescript-eslint/types": "5.59.2",
"@typescript-eslint/visitor-keys": "5.59.2",
"@typescript-eslint/types": "5.59.5",
"@typescript-eslint/visitor-keys": "5.59.5",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@ -4332,16 +4332,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "5.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.2.tgz",
"integrity": "sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==",
"version": "5.59.5",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.5.tgz",
"integrity": "sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA==",
"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.2",
"@typescript-eslint/types": "5.59.2",
"@typescript-eslint/typescript-estree": "5.59.2",
"@typescript-eslint/scope-manager": "5.59.5",
"@typescript-eslint/types": "5.59.5",
"@typescript-eslint/typescript-estree": "5.59.5",
"eslint-scope": "^5.1.1",
"semver": "^7.3.7"
},
@ -4377,11 +4377,11 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "5.59.2",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.2.tgz",
"integrity": "sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==",
"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==",
"dependencies": {
"@typescript-eslint/types": "5.59.2",
"@typescript-eslint/types": "5.59.5",
"eslint-visitor-keys": "^3.3.0"
},
"engines": {
@ -4393,133 +4393,133 @@
}
},
"node_modules/@webassemblyjs/ast": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.5.tgz",
"integrity": "sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ==",
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz",
"integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==",
"dependencies": {
"@webassemblyjs/helper-numbers": "1.11.5",
"@webassemblyjs/helper-wasm-bytecode": "1.11.5"
"@webassemblyjs/helper-numbers": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6"
}
},
"node_modules/@webassemblyjs/floating-point-hex-parser": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.5.tgz",
"integrity": "sha512-1j1zTIC5EZOtCplMBG/IEwLtUojtwFVwdyVMbL/hwWqbzlQoJsWCOavrdnLkemwNoC/EOwtUFch3fuo+cbcXYQ=="
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw=="
},
"node_modules/@webassemblyjs/helper-api-error": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.5.tgz",
"integrity": "sha512-L65bDPmfpY0+yFrsgz8b6LhXmbbs38OnwDCf6NpnMUYqa+ENfE5Dq9E42ny0qz/PdR0LJyq/T5YijPnU8AXEpA=="
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q=="
},
"node_modules/@webassemblyjs/helper-buffer": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.5.tgz",
"integrity": "sha512-fDKo1gstwFFSfacIeH5KfwzjykIE6ldh1iH9Y/8YkAZrhmu4TctqYjSh7t0K2VyDSXOZJ1MLhht/k9IvYGcIxg=="
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz",
"integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA=="
},
"node_modules/@webassemblyjs/helper-numbers": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.5.tgz",
"integrity": "sha512-DhykHXM0ZABqfIGYNv93A5KKDw/+ywBFnuWybZZWcuzWHfbp21wUfRkbtz7dMGwGgT4iXjWuhRMA2Mzod6W4WA==",
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
"integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.11.5",
"@webassemblyjs/helper-api-error": "1.11.5",
"@webassemblyjs/floating-point-hex-parser": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6",
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.5.tgz",
"integrity": "sha512-oC4Qa0bNcqnjAowFn7MPCETQgDYytpsfvz4ujZz63Zu/a/v71HeCAAmZsgZ3YVKec3zSPYytG3/PrRCqbtcAvA=="
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA=="
},
"node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.5.tgz",
"integrity": "sha512-uEoThA1LN2NA+K3B9wDo3yKlBfVtC6rh0i4/6hvbz071E8gTNZD/pT0MsBf7MeD6KbApMSkaAK0XeKyOZC7CIA==",
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz",
"integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==",
"dependencies": {
"@webassemblyjs/ast": "1.11.5",
"@webassemblyjs/helper-buffer": "1.11.5",
"@webassemblyjs/helper-wasm-bytecode": "1.11.5",
"@webassemblyjs/wasm-gen": "1.11.5"
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/wasm-gen": "1.11.6"
}
},
"node_modules/@webassemblyjs/ieee754": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.5.tgz",
"integrity": "sha512-37aGq6qVL8A8oPbPrSGMBcp38YZFXcHfiROflJn9jxSdSMMM5dS5P/9e2/TpaJuhE+wFrbukN2WI6Hw9MH5acg==",
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
"integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
}
},
"node_modules/@webassemblyjs/leb128": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.5.tgz",
"integrity": "sha512-ajqrRSXaTJoPW+xmkfYN6l8VIeNnR4vBOTQO9HzR7IygoCcKWkICbKFbVTNMjMgMREqXEr0+2M6zukzM47ZUfQ==",
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
"integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
"dependencies": {
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webassemblyjs/utf8": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.5.tgz",
"integrity": "sha512-WiOhulHKTZU5UPlRl53gHR8OxdGsSOxqfpqWeA2FmcwBMaoEdz6b2x2si3IwC9/fSPLfe8pBMRTHVMk5nlwnFQ=="
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA=="
},
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.5.tgz",
"integrity": "sha512-C0p9D2fAu3Twwqvygvf42iGCQ4av8MFBLiTb+08SZ4cEdwzWx9QeAHDo1E2k+9s/0w1DM40oflJOpkZ8jW4HCQ==",
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz",
"integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==",
"dependencies": {
"@webassemblyjs/ast": "1.11.5",
"@webassemblyjs/helper-buffer": "1.11.5",
"@webassemblyjs/helper-wasm-bytecode": "1.11.5",
"@webassemblyjs/helper-wasm-section": "1.11.5",
"@webassemblyjs/wasm-gen": "1.11.5",
"@webassemblyjs/wasm-opt": "1.11.5",
"@webassemblyjs/wasm-parser": "1.11.5",
"@webassemblyjs/wast-printer": "1.11.5"
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/helper-wasm-section": "1.11.6",
"@webassemblyjs/wasm-gen": "1.11.6",
"@webassemblyjs/wasm-opt": "1.11.6",
"@webassemblyjs/wasm-parser": "1.11.6",
"@webassemblyjs/wast-printer": "1.11.6"
}
},
"node_modules/@webassemblyjs/wasm-gen": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.5.tgz",
"integrity": "sha512-14vteRlRjxLK9eSyYFvw1K8Vv+iPdZU0Aebk3j6oB8TQiQYuO6hj9s4d7qf6f2HJr2khzvNldAFG13CgdkAIfA==",
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz",
"integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==",
"dependencies": {
"@webassemblyjs/ast": "1.11.5",
"@webassemblyjs/helper-wasm-bytecode": "1.11.5",
"@webassemblyjs/ieee754": "1.11.5",
"@webassemblyjs/leb128": "1.11.5",
"@webassemblyjs/utf8": "1.11.5"
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/ieee754": "1.11.6",
"@webassemblyjs/leb128": "1.11.6",
"@webassemblyjs/utf8": "1.11.6"
}
},
"node_modules/@webassemblyjs/wasm-opt": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.5.tgz",
"integrity": "sha512-tcKwlIXstBQgbKy1MlbDMlXaxpucn42eb17H29rawYLxm5+MsEmgPzeCP8B1Cl69hCice8LeKgZpRUAPtqYPgw==",
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz",
"integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==",
"dependencies": {
"@webassemblyjs/ast": "1.11.5",
"@webassemblyjs/helper-buffer": "1.11.5",
"@webassemblyjs/wasm-gen": "1.11.5",
"@webassemblyjs/wasm-parser": "1.11.5"
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-buffer": "1.11.6",
"@webassemblyjs/wasm-gen": "1.11.6",
"@webassemblyjs/wasm-parser": "1.11.6"
}
},
"node_modules/@webassemblyjs/wasm-parser": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.5.tgz",
"integrity": "sha512-SVXUIwsLQlc8srSD7jejsfTU83g7pIGr2YYNb9oHdtldSxaOhvA5xwvIiWIfcX8PlSakgqMXsLpLfbbJ4cBYew==",
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz",
"integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==",
"dependencies": {
"@webassemblyjs/ast": "1.11.5",
"@webassemblyjs/helper-api-error": "1.11.5",
"@webassemblyjs/helper-wasm-bytecode": "1.11.5",
"@webassemblyjs/ieee754": "1.11.5",
"@webassemblyjs/leb128": "1.11.5",
"@webassemblyjs/utf8": "1.11.5"
"@webassemblyjs/ast": "1.11.6",
"@webassemblyjs/helper-api-error": "1.11.6",
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
"@webassemblyjs/ieee754": "1.11.6",
"@webassemblyjs/leb128": "1.11.6",
"@webassemblyjs/utf8": "1.11.6"
}
},
"node_modules/@webassemblyjs/wast-printer": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.5.tgz",
"integrity": "sha512-f7Pq3wvg3GSPUPzR0F6bmI89Hdb+u9WXrSKc4v+N0aV0q6r42WoF92Jp2jEorBEBRoRNXgjp53nBniDXcqZYPA==",
"version": "1.11.6",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz",
"integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==",
"dependencies": {
"@webassemblyjs/ast": "1.11.5",
"@webassemblyjs/ast": "1.11.6",
"@xtuc/long": "4.2.2"
}
},
@ -4582,9 +4582,9 @@
}
},
"node_modules/acorn-import-assertions": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
"integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz",
"integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==",
"peerDependencies": {
"acorn": "^8"
}
@ -5511,9 +5511,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001486",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001486.tgz",
"integrity": "sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==",
"version": "1.0.30001487",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001487.tgz",
"integrity": "sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==",
"funding": [
{
"type": "opencollective",
@ -6171,13 +6171,19 @@
}
},
"node_modules/cssdb": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.5.4.tgz",
"integrity": "sha512-fGD+J6Jlq+aurfE1VDXlLS4Pt0VtNlu2+YgfGOdMxRyl/HQ9bDiHTwSck1Yz8A97Dt/82izSK6Bp/4nVqacOsg==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/csstools"
}
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/cssdb/-/cssdb-7.6.0.tgz",
"integrity": "sha512-Nna7rph8V0jC6+JBY4Vk4ndErUmfJfV6NJCaZdurL0omggabiy+QB2HCQtu5c/ACLZ0I7REv7A4QyPIoYzZx0w==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/csstools"
},
{
"type": "github",
"url": "https://github.com/sponsors/csstools"
}
]
},
"node_modules/cssesc": {
"version": "3.0.0",
@ -6743,9 +6749,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.385",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.385.tgz",
"integrity": "sha512-L9zlje9bIw0h+CwPQumiuVlfMcV4boxRjFIWDcLfFqTZNbkwOExBzfmswytHawObQX4OUhtNv8gIiB21kOurIg=="
"version": "1.4.394",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.394.tgz",
"integrity": "sha512-0IbC2cfr8w5LxTz+nmn2cJTGafsK9iauV2r5A5scfzyovqLrxuLoxOHE5OBobP3oVIggJT+0JfKnw9sm87c8Hw=="
},
"node_modules/emittery": {
"version": "0.8.1",
@ -6780,9 +6786,9 @@
}
},
"node_modules/enhanced-resolve": {
"version": "5.13.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.13.0.tgz",
"integrity": "sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg==",
"version": "5.14.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.14.0.tgz",
"integrity": "sha512-+DCows0XNwLDcUhbFJPdlQEVnT2zXlCv7hPxemTz86/O+B/hCQ+mb7ydkPKiflpVraqLPCAfu7lDy+hBXueojw==",
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
@ -7361,9 +7367,9 @@
}
},
"node_modules/eslint-plugin-testing-library": {
"version": "5.10.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.10.3.tgz",
"integrity": "sha512-0yhsKFsjHLud5PM+f2dWr9K3rqYzMy4cSHs3lcmFYMa1CdSzRvHGgXvsFarBjZ41gU8jhTdMIkg8jHLxGJqLqw==",
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-5.11.0.tgz",
"integrity": "sha512-ELY7Gefo+61OfXKlQeXNIDVVLPcvKTeiQOoMZG9TeuWa7Ln4dUNRv8JdRWBQI9Mbb427XGlVB1aa1QPZxBJM8Q==",
"dependencies": {
"@typescript-eslint/utils": "^5.58.0"
},
@ -8342,12 +8348,13 @@
}
},
"node_modules/get-intrinsic": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
"integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3"
},
"funding": {
@ -9551,14 +9558,14 @@
}
},
"node_modules/jake": {
"version": "10.8.5",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz",
"integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==",
"version": "10.8.6",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.6.tgz",
"integrity": "sha512-G43Ub9IYEFfu72sua6rzooi8V8Gz2lkfk48rW20vEWCGizeaEPlKB1Kh8JIA84yQbiAEfqlPmSpGgCKKxH3rDA==",
"dependencies": {
"async": "^3.2.3",
"chalk": "^4.0.2",
"filelist": "^1.0.1",
"minimatch": "^3.0.4"
"filelist": "^1.0.4",
"minimatch": "^3.1.2"
},
"bin": {
"jake": "bin/cli.js"
@ -15068,9 +15075,9 @@
}
},
"node_modules/semver": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.0.tgz",
"integrity": "sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==",
"version": "7.5.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz",
"integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==",
"dependencies": {
"lru-cache": "^6.0.0"
},
@ -15969,9 +15976,9 @@
}
},
"node_modules/terser": {
"version": "5.17.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.17.1.tgz",
"integrity": "sha512-hVl35zClmpisy6oaoKALOpS0rDYLxRFLHhRuDlEGTKey9qHjS1w9GMORjuwIMt70Wan4lwsLYyWDVnWgF+KUEw==",
"version": "5.17.3",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.17.3.tgz",
"integrity": "sha512-AudpAZKmZHkG9jueayypz4duuCFJMMNGRMwaPvQKWfxKedh8Z2x3OCoDqIIi1xx5+iwx1u6Au8XQcc9Lke65Yg==",
"dependencies": {
"@jridgewell/source-map": "^0.3.2",
"acorn": "^8.5.0",
@ -16548,9 +16555,9 @@
}
},
"node_modules/webpack": {
"version": "5.82.0",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.82.0.tgz",
"integrity": "sha512-iGNA2fHhnDcV1bONdUu554eZx+XeldsaeQ8T67H6KKHl2nUSwX8Zm7cmzOA46ox/X1ARxf7Bjv8wQ/HsB5fxBg==",
"version": "5.82.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.82.1.tgz",
"integrity": "sha512-C6uiGQJ+Gt4RyHXXYt+v9f+SN1v83x68URwgxNQ98cvH8kxiuywWGP4XeNZ1paOzZ63aY3cTciCEQJNFUljlLw==",
"dependencies": {
"@types/eslint-scope": "^3.7.3",
"@types/estree": "^1.0.0",
@ -16561,7 +16568,7 @@
"acorn-import-assertions": "^1.7.6",
"browserslist": "^4.14.5",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.13.0",
"enhanced-resolve": "^5.14.0",
"es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1",
"events": "^3.2.0",

View File

@ -21,8 +21,11 @@ class Api {
const headers = maybeWithAuth({}, user);
console.log(`[Api] Polling ${url}`);
for await (let line of fetchLinesIterator(url, headers)) {
console.log(`[Api, ${shortUrl}] Received message ${line}`);
messages.push(JSON.parse(line));
const message = JSON.parse(line);
if (message.id) {
console.log(`[Api, ${shortUrl}] Received message ${line}`);
messages.push(message);
}
}
return messages;
}

View File

@ -585,7 +585,7 @@ const Stats = () => {
description={t("account_usage_attachment_storage_description", {
filesize: formatBytes(account.limits.attachment_file_size),
expiry: humanizeDuration(account.limits.attachment_expiry_duration * 1000, {
language: i18n.language,
language: i18n.resolvedLanguage,
fallbacks: ["en"]
})
})}

View File

@ -436,7 +436,7 @@ const Appearance = () => {
const Language = () => {
const { t, i18n } = useTranslation();
const labelId = "prefLanguage";
const lang = i18n.language ?? "en";
const lang = i18n.resolvedLanguage ?? "en";
// Country flags are displayed using emoji. Emoji rendering is handled by platform fonts.
// Windows in particular does not yet play nicely with flag emoji so for now, hide flags on Windows.
@ -541,8 +541,8 @@ const ReservationsTable = (props) => {
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const { subscriptions } = useOutletContext();
const localSubscriptions = (subscriptions?.length > 0)
? Object.assign(...subscriptions.filter(s => s.baseUrl === config.base_url).map(s => ({[s.topic]: s})))
: [];
? Object.assign({}, ...subscriptions.filter(s => s.baseUrl === config.base_url).map(s => ({[s.topic]: s})))
: {};
const handleEditClick = (reservation) => {
setDialogKey(prev => prev+1);

View File

@ -34,7 +34,7 @@ export const ReserveAddDialog = (props) => {
const handleSubmit = async () => {
try {
await accountApi.upsertReservation(topic, everyone);
console.debug(`[ReserveAddDialog] Added reservation for topic ${t}: ${everyone}`);
console.debug(`[ReserveAddDialog] Added reservation for topic ${topic}: ${everyone}`);
} catch (e) {
console.log(`[ReserveAddDialog] Error adding topic reservation.`, e);
if (e instanceof UnauthorizedError) {