Merge branch 'main' into user-account

This commit is contained in:
binwiederhier 2023-02-14 21:22:46 -05:00
commit 0df3e3e4f5
9 changed files with 1059 additions and 12764 deletions

View File

@ -847,7 +847,7 @@ to `https://ntfy.example.com/v1/account/billing/webhook`.
Here's an example: Here's an example:
``` yaml ``` yaml
stripe-secret-key: "sk_live_ZmhzZGtmbGhkc2tqZmhzYcO2a2hmbGtnaHNkbGtnaGRsc2hnbG" stripe-secret-key: "sk_test_ZmhzZGtmbGhkc2tqZmhzYcO2a2hmbGtnaHNkbGtnaGRsc2hnbG"
stripe-webhook-key: "whsec_ZnNkZnNIRExBSFNES0hBRFNmaHNka2ZsaGR" stripe-webhook-key: "whsec_ZnNkZnNIRExBSFNES0hBRFNmaHNka2ZsaGR"
``` ```

View File

@ -327,7 +327,76 @@ To build your own version with Firebase, you must:
``` ```
## iOS app ## iOS app
The ntfy iOS app source code is available [on GitHub](https://github.com/binwiederhier/ntfy-ios). Building the iOS app is very involved. Please report any inconsistencies or issues with it. The requirements are
strictly based off of my development on this app. There may be other versions of macOS / XCode that work.
### Requirements
1. macOS Monterey or later
1. XCode 13.2+
1. A physical iOS device (for push notifications, Firebase does not work in the XCode simulator)
1. Firebase account
1. Apple Developer license? (I forget if it's possible to do testing without purchasing the license)
### Apple setup
!!! info !!! info
I haven't had time to move the build instructions here. Please check out the repository instead. Along with this step, the [PLIST Deployment](#plist-deployment-and-configuration) step is also required
for these changes to take effect in the iOS app.
1. [Create a new key in Apple Developer Member Center](https://developer.apple.com/account/resources/authkeys/add)
1. Select "Apple Push Notifications service (APNs)"
1. Download the newly created key (should have a file name similar to `AuthKey_ZZZZZZ.p8`, where `ZZZZZZ` is the **Key ID**)
1. Record your **Team ID** - it can be seen in the top-right corner of the page, or on your Account > Membership page
1. Next, navigate to "Project Settings" in the firebase console for your project, and select the iOS app you created. Then, click "Cloud Messaging" in the left sidebar, and scroll down to the "APNs Authentication Key" section. Click "Upload Key", and upload the key you downloaded from Apple Developer.
!!! warning
If you don't do the above setups for APNS, **notifications will not post instantly or sometimes at all**. This is because of the missing APNS key, which is required for firebase to send notifications to the iOS app. See below for a snip from the firebase docs.
If you don't have an APNs authentication key, you can still send notifications to iOS devices, but they won't be delivered
instantly. Instead, they'll be delivered when the device wakes up to check for new notifications or when your application
sends a firebase request to check for them. The time to check for new notifications can vary from a few seconds to hours,
days or even weeks. Enabling APNs authentication keys ensures that notifications are delivered instantly and is strongly
recommended.
### Firebase setup
1. If you haven't already, create a Google / Firebase account
1. Visit the [Firebase console](https://console.firebase.google.com)
1. Create a new Firebase project:
1. Enter a project name
1. Disable Google Analytics (currently iOS app does not support analytics)
1. On the "Project settings" page, add an iOS app
1. Apple bundle ID - "com.copephobia.ntfy-ios" (this can be changed to match XCode's ntfy.sh target > "Bundle Identifier" value)
1. Register the app
1. Download the config file - GoogleInfo.plist (this will need to be included in the ntfy-ios repository / XCode)
1. Generate a new service account private key for the ntfy server
1. Go to "Project settings" > "Service accounts"
1. Click "Generate new private key" to generate and download a private key to use for sending messages via the ntfy server
### ntfy server
Note that the ntfy server is not officially supported on macOS. It should, however, be able to run on macOS using these
steps:
1. If not already made, make the `/etc/ntfy/` directory and move the service account private key to that folder
1. Copy the `server/server.yml` file from the ntfy repository to `/etc/ntfy/`
1. Modify the `/etc/ntfy/server.yml` file `firebase-key-file` value to the path of the private key
1. Install go: `brew install go`
1. In the ntfy repository, run `make cli-darwin-server`.
### XCode setup
1. Follow step 4 of [https://firebase.google.com/docs/ios/setup](Add Firebase to your Apple project) to install the
`firebase-ios-sdk` in XCode, if it's not already present - you can select any packages in addition to Firebase Core / Firebase Messaging
1. Similarly, install the SQLite.swift package dependency in XCode
1. When running the debug build, ensure XCode is pointed to the connected iOS device - registering for push notifications does not work in the iOS simulators
### PLIST config
To have instant notifications/better notification delivery when using firebase, you will need to add the
`GoogleService-Info.plist` file to your project. Here's how to do that:
1. In XCode, find the NTFY app target. **Not** the NSE app target.
1. Find the Asset/ folder in the project navigator
1. Drag the `GoogleService-Info.plist` file into the Asset/ folder that you get from the firebase console. It can be
found in the "Project settings" > "General" > "Your apps" with a button labled "GoogleService-Info.plist"
After that, you should be all set!

View File

@ -2,13 +2,18 @@
Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases) Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases)
and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases). and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases).
## ntfy server v1.31.0 (UNRELEASED) ## ntfy server v2.0.0 (UNRELEASED)
Released XXXX
This is the biggest ntfy server release I've ever done. This release adds the ability to sign-up for accounts, log-in This is the biggest ntfy server release I've ever done. This release adds the ability to sign-up for accounts, log-in
via the web app, synchronize accounts between devices (web only for now), introduces user access tokens, user tiers, via the web app, synchronize accounts between devices (web only for now), introduces user access tokens, user tiers,
and Stripe payments integration to support paid tiers (don't worry, [everything will stay open source](https://ntfy.sh/docs/faq/)). and Stripe payments integration to support paid tiers (don't worry, [everything will stay open source](https://ntfy.sh/docs/faq/)).
**Bug fixes + maintenance:**
* `OPTIONS` method calls are not serviced when the UI is disabled ([#598](https://github.com/binwiederhier/ntfy/issues/598), thanks to [@enticedwanderer](https://github.com/enticedwanderer) for reporting)
## ntfy server v1.31.0 (UNRELEASED)
**Features:** **Features:**
* ⭐ User account signup, login, topic reservations, access tokens, tiers etc. ⭐ ([#522](https://github.com/binwiederhier/ntfy/issues/522)) * ⭐ User account signup, login, topic reservations, access tokens, tiers etc. ⭐ ([#522](https://github.com/binwiederhier/ntfy/issues/522))
@ -19,7 +24,7 @@ and Stripe payments integration to support paid tiers (don't worry, [everything
* Fix `chown` issues with RHEL-like based systems ([#566](https://github.com/binwiederhier/ntfy/issues/566)/[#565](https://github.com/binwiederhier/ntfy/pull/565), thanks to [@danieldemus](https://github.com/danieldemus)) * Fix `chown` issues with RHEL-like based systems ([#566](https://github.com/binwiederhier/ntfy/issues/566)/[#565](https://github.com/binwiederhier/ntfy/pull/565), thanks to [@danieldemus](https://github.com/danieldemus))
* Removed `upx` (binary packing) for all builds due to false virus warnings ([#576](https://github.com/binwiederhier/ntfy/issues/576), thanks to [@shawnhwei](https://github.com/shawnhwei) for reporting) * Removed `upx` (binary packing) for all builds due to false virus warnings ([#576](https://github.com/binwiederhier/ntfy/issues/576), thanks to [@shawnhwei](https://github.com/shawnhwei) for reporting)
* `OPTIONS` method calls are not serviced when the UI is disabled ([#598](https://github.com/binwiederhier/ntfy/issues/598), thanks to [@enticedwanderer](https://github.com/enticedwanderer) for reporting) * Upgraded `go-smtp` library and tests to v0.16.0 ([#569](https://github.com/binwiederhier/ntfy/issues/569))
**Documentation:** **Documentation:**
@ -27,6 +32,7 @@ and Stripe payments integration to support paid tiers (don't worry, [everything
* Small wording change for `client.yml` ([#562](https://github.com/binwiederhier/ntfy/pull/562), thanks to [@fleopaulD](https://github.com/fleopaulD)) * Small wording change for `client.yml` ([#562](https://github.com/binwiederhier/ntfy/pull/562), thanks to [@fleopaulD](https://github.com/fleopaulD))
* Fix K8s install docs ([#582](https://github.com/binwiederhier/ntfy/pull/582), thanks to [@Remedan](https://github.com/Remedan)) * Fix K8s install docs ([#582](https://github.com/binwiederhier/ntfy/pull/582), thanks to [@Remedan](https://github.com/Remedan))
* Updated Jellyseer docs ([#604](https://github.com/binwiederhier/ntfy/pull/604), thanks to [@Y0ngg4n](https://github.com/Y0ngg4n)) * Updated Jellyseer docs ([#604](https://github.com/binwiederhier/ntfy/pull/604), thanks to [@Y0ngg4n](https://github.com/Y0ngg4n))
* Updated iOS developer docs ([#605](https://github.com/binwiederhier/ntfy/pull/605), thanks to [@SticksDev](https://github.com/SticksDev))
**Additional languages:** **Additional languages:**

36
go.mod
View File

@ -4,22 +4,22 @@ go 1.18
require ( require (
cloud.google.com/go/firestore v1.9.0 // indirect cloud.google.com/go/firestore v1.9.0 // indirect
cloud.google.com/go/storage v1.28.1 // indirect cloud.google.com/go/storage v1.29.0 // indirect
github.com/BurntSushi/toml v1.2.1 // indirect github.com/BurntSushi/toml v1.2.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/emersion/go-smtp v0.15.0 github.com/emersion/go-smtp v0.16.0
github.com/gabriel-vasile/mimetype v1.4.1 github.com/gabriel-vasile/mimetype v1.4.1
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/mattn/go-sqlite3 v1.14.16 github.com/mattn/go-sqlite3 v1.14.16
github.com/olebedev/when v0.0.0-20221205223600-4d190b02b8d8 github.com/olebedev/when v0.0.0-20221205223600-4d190b02b8d8
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.1
github.com/urfave/cli/v2 v2.23.7 github.com/urfave/cli/v2 v2.24.3
golang.org/x/crypto v0.4.0 golang.org/x/crypto v0.6.0
golang.org/x/oauth2 v0.3.0 // indirect golang.org/x/oauth2 v0.5.0 // indirect
golang.org/x/sync v0.1.0 golang.org/x/sync v0.1.0
golang.org/x/term v0.3.0 golang.org/x/term v0.5.0
golang.org/x/time v0.3.0 golang.org/x/time v0.3.0
google.golang.org/api v0.105.0 google.golang.org/api v0.110.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
) )
@ -27,15 +27,15 @@ require github.com/pkg/errors v0.9.1 // indirect
require ( require (
firebase.google.com/go/v4 v4.10.0 firebase.google.com/go/v4 v4.10.0
github.com/stripe/stripe-go/v74 v74.5.0 github.com/stripe/stripe-go/v74 v74.7.0
) )
require ( require (
cloud.google.com/go v0.107.0 // indirect cloud.google.com/go v0.109.0 // indirect
cloud.google.com/go/compute v1.14.0 // indirect cloud.google.com/go/compute v1.18.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v0.9.0 // indirect cloud.google.com/go/iam v0.10.0 // indirect
cloud.google.com/go/longrunning v0.3.0 // indirect cloud.google.com/go/longrunning v0.4.1 // indirect
github.com/AlekSi/pointer v1.2.0 // indirect github.com/AlekSi/pointer v1.2.0 // indirect
github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/MicahParks/keyfunc v1.9.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
@ -45,21 +45,21 @@ require (
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-cmp v0.5.9 // indirect
github.com/google/uuid v1.3.0 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.7.0 // indirect github.com/googleapis/gax-go/v2 v2.7.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect github.com/stretchr/objx v0.5.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect
golang.org/x/net v0.4.0 // indirect golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.3.0 // indirect golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.5.0 // indirect golang.org/x/text v0.7.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/appengine/v2 v2.0.2 // indirect google.golang.org/appengine/v2 v2.0.2 // indirect
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef // indirect google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc // indirect
google.golang.org/grpc v1.51.0 // indirect google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

72
go.sum
View File

@ -1,18 +1,18 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.107.0 h1:qkj22L7bgkl6vIeZDlOY2po43Mx/TIa2Wsa7VR+PEww= cloud.google.com/go v0.109.0 h1:38CZoKGlCnPZjGdyj0ZfpoGae0/wgNfy5F0byyxg0Gk=
cloud.google.com/go v0.107.0/go.mod h1:wpc2eNrD7hXUTy8EKS10jkxpZBjASrORK7goS+3YX2I= cloud.google.com/go v0.109.0/go.mod h1:2sYycXt75t/CSB5R9M2wPU1tJmire7AQZTPtITcGBVE=
cloud.google.com/go/compute v1.14.0 h1:hfm2+FfxVmnRlh6LpB7cg1ZNU+5edAHmW679JePztk0= cloud.google.com/go/compute v1.18.0 h1:FEigFqoDbys2cvFkZ9Fjq4gnHBP55anJ0yQyau2f9oY=
cloud.google.com/go/compute v1.14.0/go.mod h1:YfLtxrj9sU4Yxv+sXzZkyPjEyPBZfXHUvjxega5vAdo= cloud.google.com/go/compute v1.18.0/go.mod h1:1X7yHxec2Ga+Ss6jPyjxRxpu2uu7PLgsOVXvgU0yacs=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= 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/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 h1:IBlRyxgGySXu5VuW0RgGFlTtLukSnNkpDiEOMkQkmpA=
cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE= cloud.google.com/go/firestore v1.9.0/go.mod h1:HMkjKHNTtRyZNiMzu7YAsLr9K3X2udY2AMwDaMEQiiE=
cloud.google.com/go/iam v0.9.0 h1:bK6Or6mxhuL8lnj1i9j0yMo2wE/IeTO2cWlfUrf/TZs= cloud.google.com/go/iam v0.10.0 h1:fpP/gByFs6US1ma53v7VxhvbJpO2Aapng6wabJ99MuI=
cloud.google.com/go/iam v0.9.0/go.mod h1:nXAECrMt2qHpF6RZUZseteD6QyanL68reN4OXPw0UWM= cloud.google.com/go/iam v0.10.0/go.mod h1:nXAECrMt2qHpF6RZUZseteD6QyanL68reN4OXPw0UWM=
cloud.google.com/go/longrunning v0.3.0 h1:NjljC+FYPV3uh5/OwWT6pVU+doBqMg2x/rZlE+CamDs= cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM=
cloud.google.com/go/longrunning v0.3.0/go.mod h1:qth9Y41RRSUE69rDcOn6DdK3HfQfsUI0YSmW3iIlLJc= cloud.google.com/go/longrunning v0.4.1/go.mod h1:4iWDqhBZ70CvZ6BfETbvam3T8FMvLK+eFj0E6AaRQTo=
cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI= cloud.google.com/go/storage v1.29.0 h1:6weCgzRvMg7lzuUurI4697AqIRPU1SvzHhynwpW31jI=
cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4=
firebase.google.com/go/v4 v4.10.0 h1:dgK/8uwfJbzc5LZK/GyRRfIkZEDObN9q0kgEXsjlXN4= firebase.google.com/go/v4 v4.10.0 h1:dgK/8uwfJbzc5LZK/GyRRfIkZEDObN9q0kgEXsjlXN4=
firebase.google.com/go/v4 v4.10.0/go.mod h1:m0gLwPY9fxKggizzglgCNWOGnFnVPifLpqZzo5u3e/A= firebase.google.com/go/v4 v4.10.0/go.mod h1:m0gLwPY9fxKggizzglgCNWOGnFnVPifLpqZzo5u3e/A=
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
@ -33,8 +33,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y= github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead h1:fI1Jck0vUrXT8bnphprS1EoVRe2Q5CKCX8iDlpqjQ/Y=
github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-sasl v0.0.0-20220912192320-0145f2c60ead/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ=
github.com/emersion/go-smtp v0.15.0 h1:3+hMGMGrqP/lqd7qoxZc1hTU8LY8gHV9RFGWlqSDmP8= github.com/emersion/go-smtp v0.16.0 h1:eB9CY9527WdEZSs5sWisTmilDX7gG+Q/2IdRcmubpa8=
github.com/emersion/go-smtp v0.15.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= github.com/emersion/go-smtp v0.16.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -75,8 +75,8 @@ github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1V
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.2.1 h1:RY7tHKZcRlk788d5WSo/e83gOyyy742E8GSs771ySpg= github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k=
github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k=
github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ= github.com/googleapis/gax-go/v2 v2.7.0 h1:IcsPKeInNvYi7eqSaDjiZqDDKu5rsmunY0Y1YupQSSQ=
github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
@ -101,18 +101,18 @@ 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.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stripe/stripe-go/v74 v74.5.0 h1:YyqTvVQdS34KYGCfVB87EMn9eDV3FCFkSwfdOQhiVL4= github.com/stripe/stripe-go/v74 v74.7.0 h1:KHlyslQj9YOv62b1sycQ31LFj7KlqR+seHsSowAWrjc=
github.com/stripe/stripe-go/v74 v74.5.0/go.mod h1:5PoXNp30AJ3tGq57ZcFuaMylzNi8KpwlrYAFmO1fHZw= github.com/stripe/stripe-go/v74 v74.7.0/go.mod h1:5PoXNp30AJ3tGq57ZcFuaMylzNi8KpwlrYAFmO1fHZw=
github.com/urfave/cli/v2 v2.23.7 h1:YHDQ46s3VghFHFf1DdF+Sh7H4RqhcM+t0TmZRJx4oJY= github.com/urfave/cli/v2 v2.24.3 h1:7Q1w8VN8yE0MJEHP06bv89PjYsN4IHWED2s1v/Zlfm0=
github.com/urfave/cli/v2 v2.23.7/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= github.com/urfave/cli/v2 v2.24.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8= golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -127,11 +127,11 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8= golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 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-20181108010431-42b317875d0f/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-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -144,17 +144,17 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-20210615035016-665e8c7367d1/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -165,8 +165,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/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 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
google.golang.org/api v0.105.0 h1:t6P9Jj+6XTn4U9I2wycQai6Q/Kz7iOT+QzjJ3G2V4x8= google.golang.org/api v0.110.0 h1:l+rh0KYUooe9JGbGVx71tbFo4SMbMTXK3I3ia2QSEeU=
google.golang.org/api v0.105.0/go.mod h1:qh7eD5FJks5+BcE+cjBIm6Gz8vioK7EHvnlniqXBnqI= google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 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.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
@ -176,15 +176,15 @@ google.golang.org/appengine/v2 v2.0.2/go.mod h1:PkgRUWz4o1XOvbqtWTkBtCitEJ5Tp4Ho
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef h1:uQ2vjV/sHTsWSqdKeLqmwitzgvjMl7o4IdtHwUDXSJY= google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc h1:ijGwO+0vL2hJt5gaygqP2j6PfflOBrRot0IczKbmtio=
google.golang.org/genproto v0.0.0-20221227171554-f9683d7f8bef/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U= google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View File

@ -37,13 +37,12 @@ func logvm(v *visitor, m *message) *log.Event {
} }
// logem creates a new log event with email fields // logem creates a new log event with email fields
func logem(state *smtp.ConnectionState) *log.Event { func logem(smtpConn *smtp.Conn) *log.Event {
return log. ev := log.Tag(tagSMTP).Field("smtp_hostname", smtpConn.Hostname())
Tag(tagSMTP). if smtpConn.Conn() != nil {
Fields(log.Context{ ev.Field("smtp_remote_addr", smtpConn.Conn().RemoteAddr().String())
"smtp_hostname": state.Hostname, }
"smtp_remote_addr": state.RemoteAddr.String(), return ev
})
} }
func httpContext(r *http.Request) log.Context { func httpContext(r *http.Request) log.Context {

View File

@ -33,6 +33,9 @@ type smtpBackend struct {
mu sync.Mutex mu sync.Mutex
} }
var _ smtp.Backend = (*smtpBackend)(nil)
var _ smtp.Session = (*smtpSession)(nil)
func newMailBackend(conf *Config, handler func(http.ResponseWriter, *http.Request)) *smtpBackend { func newMailBackend(conf *Config, handler func(http.ResponseWriter, *http.Request)) *smtpBackend {
return &smtpBackend{ return &smtpBackend{
config: conf, config: conf,
@ -40,14 +43,9 @@ func newMailBackend(conf *Config, handler func(http.ResponseWriter, *http.Reques
} }
} }
func (b *smtpBackend) Login(state *smtp.ConnectionState, username, _ string) (smtp.Session, error) { func (b *smtpBackend) NewSession(conn *smtp.Conn) (smtp.Session, error) {
logem(state).Debug("Incoming mail, login with user %s", username) logem(conn).Debug("Incoming mail")
return &smtpSession{backend: b, state: state}, nil return &smtpSession{backend: b, conn: conn}, nil
}
func (b *smtpBackend) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) {
logem(state).Debug("Incoming mail, anonymous login")
return &smtpSession{backend: b, state: state}, nil
} }
func (b *smtpBackend) Counts() (total int64, success int64, failure int64) { func (b *smtpBackend) Counts() (total int64, success int64, failure int64) {
@ -59,23 +57,23 @@ func (b *smtpBackend) Counts() (total int64, success int64, failure int64) {
// smtpSession is returned after EHLO. // smtpSession is returned after EHLO.
type smtpSession struct { type smtpSession struct {
backend *smtpBackend backend *smtpBackend
state *smtp.ConnectionState conn *smtp.Conn
topic string topic string
mu sync.Mutex mu sync.Mutex
} }
func (s *smtpSession) AuthPlain(username, password string) error { func (s *smtpSession) AuthPlain(username, _ string) error {
logem(s.state).Debug("AUTH PLAIN (with username %s)", username) logem(s.conn).Field("smtp_username", username).Debug("AUTH PLAIN (with username %s)", username)
return nil return nil
} }
func (s *smtpSession) Mail(from string, opts smtp.MailOptions) error { func (s *smtpSession) Mail(from string, opts *smtp.MailOptions) error {
logem(s.state).Debug("MAIL FROM: %s (with options: %#v)", from, opts) logem(s.conn).Field("smtp_mail_from", from).Debug("MAIL FROM: %s", from)
return nil return nil
} }
func (s *smtpSession) Rcpt(to string) error { func (s *smtpSession) Rcpt(to string) error {
logem(s.state).Debug("RCPT TO: %s", to) logem(s.conn).Field("smtp_rcpt_to", to).Debug("RCPT TO: %s", to)
return s.withFailCount(func() error { return s.withFailCount(func() error {
conf := s.backend.config conf := s.backend.config
addressList, err := mail.ParseAddressList(to) addressList, err := mail.ParseAddressList(to)
@ -112,11 +110,11 @@ func (s *smtpSession) Data(r io.Reader) error {
if err != nil { if err != nil {
return err return err
} }
ev := logem(s.state).Tag(tagSMTP) ev := logem(s.conn)
if ev.IsTrace() { if ev.IsTrace() {
ev.Field("smtp_data", string(b)).Trace("DATA") ev.Field("smtp_data", string(b)).Trace("DATA")
} else if ev.IsDebug() { } else if ev.IsDebug() {
ev.Debug("DATA: %d byte(s)", len(b)) ev.Field("smtp_data_len", len(b)).Debug("DATA")
} }
msg, err := mail.ReadMessage(bytes.NewReader(b)) msg, err := mail.ReadMessage(bytes.NewReader(b))
if err != nil { if err != nil {
@ -156,9 +154,9 @@ func (s *smtpSession) Data(r io.Reader) error {
func (s *smtpSession) publishMessage(m *message) error { func (s *smtpSession) publishMessage(m *message) error {
// Extract remote address (for rate limiting) // Extract remote address (for rate limiting)
remoteAddr, _, err := net.SplitHostPort(s.state.RemoteAddr.String()) remoteAddr, _, err := net.SplitHostPort(s.conn.Conn().RemoteAddr().String())
if err != nil { if err != nil {
remoteAddr = s.state.RemoteAddr.String() remoteAddr = s.conn.Conn().RemoteAddr().String()
} }
// Call HTTP handler with fake HTTP request // Call HTTP handler with fake HTTP request
@ -198,7 +196,7 @@ func (s *smtpSession) withFailCount(fn func() error) error {
if err != nil { if err != nil {
// Almost all of these errors are parse errors, and user input errors. // Almost all of these errors are parse errors, and user input errors.
// We do not want to spam the log with WARN messages. // We do not want to spam the log with WARN messages.
logem(s.state).Err(err).Debug("Incoming mail error") logem(s.conn).Err(err).Debug("Incoming mail error")
s.backend.failure++ s.backend.failure++
} }
return err return err

View File

@ -1,16 +1,23 @@
package server package server
import ( import (
"bufio"
"github.com/emersion/go-smtp" "github.com/emersion/go-smtp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"io"
"net" "net"
"net/http" "net/http"
"strings" "strings"
"testing" "testing"
"time"
) )
func TestSmtpBackend_Multipart(t *testing.T) { func TestSmtpBackend_Multipart(t *testing.T) {
email := `MIME-Version: 1.0 email := `EHLO example.com
MAIL FROM: phil@example.com
RCPT TO: ntfy-mytopic@ntfy.sh
DATA
MIME-Version: 1.0
Date: Tue, 28 Dec 2021 00:30:10 +0100 Date: Tue, 28 Dec 2021 00:30:10 +0100
Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com> Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com>
Subject: and one more Subject: and one more
@ -28,20 +35,25 @@ Content-Type: text/html; charset="UTF-8"
<div dir="ltr">what&#39;s up<br clear="all"><div><br></div></div> <div dir="ltr">what&#39;s up<br clear="all"><div><br></div></div>
--000000000000f3320b05d42915c9--` --000000000000f3320b05d42915c9--
_, backend := newTestBackend(t, func(w http.ResponseWriter, r *http.Request) { .
`
s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "/mytopic", r.URL.Path) require.Equal(t, "/mytopic", r.URL.Path)
require.Equal(t, "and one more", r.Header.Get("Title")) require.Equal(t, "and one more", r.Header.Get("Title"))
require.Equal(t, "what's up", readAll(t, r.Body)) require.Equal(t, "what's up", readAll(t, r.Body))
}) })
session, _ := backend.AnonymousLogin(fakeConnState(t, "1.2.3.4")) defer s.Close()
require.Nil(t, session.Mail("phil@example.com", smtp.MailOptions{})) defer c.Close()
require.Nil(t, session.Rcpt("ntfy-mytopic@ntfy.sh")) writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
require.Nil(t, session.Data(strings.NewReader(email)))
} }
func TestSmtpBackend_MultipartNoBody(t *testing.T) { func TestSmtpBackend_MultipartNoBody(t *testing.T) {
email := `MIME-Version: 1.0 email := `EHLO example.com
MAIL FROM: phil@example.com
RCPT TO: ntfy-emailtest@ntfy.sh
DATA
MIME-Version: 1.0
Date: Tue, 28 Dec 2021 01:33:34 +0100 Date: Tue, 28 Dec 2021 01:33:34 +0100
Message-ID: <CAAvm7ABCDsi9vsuu0WTRXzZQBC8dXrDOLT8iCWdqrsmg@mail.gmail.com> Message-ID: <CAAvm7ABCDsi9vsuu0WTRXzZQBC8dXrDOLT8iCWdqrsmg@mail.gmail.com>
Subject: This email has a subject but no body Subject: This email has a subject but no body
@ -59,20 +71,25 @@ Content-Type: text/html; charset="UTF-8"
<div dir="ltr"><br></div> <div dir="ltr"><br></div>
--000000000000bcf4a405d429f8d4--` --000000000000bcf4a405d429f8d4--
_, backend := newTestBackend(t, func(w http.ResponseWriter, r *http.Request) { .
`
s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "/emailtest", r.URL.Path) require.Equal(t, "/emailtest", r.URL.Path)
require.Equal(t, "", r.Header.Get("Title")) // We flipped message and body require.Equal(t, "", r.Header.Get("Title")) // We flipped message and body
require.Equal(t, "This email has a subject but no body", readAll(t, r.Body)) require.Equal(t, "This email has a subject but no body", readAll(t, r.Body))
}) })
session, _ := backend.AnonymousLogin(fakeConnState(t, "1.2.3.4")) defer s.Close()
require.Nil(t, session.Mail("phil@example.com", smtp.MailOptions{})) defer c.Close()
require.Nil(t, session.Rcpt("ntfy-emailtest@ntfy.sh")) writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
require.Nil(t, session.Data(strings.NewReader(email)))
} }
func TestSmtpBackend_Plaintext(t *testing.T) { func TestSmtpBackend_Plaintext(t *testing.T) {
email := `Date: Tue, 28 Dec 2021 00:30:10 +0100 email := `EHLO example.com
MAIL FROM: phil@example.com
RCPT TO: mytopic@ntfy.sh
DATA
Date: Tue, 28 Dec 2021 00:30:10 +0100
Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com> Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com>
Subject: and one more Subject: and one more
From: Phil <phil@example.com> From: Phil <phil@example.com>
@ -80,56 +97,68 @@ To: mytopic@ntfy.sh
Content-Type: text/plain; charset="UTF-8" Content-Type: text/plain; charset="UTF-8"
what's up what's up
.
` `
conf, backend := newTestBackend(t, func(w http.ResponseWriter, r *http.Request) { s, c, conf, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "/mytopic", r.URL.Path) require.Equal(t, "/mytopic", r.URL.Path)
require.Equal(t, "and one more", r.Header.Get("Title")) require.Equal(t, "and one more", r.Header.Get("Title"))
require.Equal(t, "what's up", readAll(t, r.Body)) require.Equal(t, "what's up", readAll(t, r.Body))
}) })
conf.SMTPServerAddrPrefix = "" conf.SMTPServerAddrPrefix = ""
session, _ := backend.AnonymousLogin(fakeConnState(t, "1.2.3.4")) defer s.Close()
require.Nil(t, session.Mail("phil@example.com", smtp.MailOptions{})) defer c.Close()
require.Nil(t, session.Rcpt("mytopic@ntfy.sh")) writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
require.Nil(t, session.Data(strings.NewReader(email)))
} }
func TestSmtpBackend_Plaintext_No_ContentType(t *testing.T) { func TestSmtpBackend_Plaintext_No_ContentType(t *testing.T) {
email := `Subject: Very short mail email := `EHLO example.com
MAIL FROM: phil@example.com
RCPT TO: mytopic@ntfy.sh
DATA
Subject: Very short mail
what's up what's up
.
` `
conf, backend := newTestBackend(t, func(w http.ResponseWriter, r *http.Request) { s, c, conf, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "/mytopic", r.URL.Path) require.Equal(t, "/mytopic", r.URL.Path)
require.Equal(t, "Very short mail", r.Header.Get("Title")) require.Equal(t, "Very short mail", r.Header.Get("Title"))
require.Equal(t, "what's up", readAll(t, r.Body)) require.Equal(t, "what's up", readAll(t, r.Body))
}) })
conf.SMTPServerAddrPrefix = "" conf.SMTPServerAddrPrefix = ""
session, _ := backend.AnonymousLogin(fakeConnState(t, "1.2.3.4")) defer s.Close()
require.Nil(t, session.Mail("phil@example.com", smtp.MailOptions{})) defer c.Close()
require.Nil(t, session.Rcpt("mytopic@ntfy.sh")) writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
require.Nil(t, session.Data(strings.NewReader(email)))
} }
func TestSmtpBackend_Plaintext_EncodedSubject(t *testing.T) { func TestSmtpBackend_Plaintext_EncodedSubject(t *testing.T) {
email := `Date: Tue, 28 Dec 2021 00:30:10 +0100 email := `EHLO example.com
MAIL FROM: phil@example.com
RCPT TO: ntfy-mytopic@ntfy.sh
DATA
Date: Tue, 28 Dec 2021 00:30:10 +0100
Subject: =?UTF-8?B?VGhyZWUgc2FudGFzIPCfjoXwn46F8J+OhQ==?= Subject: =?UTF-8?B?VGhyZWUgc2FudGFzIPCfjoXwn46F8J+OhQ==?=
From: Phil <phil@example.com> From: Phil <phil@example.com>
To: ntfy-mytopic@ntfy.sh To: ntfy-mytopic@ntfy.sh
Content-Type: text/plain; charset="UTF-8" Content-Type: text/plain; charset="UTF-8"
what's up what's up
.
` `
_, backend := newTestBackend(t, func(w http.ResponseWriter, r *http.Request) { s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, "Three santas 🎅🎅🎅", r.Header.Get("Title")) require.Equal(t, "Three santas 🎅🎅🎅", r.Header.Get("Title"))
}) })
session, _ := backend.AnonymousLogin(fakeConnState(t, "1.2.3.4")) defer s.Close()
require.Nil(t, session.Mail("phil@example.com", smtp.MailOptions{})) defer c.Close()
require.Nil(t, session.Rcpt("ntfy-mytopic@ntfy.sh")) writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
require.Nil(t, session.Data(strings.NewReader(email)))
} }
func TestSmtpBackend_Plaintext_TooLongTruncate(t *testing.T) { func TestSmtpBackend_Plaintext_TooLongTruncate(t *testing.T) {
email := `Date: Tue, 28 Dec 2021 00:30:10 +0100 email := `EHLO example.com
MAIL FROM: phil@example.com
RCPT TO: mytopic@ntfy.sh
DATA
Date: Tue, 28 Dec 2021 00:30:10 +0100
Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com> Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com>
Subject: and one more Subject: and one more
From: Phil <phil@example.com> From: Phil <phil@example.com>
@ -148,60 +177,61 @@ so i'm gonna fill the rest of this with AAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
and with BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB and with BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
that should do it that should do it
.
` `
conf, backend := newTestBackend(t, func(w http.ResponseWriter, r *http.Request) { s, c, conf, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
expected := `you know this is a string. expected := `you know this is a string.
it's a long string. it's a long string.
it's supposed to be longer than the max message length it's supposed to be longer than the max message length
@ -214,68 +244,71 @@ so i'm gonna fill the rest of this with AAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
...................................................................... pppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
and with BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB and with BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBB` BBBBBBBBBBBBBBBBBBBBBBBBB`
require.Equal(t, 4096, len(expected)) // Sanity check require.Equal(t, 4096, len(expected)) // Sanity check
require.Equal(t, expected, readAll(t, r.Body)) require.Equal(t, expected, readAll(t, r.Body))
}) })
defer s.Close()
defer c.Close()
conf.SMTPServerAddrPrefix = "" conf.SMTPServerAddrPrefix = ""
session, _ := backend.AnonymousLogin(fakeConnState(t, "1.2.3.4")) writeAndReadUntilLine(t, email, c, scanner, "250 2.0.0 OK: queued")
require.Nil(t, session.Mail("phil@example.com", smtp.MailOptions{}))
require.Nil(t, session.Rcpt("mytopic@ntfy.sh"))
require.Nil(t, session.Data(strings.NewReader(email)))
} }
func TestSmtpBackend_Unsupported(t *testing.T) { func TestSmtpBackend_Unsupported(t *testing.T) {
email := `Date: Tue, 28 Dec 2021 00:30:10 +0100 email := `EHLO example.com
MAIL FROM: phil@example.com
RCPT TO: ntfy-mytopic@ntfy.sh
DATA
Date: Tue, 28 Dec 2021 00:30:10 +0100
Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com> Message-ID: <CAAvm79YP0C=Rt1N=KWmSUBB87KK2rRChmdzKqF1vCwMEUiVzLQ@mail.gmail.com>
Subject: and one more Subject: and one more
From: Phil <phil@example.com> From: Phil <phil@example.com>
@ -283,34 +316,89 @@ To: mytopic@ntfy.sh
Content-Type: text/SOMETHINGELSE Content-Type: text/SOMETHINGELSE
what's up what's up
.
` `
conf, backend := newTestBackend(t, func(http.ResponseWriter, *http.Request) { s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
// Nothing. t.Fatal("This should not be called")
}) })
conf.SMTPServerAddrPrefix = "" defer s.Close()
session, _ := backend.Login(fakeConnState(t, "1.2.3.4"), "user", "pass") defer c.Close()
require.Nil(t, session.Mail("phil@example.com", smtp.MailOptions{})) writeAndReadUntilLine(t, email, c, scanner, "554 5.0.0 Error: transaction failed, blame it on the weather: unsupported content type")
require.Nil(t, session.Rcpt("mytopic@ntfy.sh"))
require.Equal(t, errUnsupportedContentType, session.Data(strings.NewReader(email)))
} }
func newTestBackend(t *testing.T, handler func(http.ResponseWriter, *http.Request)) (*Config, *smtpBackend) { func TestSmtpBackend_InvalidAddress(t *testing.T) {
conf := newTestConfig(t) email := `EHLO example.com
MAIL FROM: phil@example.com
RCPT TO: unsupported@ntfy.sh
DATA
Date: Tue, 28 Dec 2021 00:30:10 +0100
Subject: and one more
From: Phil <phil@example.com>
To: mytopic@ntfy.sh
Content-Type: text/plain
what's up
.
`
s, c, _, scanner := newTestSMTPServer(t, func(w http.ResponseWriter, r *http.Request) {
t.Fatal("This should not be called")
})
defer s.Close()
defer c.Close()
writeAndReadUntilLine(t, email, c, scanner, "451 4.0.0 invalid address")
}
type smtpHandlerFunc func(http.ResponseWriter, *http.Request)
func newTestSMTPServer(t *testing.T, handler smtpHandlerFunc) (s *smtp.Server, c net.Conn, conf *Config, scanner *bufio.Scanner) {
conf = newTestConfig(t)
conf.SMTPServerListen = ":25" conf.SMTPServerListen = ":25"
conf.SMTPServerDomain = "ntfy.sh" conf.SMTPServerDomain = "ntfy.sh"
conf.SMTPServerAddrPrefix = "ntfy-" conf.SMTPServerAddrPrefix = "ntfy-"
backend := newMailBackend(conf, handler) backend := newMailBackend(conf, handler)
return conf, backend l, err := net.Listen("tcp", "127.0.0.1:0")
}
func fakeConnState(t *testing.T, remoteAddr string) *smtp.ConnectionState {
ip, err := net.ResolveIPAddr("ip", remoteAddr)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
return &smtp.ConnectionState{ s = smtp.NewServer(backend)
Hostname: "myhostname", s.Domain = conf.SMTPServerDomain
LocalAddr: ip, s.AllowInsecureAuth = true
RemoteAddr: ip, go func() {
require.Nil(t, s.Serve(l))
}()
c, err = net.Dial("tcp", l.Addr().String())
if err != nil {
t.Fatal(err)
} }
scanner = bufio.NewScanner(c)
return
}
func writeAndReadUntilLine(t *testing.T, email string, conn net.Conn, scanner *bufio.Scanner, expectedLine string) {
_, err := io.WriteString(conn, email)
require.Nil(t, err)
readUntilLine(t, conn, scanner, expectedLine)
}
func readUntilLine(t *testing.T, conn net.Conn, scanner *bufio.Scanner, expectedLine string) {
cancelChan := make(chan bool)
go func() {
select {
case <-cancelChan:
case <-time.After(3 * time.Second):
conn.Close()
t.Error("Failed waiting for expected output")
}
}()
var output string
for scanner.Scan() {
text := scanner.Text()
if strings.TrimSpace(text) == expectedLine {
cancelChan <- true
return
}
output += text + "\n"
//fmt.Println(text)
}
t.Fatalf("Expected line '%s' not found in output:\n%s", expectedLine, output)
} }

13185
web/package-lock.json generated

File diff suppressed because it is too large Load Diff