123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830 |
- package server
- import (
- "fmt"
- "github.com/stretchr/testify/require"
- "heckel.io/ntfy/log"
- "heckel.io/ntfy/user"
- "heckel.io/ntfy/util"
- "io"
- "net/netip"
- "path/filepath"
- "strings"
- "testing"
- "time"
- )
- func TestAccount_Signup_Success(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.EnableSignup = true
- s := newTestServer(t, conf)
- defer s.closeDatabases()
- rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- token, _ := util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
- require.NotEmpty(t, token.Token)
- require.True(t, time.Now().Add(71*time.Hour).Unix() < token.Expires)
- require.True(t, strings.HasPrefix(token.Token, "tk_"))
- require.Equal(t, "9.9.9.9", token.LastOrigin)
- require.True(t, token.LastAccess > time.Now().Unix()-2)
- require.True(t, token.LastAccess < time.Now().Unix()+2)
- rr = request(t, s, "GET", "/v1/account", "", map[string]string{
- "Authorization": util.BearerAuth(token.Token),
- })
- require.Equal(t, 200, rr.Code)
- account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
- require.Equal(t, "phil", account.Username)
- require.Equal(t, "user", account.Role)
- rr = request(t, s, "GET", "/v1/account", "", map[string]string{
- "Authorization": util.BasicAuth("", token.Token), // We allow a fake basic auth to make curl-ing easier (curl -u :<token>)
- })
- require.Equal(t, 200, rr.Code)
- account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
- require.Equal(t, "phil", account.Username)
- }
- func TestAccount_Signup_UserExists(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.EnableSignup = true
- s := newTestServer(t, conf)
- defer s.closeDatabases()
- rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
- require.Equal(t, 409, rr.Code)
- require.Equal(t, 40901, toHTTPError(t, rr.Body.String()).Code)
- }
- func TestAccount_Signup_LimitReached(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.EnableSignup = true
- s := newTestServer(t, conf)
- defer s.closeDatabases()
- for i := 0; i < 3; i++ {
- rr := request(t, s, "POST", "/v1/account", fmt.Sprintf(`{"username":"phil%d", "password":"mypass"}`, i), nil)
- require.Equal(t, 200, rr.Code)
- }
- rr := request(t, s, "POST", "/v1/account", `{"username":"thiswontwork", "password":"mypass"}`, nil)
- require.Equal(t, 429, rr.Code)
- require.Equal(t, 42906, toHTTPError(t, rr.Body.String()).Code)
- }
- func TestAccount_Signup_AsUser(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.EnableSignup = true
- s := newTestServer(t, conf)
- defer s.closeDatabases()
- log.Info("1")
- require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
- log.Info("2")
- require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
- log.Info("3")
- rr := request(t, s, "POST", "/v1/account", `{"username":"emma", "password":"emma"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 200, rr.Code)
- log.Info("4")
- rr = request(t, s, "POST", "/v1/account", `{"username":"marian", "password":"marian"}`, map[string]string{
- "Authorization": util.BasicAuth("ben", "ben"),
- })
- require.Equal(t, 401, rr.Code)
- }
- func TestAccount_Signup_Disabled(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.EnableSignup = false
- s := newTestServer(t, conf)
- defer s.closeDatabases()
- rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
- require.Equal(t, 400, rr.Code)
- require.Equal(t, 40022, toHTTPError(t, rr.Body.String()).Code)
- }
- func TestAccount_Signup_Rate_Limit(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.EnableSignup = true
- s := newTestServer(t, conf)
- for i := 0; i < 3; i++ {
- rr := request(t, s, "POST", "/v1/account", fmt.Sprintf(`{"username":"phil%d", "password":"mypass"}`, i), nil)
- require.Equal(t, 200, rr.Code, "failed on iteration %d", i)
- }
- rr := request(t, s, "POST", "/v1/account", `{"username":"notallowed", "password":"mypass"}`, nil)
- require.Equal(t, 429, rr.Code)
- require.Equal(t, 42906, toHTTPError(t, rr.Body.String()).Code)
- }
- func TestAccount_Get_Anonymous(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.VisitorRequestLimitReplenish = 86 * time.Second
- conf.VisitorEmailLimitReplenish = time.Hour
- conf.VisitorAttachmentTotalSizeLimit = 5123
- conf.AttachmentFileSizeLimit = 512
- s := newTestServer(t, conf)
- s.smtpSender = &testMailer{}
- defer s.closeDatabases()
- rr := request(t, s, "GET", "/v1/account", "", nil)
- require.Equal(t, 200, rr.Code)
- account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
- require.Equal(t, "*", account.Username)
- require.Equal(t, string(user.RoleAnonymous), account.Role)
- require.Equal(t, "ip", account.Limits.Basis)
- require.Equal(t, int64(1004), account.Limits.Messages) // I hate this
- require.Equal(t, int64(24), account.Limits.Emails) // I hate this
- require.Equal(t, int64(5123), account.Limits.AttachmentTotalSize)
- require.Equal(t, int64(512), account.Limits.AttachmentFileSize)
- require.Equal(t, int64(0), account.Stats.Messages)
- require.Equal(t, int64(1004), account.Stats.MessagesRemaining)
- require.Equal(t, int64(0), account.Stats.Emails)
- require.Equal(t, int64(24), account.Stats.EmailsRemaining)
- rr = request(t, s, "POST", "/mytopic", "", nil)
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "POST", "/mytopic", "", map[string]string{
- "Email": "phil@ntfy.sh",
- })
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "GET", "/v1/account", "", nil)
- require.Equal(t, 200, rr.Code)
- account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
- require.Equal(t, int64(2), account.Stats.Messages)
- require.Equal(t, int64(1002), account.Stats.MessagesRemaining)
- require.Equal(t, int64(1), account.Stats.Emails)
- require.Equal(t, int64(23), account.Stats.EmailsRemaining)
- }
- func TestAccount_ChangeSettings(t *testing.T) {
- s := newTestServer(t, newTestConfigWithAuthFile(t))
- defer s.closeDatabases()
- require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
- u, _ := s.userManager.User("phil")
- token, _ := s.userManager.CreateToken(u.ID, "", time.Unix(0, 0), netip.IPv4Unspecified())
- rr := request(t, s, "PATCH", "/v1/account/settings", `{"notification": {"sound": "juntos"},"ignored": true}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "PATCH", "/v1/account/settings", `{"notification": {"delete_after": 86400}, "language": "de"}`, map[string]string{
- "Authorization": util.BearerAuth(token.Value),
- })
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "GET", "/v1/account", `{"username":"marian", "password":"marian"}`, map[string]string{
- "Authorization": util.BearerAuth(token.Value),
- })
- require.Equal(t, 200, rr.Code)
- account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
- require.Equal(t, "de", account.Language)
- require.Equal(t, util.Int(86400), account.Notification.DeleteAfter)
- require.Equal(t, util.String("juntos"), account.Notification.Sound)
- require.Nil(t, account.Notification.MinPriority) // Not set
- }
- func TestAccount_Subscription_AddUpdateDelete(t *testing.T) {
- s := newTestServer(t, newTestConfigWithAuthFile(t))
- defer s.closeDatabases()
- require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
- rr := request(t, s, "POST", "/v1/account/subscription", `{"base_url": "http://abc.com", "topic": "def"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "GET", "/v1/account", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 200, rr.Code)
- account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
- require.Equal(t, 1, len(account.Subscriptions))
- require.Equal(t, "http://abc.com", account.Subscriptions[0].BaseURL)
- require.Equal(t, "def", account.Subscriptions[0].Topic)
- require.Nil(t, account.Subscriptions[0].DisplayName)
- rr = request(t, s, "PATCH", "/v1/account/subscription", `{"base_url": "http://abc.com", "topic": "def", "display_name": "ding dong"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "GET", "/v1/account", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 200, rr.Code)
- account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
- require.Equal(t, 1, len(account.Subscriptions))
- require.Equal(t, "http://abc.com", account.Subscriptions[0].BaseURL)
- require.Equal(t, "def", account.Subscriptions[0].Topic)
- require.Equal(t, util.String("ding dong"), account.Subscriptions[0].DisplayName)
- rr = request(t, s, "DELETE", "/v1/account/subscription", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- "X-BaseURL": "http://abc.com",
- "X-Topic": "def",
- })
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "GET", "/v1/account", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 200, rr.Code)
- account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
- require.Equal(t, 0, len(account.Subscriptions))
- }
- func TestAccount_ChangePassword(t *testing.T) {
- s := newTestServer(t, newTestConfigWithAuthFile(t))
- defer s.closeDatabases()
- require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
- rr := request(t, s, "POST", "/v1/account/password", `{"password": "WRONG", "new_password": ""}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 400, rr.Code)
- rr = request(t, s, "POST", "/v1/account/password", `{"password": "WRONG", "new_password": "new password"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 400, rr.Code)
- require.Equal(t, 40026, toHTTPError(t, rr.Body.String()).Code)
- rr = request(t, s, "POST", "/v1/account/password", `{"password": "phil", "new_password": "new password"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "GET", "/v1/account", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 401, rr.Code)
- rr = request(t, s, "GET", "/v1/account", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "new password"),
- })
- require.Equal(t, 200, rr.Code)
- }
- func TestAccount_ChangePassword_NoAccount(t *testing.T) {
- s := newTestServer(t, newTestConfigWithAuthFile(t))
- defer s.closeDatabases()
- rr := request(t, s, "POST", "/v1/account/password", `{"password": "new password"}`, nil)
- require.Equal(t, 401, rr.Code)
- }
- func TestAccount_ExtendToken(t *testing.T) {
- s := newTestServer(t, newTestConfigWithAuthFile(t))
- defer s.closeDatabases()
- require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
- rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 200, rr.Code)
- token, err := util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
- require.Nil(t, err)
- time.Sleep(time.Second)
- rr = request(t, s, "PATCH", "/v1/account/token", "", map[string]string{
- "Authorization": util.BearerAuth(token.Token),
- })
- require.Equal(t, 200, rr.Code)
- extendedToken, err := util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
- require.Nil(t, err)
- require.Equal(t, token.Token, extendedToken.Token)
- require.True(t, token.Expires < extendedToken.Expires)
- expires := time.Now().Add(999 * time.Hour)
- body := fmt.Sprintf(`{"token":"%s", "label":"some label", "expires": %d}`, token.Token, expires.Unix())
- rr = request(t, s, "PATCH", "/v1/account/token", body, map[string]string{
- "Authorization": util.BearerAuth(token.Token),
- })
- require.Equal(t, 200, rr.Code)
- token, err = util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
- require.Nil(t, err)
- require.Equal(t, "some label", token.Label)
- require.Equal(t, expires.Unix(), token.Expires)
- }
- func TestAccount_ExtendToken_NoTokenProvided(t *testing.T) {
- s := newTestServer(t, newTestConfigWithAuthFile(t))
- defer s.closeDatabases()
- require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
- rr := request(t, s, "PATCH", "/v1/account/token", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"), // Not Bearer!
- })
- require.Equal(t, 400, rr.Code)
- require.Equal(t, 40023, toHTTPError(t, rr.Body.String()).Code)
- }
- func TestAccount_DeleteToken(t *testing.T) {
- s := newTestServer(t, newTestConfigWithAuthFile(t))
- defer s.closeDatabases()
- require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
- rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 200, rr.Code)
- token, err := util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
- require.Nil(t, err)
- require.True(t, token.Expires > time.Now().Add(71*time.Hour).Unix())
- // Delete token failure (using basic auth)
- rr = request(t, s, "DELETE", "/v1/account/token", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"), // Not Bearer!
- })
- require.Equal(t, 400, rr.Code)
- require.Equal(t, 40023, toHTTPError(t, rr.Body.String()).Code)
- // Delete token with wrong token
- rr = request(t, s, "DELETE", "/v1/account/token", "", map[string]string{
- "Authorization": util.BearerAuth("invalidtoken"),
- })
- require.Equal(t, 401, rr.Code)
- // Delete token with correct token
- rr = request(t, s, "DELETE", "/v1/account/token", "", map[string]string{
- "Authorization": util.BearerAuth(token.Token),
- })
- require.Equal(t, 200, rr.Code)
- // Cannot get account anymore
- rr = request(t, s, "GET", "/v1/account", "", map[string]string{
- "Authorization": util.BearerAuth(token.Token),
- })
- require.Equal(t, 401, rr.Code)
- }
- func TestAccount_Delete_Success(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.EnableSignup = true
- s := newTestServer(t, conf)
- rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "GET", "/v1/account", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "DELETE", "/v1/account", `{"password":"mypass"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- // Account was marked deleted
- rr = request(t, s, "GET", "/v1/account", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 401, rr.Code)
- // Cannot re-create account, since still exists
- rr = request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
- require.Equal(t, 409, rr.Code)
- }
- func TestAccount_Delete_Not_Allowed(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.EnableSignup = true
- s := newTestServer(t, conf)
- rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "DELETE", "/v1/account", "", nil)
- require.Equal(t, 401, rr.Code)
- rr = request(t, s, "DELETE", "/v1/account", `{"password":"mypass"}`, nil)
- require.Equal(t, 401, rr.Code)
- rr = request(t, s, "DELETE", "/v1/account", `{"password":"INCORRECT"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 400, rr.Code)
- require.Equal(t, 40026, toHTTPError(t, rr.Body.String()).Code)
- }
- func TestAccount_Reservation_AddWithoutTierFails(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.EnableSignup = true
- s := newTestServer(t, conf)
- rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "POST", "/v1/account/reservation", `{"topic":"mytopic", "everyone":"deny-all"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 401, rr.Code)
- }
- func TestAccount_Reservation_AddAdminSuccess(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.EnableSignup = true
- s := newTestServer(t, conf)
- // A user, an admin, and a reservation walk into a bar
- require.Nil(t, s.userManager.AddTier(&user.Tier{
- Code: "pro",
- ReservationLimit: 2,
- }))
- require.Nil(t, s.userManager.AddUser("noadmin1", "pass", user.RoleUser))
- require.Nil(t, s.userManager.ChangeTier("noadmin1", "pro"))
- require.Nil(t, s.userManager.AddReservation("noadmin1", "mytopic", user.PermissionDenyAll))
- require.Nil(t, s.userManager.AddUser("noadmin2", "pass", user.RoleUser))
- require.Nil(t, s.userManager.ChangeTier("noadmin2", "pro"))
- require.Nil(t, s.userManager.AddUser("phil", "adminpass", user.RoleAdmin))
- // Admin can reserve topic
- rr := request(t, s, "POST", "/v1/account/reservation", `{"topic":"sometopic","everyone":"deny-all"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "adminpass"),
- })
- require.Equal(t, 200, rr.Code)
- // User cannot reserve already reserved topic
- rr = request(t, s, "POST", "/v1/account/reservation", `{"topic":"mytopic","everyone":"deny-all"}`, map[string]string{
- "Authorization": util.BasicAuth("noadmin2", "pass"),
- })
- require.Equal(t, 409, rr.Code)
- // Admin cannot reserve already reserved topic
- rr = request(t, s, "POST", "/v1/account/reservation", `{"topic":"mytopic","everyone":"deny-all"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "adminpass"),
- })
- require.Equal(t, 409, rr.Code)
- reservations, err := s.userManager.Reservations("phil")
- require.Nil(t, err)
- require.Equal(t, 1, len(reservations))
- require.Equal(t, "sometopic", reservations[0].Topic)
- reservations, err = s.userManager.Reservations("noadmin1")
- require.Nil(t, err)
- require.Equal(t, 1, len(reservations))
- require.Equal(t, "mytopic", reservations[0].Topic)
- reservations, err = s.userManager.Reservations("noadmin2")
- require.Nil(t, err)
- require.Equal(t, 0, len(reservations))
- }
- func TestAccount_Reservation_AddRemoveUserWithTierSuccess(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.EnableSignup = true
- s := newTestServer(t, conf)
- // Create user
- rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
- require.Equal(t, 200, rr.Code)
- // Create a tier
- require.Nil(t, s.userManager.AddTier(&user.Tier{
- Code: "pro",
- MessageLimit: 123,
- MessageExpiryDuration: 86400 * time.Second,
- EmailLimit: 32,
- ReservationLimit: 2,
- AttachmentFileSizeLimit: 1231231,
- AttachmentTotalSizeLimit: 123123,
- AttachmentExpiryDuration: 10800 * time.Second,
- AttachmentBandwidthLimit: 21474836480,
- }))
- require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
- // Reserve two topics
- rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "mytopic", "everyone":"deny-all"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "another", "everyone":"read-only"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- // Trying to reserve a third should fail
- rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "yet-another", "everyone":"deny-all"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 429, rr.Code)
- // Modify existing should still work
- rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "another", "everyone":"write-only"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- // Check account result
- rr = request(t, s, "GET", "/v1/account", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
- require.Equal(t, "pro", account.Tier.Code)
- require.Equal(t, int64(123), account.Limits.Messages)
- require.Equal(t, int64(86400), account.Limits.MessagesExpiryDuration)
- require.Equal(t, int64(32), account.Limits.Emails)
- require.Equal(t, int64(2), account.Limits.Reservations)
- require.Equal(t, int64(1231231), account.Limits.AttachmentFileSize)
- require.Equal(t, int64(123123), account.Limits.AttachmentTotalSize)
- require.Equal(t, int64(10800), account.Limits.AttachmentExpiryDuration)
- require.Equal(t, int64(21474836480), account.Limits.AttachmentBandwidth)
- require.Equal(t, 2, len(account.Reservations))
- require.Equal(t, "another", account.Reservations[0].Topic)
- require.Equal(t, "write-only", account.Reservations[0].Everyone)
- require.Equal(t, "mytopic", account.Reservations[1].Topic)
- require.Equal(t, "deny-all", account.Reservations[1].Everyone)
- // Delete and re-check
- rr = request(t, s, "DELETE", "/v1/account/reservation/another", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "GET", "/v1/account", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
- require.Equal(t, 1, len(account.Reservations))
- require.Equal(t, "mytopic", account.Reservations[0].Topic)
- }
- func TestAccount_Reservation_PublishByAnonymousFails(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.AuthDefault = user.PermissionReadWrite
- conf.EnableSignup = true
- s := newTestServer(t, conf)
- // Create user with tier
- rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
- require.Equal(t, 200, rr.Code)
- require.Nil(t, s.userManager.AddTier(&user.Tier{
- Code: "pro",
- MessageLimit: 20,
- ReservationLimit: 2,
- }))
- require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
- // Reserve a topic
- rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "mytopic", "everyone":"deny-all"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- // Publish a message
- rr = request(t, s, "POST", "/mytopic", `Howdy`, map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- // Publish a message (as anonymous)
- rr = request(t, s, "POST", "/mytopic", `Howdy`, nil)
- require.Equal(t, 403, rr.Code)
- }
- func TestAccount_Reservation_Delete_Messages_And_Attachments(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.AuthDefault = user.PermissionReadWrite
- s := newTestServer(t, conf)
- // Create user with tier
- require.Nil(t, s.userManager.AddUser("phil", "mypass", user.RoleUser))
- require.Nil(t, s.userManager.AddTier(&user.Tier{
- Code: "pro",
- MessageLimit: 20,
- MessageExpiryDuration: time.Hour,
- ReservationLimit: 2,
- AttachmentTotalSizeLimit: 10000,
- AttachmentFileSizeLimit: 10000,
- AttachmentExpiryDuration: time.Hour,
- AttachmentBandwidthLimit: 10000,
- }))
- require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
- // Reserve two topics "mytopic1" and "mytopic2"
- rr := request(t, s, "POST", "/v1/account/reservation", `{"topic": "mytopic1", "everyone":"deny-all"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "mytopic2", "everyone":"deny-all"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- // Publish a message with attachment to each topic
- rr = request(t, s, "POST", "/mytopic1?f=attach.txt", `Howdy`, map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- m1 := toMessage(t, rr.Body.String())
- require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m1.ID))
- rr = request(t, s, "POST", "/mytopic2?f=attach.txt", `Howdy`, map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- m2 := toMessage(t, rr.Body.String())
- require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m2.ID))
- // Delete reservation
- rr = request(t, s, "DELETE", "/v1/account/reservation/mytopic1", ``, map[string]string{
- "X-Delete-Messages": "true",
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- rr = request(t, s, "DELETE", "/v1/account/reservation/mytopic2", ``, map[string]string{
- "X-Delete-Messages": "false",
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- // Verify that messages and attachments were deleted
- // This does not explicitly call the manager!
- time.Sleep(time.Second)
- ms, err := s.messageCache.Messages("mytopic1", sinceAllMessages, false)
- require.Nil(t, err)
- require.Equal(t, 0, len(ms))
- require.NoFileExists(t, filepath.Join(s.config.AttachmentCacheDir, m1.ID))
- ms, err = s.messageCache.Messages("mytopic2", sinceAllMessages, false)
- require.Nil(t, err)
- require.Equal(t, 1, len(ms))
- require.Equal(t, m2.ID, ms[0].ID)
- require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m2.ID))
- }
- func TestAccount_Reservation_Add_Kills_Other_Subscribers(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.AuthDefault = user.PermissionReadWrite
- conf.EnableSignup = true
- s := newTestServer(t, conf)
- defer s.closeDatabases()
- // Create user with tier
- rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
- require.Equal(t, 200, rr.Code)
- require.Nil(t, s.userManager.AddTier(&user.Tier{
- Code: "pro",
- MessageLimit: 20,
- ReservationLimit: 2,
- }))
- require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
- // Subscribe anonymously
- anonCh, userCh := make(chan bool), make(chan bool)
- go func() {
- rr := request(t, s, "GET", "/mytopic/json", ``, nil) // This blocks until it's killed!
- require.Equal(t, 200, rr.Code)
- messages := toMessages(t, rr.Body.String())
- require.Equal(t, 2, len(messages)) // This is the meat. We should NOT receive the second message!
- require.Equal(t, "open", messages[0].Event)
- require.Equal(t, "message before reservation", messages[1].Message)
- anonCh <- true
- log.Info("Anonymous subscription ended")
- }()
- // Subscribe with user
- go func() {
- rr := request(t, s, "GET", "/mytopic/json", ``, map[string]string{ // Blocks!
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- messages := toMessages(t, rr.Body.String())
- require.Equal(t, 3, len(messages))
- require.Equal(t, "open", messages[0].Event)
- require.Equal(t, "message before reservation", messages[1].Message)
- require.Equal(t, "message after reservation", messages[2].Message)
- userCh <- true
- log.Info("User subscription ended")
- }()
- // Publish message (before reservation)
- time.Sleep(2 * time.Second) // Wait for subscribers
- rr = request(t, s, "POST", "/mytopic", "message before reservation", nil)
- require.Equal(t, 200, rr.Code)
- time.Sleep(2 * time.Second) // Wait for subscribers to receive message
- // Reserve a topic
- rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "mytopic", "everyone":"deny-all"}`, map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- // Everyone but phil should be killed
- select {
- case <-anonCh:
- case <-time.After(5 * time.Second):
- t.Fatal("Waiting for anonymous subscription to be killed failed")
- }
- // Publish a message
- rr = request(t, s, "POST", "/mytopic", "message after reservation", map[string]string{
- "Authorization": util.BasicAuth("phil", "mypass"),
- })
- require.Equal(t, 200, rr.Code)
- // Kill user Go routine
- s.topics["mytopic"].CancelSubscribers("<invalid>")
- select {
- case <-userCh:
- case <-time.After(5 * time.Second):
- t.Fatal("Waiting for user subscription to be killed failed")
- }
- }
- func TestAccount_Persist_UserStats_After_Tier_Change(t *testing.T) {
- conf := newTestConfigWithAuthFile(t)
- conf.AuthDefault = user.PermissionReadWrite
- conf.AuthStatsQueueWriterInterval = 200 * time.Millisecond
- s := newTestServer(t, conf)
- defer s.closeDatabases()
- // Create user with tier
- require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
- require.Nil(t, s.userManager.AddTier(&user.Tier{
- Code: "starter",
- MessageLimit: 10,
- }))
- require.Nil(t, s.userManager.AddTier(&user.Tier{
- Code: "pro",
- MessageLimit: 20,
- }))
- require.Nil(t, s.userManager.ChangeTier("phil", "starter"))
- // Publish a message
- rr := request(t, s, "POST", "/mytopic", "hi", map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 200, rr.Code)
- // Wait for stats queue writer
- time.Sleep(300 * time.Millisecond)
- // Verify that message stats were persisted
- u, err := s.userManager.User("phil")
- require.Nil(t, err)
- require.Equal(t, int64(1), u.Stats.Messages)
- // Change tier, make a request (to reset limiters)
- require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
- rr = request(t, s, "GET", "/v1/account", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 200, rr.Code)
- account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
- require.Equal(t, int64(1), account.Stats.Messages) // Is not reset!
- // Publish another message
- rr = request(t, s, "POST", "/mytopic", "hi", map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 200, rr.Code)
- // Verify that message stats were persisted
- time.Sleep(300 * time.Millisecond)
- u, err = s.userManager.User("phil")
- require.Nil(t, err)
- require.Equal(t, int64(2), u.Stats.Messages) // v.EnqueueUserStats had run!
- // Stats keep counting
- rr = request(t, s, "GET", "/v1/account", "", map[string]string{
- "Authorization": util.BasicAuth("phil", "phil"),
- })
- require.Equal(t, 200, rr.Code)
- account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
- require.Equal(t, int64(2), account.Stats.Messages) // Is not reset!
- }
|