server_account_test.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806
  1. package server
  2. import (
  3. "fmt"
  4. "github.com/stretchr/testify/require"
  5. "heckel.io/ntfy/log"
  6. "heckel.io/ntfy/user"
  7. "heckel.io/ntfy/util"
  8. "io"
  9. "net/netip"
  10. "path/filepath"
  11. "strings"
  12. "testing"
  13. "time"
  14. )
  15. func TestAccount_Signup_Success(t *testing.T) {
  16. conf := newTestConfigWithAuthFile(t)
  17. conf.EnableSignup = true
  18. s := newTestServer(t, conf)
  19. defer s.closeDatabases()
  20. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  21. require.Equal(t, 200, rr.Code)
  22. rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
  23. "Authorization": util.BasicAuth("phil", "mypass"),
  24. })
  25. require.Equal(t, 200, rr.Code)
  26. token, _ := util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
  27. require.NotEmpty(t, token.Token)
  28. require.True(t, time.Now().Add(71*time.Hour).Unix() < token.Expires)
  29. require.True(t, strings.HasPrefix(token.Token, "tk_"))
  30. require.Equal(t, "9.9.9.9", token.LastOrigin)
  31. require.True(t, token.LastAccess > time.Now().Unix()-2)
  32. require.True(t, token.LastAccess < time.Now().Unix()+2)
  33. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  34. "Authorization": util.BearerAuth(token.Token),
  35. })
  36. require.Equal(t, 200, rr.Code)
  37. account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  38. require.Equal(t, "phil", account.Username)
  39. require.Equal(t, "user", account.Role)
  40. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  41. "Authorization": util.BasicAuth("", token.Token), // We allow a fake basic auth to make curl-ing easier (curl -u :<token>)
  42. })
  43. require.Equal(t, 200, rr.Code)
  44. account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  45. require.Equal(t, "phil", account.Username)
  46. }
  47. func TestAccount_Signup_UserExists(t *testing.T) {
  48. conf := newTestConfigWithAuthFile(t)
  49. conf.EnableSignup = true
  50. s := newTestServer(t, conf)
  51. defer s.closeDatabases()
  52. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  53. require.Equal(t, 200, rr.Code)
  54. rr = request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  55. require.Equal(t, 409, rr.Code)
  56. require.Equal(t, 40901, toHTTPError(t, rr.Body.String()).Code)
  57. }
  58. func TestAccount_Signup_LimitReached(t *testing.T) {
  59. conf := newTestConfigWithAuthFile(t)
  60. conf.EnableSignup = true
  61. s := newTestServer(t, conf)
  62. defer s.closeDatabases()
  63. for i := 0; i < 3; i++ {
  64. rr := request(t, s, "POST", "/v1/account", fmt.Sprintf(`{"username":"phil%d", "password":"mypass"}`, i), nil)
  65. require.Equal(t, 200, rr.Code)
  66. }
  67. rr := request(t, s, "POST", "/v1/account", `{"username":"thiswontwork", "password":"mypass"}`, nil)
  68. require.Equal(t, 429, rr.Code)
  69. require.Equal(t, 42906, toHTTPError(t, rr.Body.String()).Code)
  70. }
  71. func TestAccount_Signup_AsUser(t *testing.T) {
  72. conf := newTestConfigWithAuthFile(t)
  73. conf.EnableSignup = true
  74. s := newTestServer(t, conf)
  75. defer s.closeDatabases()
  76. log.Info("1")
  77. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
  78. log.Info("2")
  79. require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
  80. log.Info("3")
  81. rr := request(t, s, "POST", "/v1/account", `{"username":"emma", "password":"emma"}`, map[string]string{
  82. "Authorization": util.BasicAuth("phil", "phil"),
  83. })
  84. require.Equal(t, 200, rr.Code)
  85. log.Info("4")
  86. rr = request(t, s, "POST", "/v1/account", `{"username":"marian", "password":"marian"}`, map[string]string{
  87. "Authorization": util.BasicAuth("ben", "ben"),
  88. })
  89. require.Equal(t, 401, rr.Code)
  90. }
  91. func TestAccount_Signup_Disabled(t *testing.T) {
  92. conf := newTestConfigWithAuthFile(t)
  93. conf.EnableSignup = false
  94. s := newTestServer(t, conf)
  95. defer s.closeDatabases()
  96. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  97. require.Equal(t, 400, rr.Code)
  98. require.Equal(t, 40022, toHTTPError(t, rr.Body.String()).Code)
  99. }
  100. func TestAccount_Signup_Rate_Limit(t *testing.T) {
  101. conf := newTestConfigWithAuthFile(t)
  102. conf.EnableSignup = true
  103. s := newTestServer(t, conf)
  104. for i := 0; i < 3; i++ {
  105. rr := request(t, s, "POST", "/v1/account", fmt.Sprintf(`{"username":"phil%d", "password":"mypass"}`, i), nil)
  106. require.Equal(t, 200, rr.Code, "failed on iteration %d", i)
  107. }
  108. rr := request(t, s, "POST", "/v1/account", `{"username":"notallowed", "password":"mypass"}`, nil)
  109. require.Equal(t, 429, rr.Code)
  110. require.Equal(t, 42906, toHTTPError(t, rr.Body.String()).Code)
  111. }
  112. func TestAccount_Get_Anonymous(t *testing.T) {
  113. conf := newTestConfigWithAuthFile(t)
  114. conf.VisitorRequestLimitReplenish = 86 * time.Second
  115. conf.VisitorEmailLimitReplenish = time.Hour
  116. conf.VisitorAttachmentTotalSizeLimit = 5123
  117. conf.AttachmentFileSizeLimit = 512
  118. s := newTestServer(t, conf)
  119. s.smtpSender = &testMailer{}
  120. defer s.closeDatabases()
  121. rr := request(t, s, "GET", "/v1/account", "", nil)
  122. require.Equal(t, 200, rr.Code)
  123. account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  124. require.Equal(t, "*", account.Username)
  125. require.Equal(t, string(user.RoleAnonymous), account.Role)
  126. require.Equal(t, "ip", account.Limits.Basis)
  127. require.Equal(t, int64(1004), account.Limits.Messages) // I hate this
  128. require.Equal(t, int64(24), account.Limits.Emails) // I hate this
  129. require.Equal(t, int64(5123), account.Limits.AttachmentTotalSize)
  130. require.Equal(t, int64(512), account.Limits.AttachmentFileSize)
  131. require.Equal(t, int64(0), account.Stats.Messages)
  132. require.Equal(t, int64(1004), account.Stats.MessagesRemaining)
  133. require.Equal(t, int64(0), account.Stats.Emails)
  134. require.Equal(t, int64(24), account.Stats.EmailsRemaining)
  135. require.Equal(t, int64(0), account.Stats.Calls)
  136. require.Equal(t, int64(0), account.Stats.CallsRemaining)
  137. rr = request(t, s, "POST", "/mytopic", "", nil)
  138. require.Equal(t, 200, rr.Code)
  139. rr = request(t, s, "POST", "/mytopic", "", map[string]string{
  140. "Email": "phil@ntfy.sh",
  141. })
  142. require.Equal(t, 200, rr.Code)
  143. rr = request(t, s, "GET", "/v1/account", "", nil)
  144. require.Equal(t, 200, rr.Code)
  145. account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  146. require.Equal(t, int64(2), account.Stats.Messages)
  147. require.Equal(t, int64(1002), account.Stats.MessagesRemaining)
  148. require.Equal(t, int64(1), account.Stats.Emails)
  149. require.Equal(t, int64(23), account.Stats.EmailsRemaining)
  150. }
  151. func TestAccount_ChangeSettings(t *testing.T) {
  152. s := newTestServer(t, newTestConfigWithAuthFile(t))
  153. defer s.closeDatabases()
  154. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  155. u, _ := s.userManager.User("phil")
  156. token, _ := s.userManager.CreateToken(u.ID, "", time.Unix(0, 0), netip.IPv4Unspecified())
  157. rr := request(t, s, "PATCH", "/v1/account/settings", `{"notification": {"sound": "juntos"},"ignored": true}`, map[string]string{
  158. "Authorization": util.BasicAuth("phil", "phil"),
  159. })
  160. require.Equal(t, 200, rr.Code)
  161. rr = request(t, s, "PATCH", "/v1/account/settings", `{"notification": {"delete_after": 86400}, "language": "de"}`, map[string]string{
  162. "Authorization": util.BearerAuth(token.Value),
  163. })
  164. require.Equal(t, 200, rr.Code)
  165. rr = request(t, s, "GET", "/v1/account", `{"username":"marian", "password":"marian"}`, map[string]string{
  166. "Authorization": util.BearerAuth(token.Value),
  167. })
  168. require.Equal(t, 200, rr.Code)
  169. account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  170. require.Equal(t, "de", account.Language)
  171. require.Equal(t, util.Int(86400), account.Notification.DeleteAfter)
  172. require.Equal(t, util.String("juntos"), account.Notification.Sound)
  173. require.Nil(t, account.Notification.MinPriority) // Not set
  174. }
  175. func TestAccount_Subscription_AddUpdateDelete(t *testing.T) {
  176. s := newTestServer(t, newTestConfigWithAuthFile(t))
  177. defer s.closeDatabases()
  178. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  179. rr := request(t, s, "POST", "/v1/account/subscription", `{"base_url": "http://abc.com", "topic": "def"}`, map[string]string{
  180. "Authorization": util.BasicAuth("phil", "phil"),
  181. })
  182. require.Equal(t, 200, rr.Code)
  183. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  184. "Authorization": util.BasicAuth("phil", "phil"),
  185. })
  186. require.Equal(t, 200, rr.Code)
  187. account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  188. require.Equal(t, 1, len(account.Subscriptions))
  189. require.Equal(t, "http://abc.com", account.Subscriptions[0].BaseURL)
  190. require.Equal(t, "def", account.Subscriptions[0].Topic)
  191. require.Nil(t, account.Subscriptions[0].DisplayName)
  192. rr = request(t, s, "PATCH", "/v1/account/subscription", `{"base_url": "http://abc.com", "topic": "def", "display_name": "ding dong"}`, map[string]string{
  193. "Authorization": util.BasicAuth("phil", "phil"),
  194. })
  195. require.Equal(t, 200, rr.Code)
  196. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  197. "Authorization": util.BasicAuth("phil", "phil"),
  198. })
  199. require.Equal(t, 200, rr.Code)
  200. account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  201. require.Equal(t, 1, len(account.Subscriptions))
  202. require.Equal(t, "http://abc.com", account.Subscriptions[0].BaseURL)
  203. require.Equal(t, "def", account.Subscriptions[0].Topic)
  204. require.Equal(t, util.String("ding dong"), account.Subscriptions[0].DisplayName)
  205. rr = request(t, s, "DELETE", "/v1/account/subscription", "", map[string]string{
  206. "Authorization": util.BasicAuth("phil", "phil"),
  207. "X-BaseURL": "http://abc.com",
  208. "X-Topic": "def",
  209. })
  210. require.Equal(t, 200, rr.Code)
  211. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  212. "Authorization": util.BasicAuth("phil", "phil"),
  213. })
  214. require.Equal(t, 200, rr.Code)
  215. account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  216. require.Equal(t, 0, len(account.Subscriptions))
  217. }
  218. func TestAccount_ChangePassword(t *testing.T) {
  219. s := newTestServer(t, newTestConfigWithAuthFile(t))
  220. defer s.closeDatabases()
  221. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  222. rr := request(t, s, "POST", "/v1/account/password", `{"password": "WRONG", "new_password": ""}`, map[string]string{
  223. "Authorization": util.BasicAuth("phil", "phil"),
  224. })
  225. require.Equal(t, 400, rr.Code)
  226. rr = request(t, s, "POST", "/v1/account/password", `{"password": "WRONG", "new_password": "new password"}`, map[string]string{
  227. "Authorization": util.BasicAuth("phil", "phil"),
  228. })
  229. require.Equal(t, 400, rr.Code)
  230. require.Equal(t, 40026, toHTTPError(t, rr.Body.String()).Code)
  231. rr = request(t, s, "POST", "/v1/account/password", `{"password": "phil", "new_password": "new password"}`, map[string]string{
  232. "Authorization": util.BasicAuth("phil", "phil"),
  233. })
  234. require.Equal(t, 200, rr.Code)
  235. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  236. "Authorization": util.BasicAuth("phil", "phil"),
  237. })
  238. require.Equal(t, 401, rr.Code)
  239. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  240. "Authorization": util.BasicAuth("phil", "new password"),
  241. })
  242. require.Equal(t, 200, rr.Code)
  243. }
  244. func TestAccount_ChangePassword_NoAccount(t *testing.T) {
  245. s := newTestServer(t, newTestConfigWithAuthFile(t))
  246. defer s.closeDatabases()
  247. rr := request(t, s, "POST", "/v1/account/password", `{"password": "new password"}`, nil)
  248. require.Equal(t, 401, rr.Code)
  249. }
  250. func TestAccount_ExtendToken(t *testing.T) {
  251. t.Parallel()
  252. s := newTestServer(t, newTestConfigWithAuthFile(t))
  253. defer s.closeDatabases()
  254. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  255. rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
  256. "Authorization": util.BasicAuth("phil", "phil"),
  257. })
  258. require.Equal(t, 200, rr.Code)
  259. token, err := util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
  260. require.Nil(t, err)
  261. time.Sleep(time.Second)
  262. rr = request(t, s, "PATCH", "/v1/account/token", "", map[string]string{
  263. "Authorization": util.BearerAuth(token.Token),
  264. })
  265. require.Equal(t, 200, rr.Code)
  266. extendedToken, err := util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
  267. require.Nil(t, err)
  268. require.Equal(t, token.Token, extendedToken.Token)
  269. require.True(t, token.Expires < extendedToken.Expires)
  270. expires := time.Now().Add(999 * time.Hour)
  271. body := fmt.Sprintf(`{"token":"%s", "label":"some label", "expires": %d}`, token.Token, expires.Unix())
  272. rr = request(t, s, "PATCH", "/v1/account/token", body, map[string]string{
  273. "Authorization": util.BearerAuth(token.Token),
  274. })
  275. require.Equal(t, 200, rr.Code)
  276. token, err = util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
  277. require.Nil(t, err)
  278. require.Equal(t, "some label", token.Label)
  279. require.Equal(t, expires.Unix(), token.Expires)
  280. }
  281. func TestAccount_ExtendToken_NoTokenProvided(t *testing.T) {
  282. s := newTestServer(t, newTestConfigWithAuthFile(t))
  283. defer s.closeDatabases()
  284. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  285. rr := request(t, s, "PATCH", "/v1/account/token", "", map[string]string{
  286. "Authorization": util.BasicAuth("phil", "phil"), // Not Bearer!
  287. })
  288. require.Equal(t, 400, rr.Code)
  289. require.Equal(t, 40023, toHTTPError(t, rr.Body.String()).Code)
  290. }
  291. func TestAccount_DeleteToken(t *testing.T) {
  292. s := newTestServer(t, newTestConfigWithAuthFile(t))
  293. defer s.closeDatabases()
  294. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  295. rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
  296. "Authorization": util.BasicAuth("phil", "phil"),
  297. })
  298. require.Equal(t, 200, rr.Code)
  299. token, err := util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
  300. require.Nil(t, err)
  301. require.True(t, token.Expires > time.Now().Add(71*time.Hour).Unix())
  302. // Delete token failure (using basic auth)
  303. rr = request(t, s, "DELETE", "/v1/account/token", "", map[string]string{
  304. "Authorization": util.BasicAuth("phil", "phil"), // Not Bearer!
  305. })
  306. require.Equal(t, 400, rr.Code)
  307. require.Equal(t, 40023, toHTTPError(t, rr.Body.String()).Code)
  308. // Delete token with wrong token
  309. rr = request(t, s, "DELETE", "/v1/account/token", "", map[string]string{
  310. "Authorization": util.BearerAuth("invalidtoken"),
  311. })
  312. require.Equal(t, 401, rr.Code)
  313. // Delete token with correct token
  314. rr = request(t, s, "DELETE", "/v1/account/token", "", map[string]string{
  315. "Authorization": util.BearerAuth(token.Token),
  316. })
  317. require.Equal(t, 200, rr.Code)
  318. // Cannot get account anymore
  319. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  320. "Authorization": util.BearerAuth(token.Token),
  321. })
  322. require.Equal(t, 401, rr.Code)
  323. }
  324. func TestAccount_Delete_Success(t *testing.T) {
  325. conf := newTestConfigWithAuthFile(t)
  326. conf.EnableSignup = true
  327. s := newTestServer(t, conf)
  328. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  329. require.Equal(t, 200, rr.Code)
  330. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  331. "Authorization": util.BasicAuth("phil", "mypass"),
  332. })
  333. require.Equal(t, 200, rr.Code)
  334. rr = request(t, s, "DELETE", "/v1/account", `{"password":"mypass"}`, map[string]string{
  335. "Authorization": util.BasicAuth("phil", "mypass"),
  336. })
  337. require.Equal(t, 200, rr.Code)
  338. // Account was marked deleted
  339. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  340. "Authorization": util.BasicAuth("phil", "mypass"),
  341. })
  342. require.Equal(t, 401, rr.Code)
  343. // Cannot re-create account, since still exists
  344. rr = request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  345. require.Equal(t, 409, rr.Code)
  346. }
  347. func TestAccount_Delete_Not_Allowed(t *testing.T) {
  348. conf := newTestConfigWithAuthFile(t)
  349. conf.EnableSignup = true
  350. s := newTestServer(t, conf)
  351. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  352. require.Equal(t, 200, rr.Code)
  353. rr = request(t, s, "DELETE", "/v1/account", "", nil)
  354. require.Equal(t, 401, rr.Code)
  355. rr = request(t, s, "DELETE", "/v1/account", `{"password":"mypass"}`, nil)
  356. require.Equal(t, 401, rr.Code)
  357. rr = request(t, s, "DELETE", "/v1/account", `{"password":"INCORRECT"}`, map[string]string{
  358. "Authorization": util.BasicAuth("phil", "mypass"),
  359. })
  360. require.Equal(t, 400, rr.Code)
  361. require.Equal(t, 40026, toHTTPError(t, rr.Body.String()).Code)
  362. }
  363. func TestAccount_Delete_Success_WithWebPush(t *testing.T) {
  364. conf := configureAuth(t, newTestConfigWithWebPush(t))
  365. conf.EnableSignup = true
  366. s := newTestServer(t, conf)
  367. // Add account
  368. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  369. require.Equal(t, 200, rr.Code)
  370. // Add web push subscription
  371. rr = request(t, s, "POST", "/v1/webpush", payloadForTopics(t, []string{"mytopic"}, testWebPushEndpoint), map[string]string{
  372. "Authorization": util.BasicAuth("phil", "mypass"),
  373. })
  374. require.Equal(t, 200, rr.Code)
  375. u, err := s.userManager.User("phil")
  376. require.Nil(t, err)
  377. subs, err := s.webPush.SubscriptionsForTopic("mytopic")
  378. require.Nil(t, err)
  379. require.Len(t, subs, 1)
  380. require.Equal(t, u.ID, subs[0].UserID)
  381. // Delete account
  382. rr = request(t, s, "DELETE", "/v1/account", `{"password":"mypass"}`, map[string]string{
  383. "Authorization": util.BasicAuth("phil", "mypass"),
  384. })
  385. require.Equal(t, 200, rr.Code)
  386. // Make sure web push subscription was deleted
  387. subs, err = s.webPush.SubscriptionsForTopic("mytopic")
  388. require.Nil(t, err)
  389. require.Len(t, subs, 0)
  390. }
  391. func TestAccount_Reservation_AddWithoutTierFails(t *testing.T) {
  392. conf := newTestConfigWithAuthFile(t)
  393. conf.EnableSignup = true
  394. s := newTestServer(t, conf)
  395. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  396. require.Equal(t, 200, rr.Code)
  397. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic":"mytopic", "everyone":"deny-all"}`, map[string]string{
  398. "Authorization": util.BasicAuth("phil", "mypass"),
  399. })
  400. require.Equal(t, 401, rr.Code)
  401. }
  402. func TestAccount_Reservation_AddAdminSuccess(t *testing.T) {
  403. conf := newTestConfigWithAuthFile(t)
  404. conf.EnableSignup = true
  405. s := newTestServer(t, conf)
  406. // A user, an admin, and a reservation walk into a bar
  407. require.Nil(t, s.userManager.AddTier(&user.Tier{
  408. Code: "pro",
  409. ReservationLimit: 2,
  410. }))
  411. require.Nil(t, s.userManager.AddUser("noadmin1", "pass", user.RoleUser))
  412. require.Nil(t, s.userManager.ChangeTier("noadmin1", "pro"))
  413. require.Nil(t, s.userManager.AddReservation("noadmin1", "mytopic", user.PermissionDenyAll))
  414. require.Nil(t, s.userManager.AddUser("noadmin2", "pass", user.RoleUser))
  415. require.Nil(t, s.userManager.ChangeTier("noadmin2", "pro"))
  416. require.Nil(t, s.userManager.AddUser("phil", "adminpass", user.RoleAdmin))
  417. // Admin can reserve topic
  418. rr := request(t, s, "POST", "/v1/account/reservation", `{"topic":"sometopic","everyone":"deny-all"}`, map[string]string{
  419. "Authorization": util.BasicAuth("phil", "adminpass"),
  420. })
  421. require.Equal(t, 200, rr.Code)
  422. // User cannot reserve already reserved topic
  423. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic":"mytopic","everyone":"deny-all"}`, map[string]string{
  424. "Authorization": util.BasicAuth("noadmin2", "pass"),
  425. })
  426. require.Equal(t, 409, rr.Code)
  427. // Admin cannot reserve already reserved topic
  428. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic":"mytopic","everyone":"deny-all"}`, map[string]string{
  429. "Authorization": util.BasicAuth("phil", "adminpass"),
  430. })
  431. require.Equal(t, 409, rr.Code)
  432. reservations, err := s.userManager.Reservations("phil")
  433. require.Nil(t, err)
  434. require.Equal(t, 1, len(reservations))
  435. require.Equal(t, "sometopic", reservations[0].Topic)
  436. reservations, err = s.userManager.Reservations("noadmin1")
  437. require.Nil(t, err)
  438. require.Equal(t, 1, len(reservations))
  439. require.Equal(t, "mytopic", reservations[0].Topic)
  440. reservations, err = s.userManager.Reservations("noadmin2")
  441. require.Nil(t, err)
  442. require.Equal(t, 0, len(reservations))
  443. }
  444. func TestAccount_Reservation_AddRemoveUserWithTierSuccess(t *testing.T) {
  445. conf := newTestConfigWithAuthFile(t)
  446. conf.EnableSignup = true
  447. conf.EnableReservations = true
  448. conf.TwilioAccount = "dummy"
  449. s := newTestServer(t, conf)
  450. // Create user
  451. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  452. require.Equal(t, 200, rr.Code)
  453. // Create a tier
  454. require.Nil(t, s.userManager.AddTier(&user.Tier{
  455. Code: "pro",
  456. MessageLimit: 123,
  457. MessageExpiryDuration: 86400 * time.Second,
  458. EmailLimit: 32,
  459. CallLimit: 10,
  460. ReservationLimit: 2,
  461. AttachmentFileSizeLimit: 1231231,
  462. AttachmentTotalSizeLimit: 123123,
  463. AttachmentExpiryDuration: 10800 * time.Second,
  464. AttachmentBandwidthLimit: 21474836480,
  465. }))
  466. require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
  467. // Reserve two topics
  468. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "mytopic", "everyone":"deny-all"}`, map[string]string{
  469. "Authorization": util.BasicAuth("phil", "mypass"),
  470. })
  471. require.Equal(t, 200, rr.Code)
  472. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "another", "everyone":"read-only"}`, map[string]string{
  473. "Authorization": util.BasicAuth("phil", "mypass"),
  474. })
  475. require.Equal(t, 200, rr.Code)
  476. // Trying to reserve a third should fail
  477. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "yet-another", "everyone":"deny-all"}`, map[string]string{
  478. "Authorization": util.BasicAuth("phil", "mypass"),
  479. })
  480. require.Equal(t, 429, rr.Code)
  481. // Modify existing should still work
  482. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "another", "everyone":"write-only"}`, map[string]string{
  483. "Authorization": util.BasicAuth("phil", "mypass"),
  484. })
  485. require.Equal(t, 200, rr.Code)
  486. // Check account result
  487. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  488. "Authorization": util.BasicAuth("phil", "mypass"),
  489. })
  490. require.Equal(t, 200, rr.Code)
  491. account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  492. require.Equal(t, "pro", account.Tier.Code)
  493. require.Equal(t, int64(123), account.Limits.Messages)
  494. require.Equal(t, int64(86400), account.Limits.MessagesExpiryDuration)
  495. require.Equal(t, int64(32), account.Limits.Emails)
  496. require.Equal(t, int64(10), account.Limits.Calls)
  497. require.Equal(t, int64(2), account.Limits.Reservations)
  498. require.Equal(t, int64(1231231), account.Limits.AttachmentFileSize)
  499. require.Equal(t, int64(123123), account.Limits.AttachmentTotalSize)
  500. require.Equal(t, int64(10800), account.Limits.AttachmentExpiryDuration)
  501. require.Equal(t, int64(21474836480), account.Limits.AttachmentBandwidth)
  502. require.Equal(t, 2, len(account.Reservations))
  503. require.Equal(t, "another", account.Reservations[0].Topic)
  504. require.Equal(t, "write-only", account.Reservations[0].Everyone)
  505. require.Equal(t, "mytopic", account.Reservations[1].Topic)
  506. require.Equal(t, "deny-all", account.Reservations[1].Everyone)
  507. // Delete and re-check
  508. rr = request(t, s, "DELETE", "/v1/account/reservation/another", "", map[string]string{
  509. "Authorization": util.BasicAuth("phil", "mypass"),
  510. })
  511. require.Equal(t, 200, rr.Code)
  512. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  513. "Authorization": util.BasicAuth("phil", "mypass"),
  514. })
  515. require.Equal(t, 200, rr.Code)
  516. account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  517. require.Equal(t, 1, len(account.Reservations))
  518. require.Equal(t, "mytopic", account.Reservations[0].Topic)
  519. }
  520. func TestAccount_Reservation_PublishByAnonymousFails(t *testing.T) {
  521. conf := newTestConfigWithAuthFile(t)
  522. conf.AuthDefault = user.PermissionReadWrite
  523. conf.EnableSignup = true
  524. s := newTestServer(t, conf)
  525. // Create user with tier
  526. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  527. require.Equal(t, 200, rr.Code)
  528. require.Nil(t, s.userManager.AddTier(&user.Tier{
  529. Code: "pro",
  530. MessageLimit: 20,
  531. ReservationLimit: 2,
  532. }))
  533. require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
  534. // Reserve a topic
  535. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "mytopic", "everyone":"deny-all"}`, map[string]string{
  536. "Authorization": util.BasicAuth("phil", "mypass"),
  537. })
  538. require.Equal(t, 200, rr.Code)
  539. // Publish a message
  540. rr = request(t, s, "POST", "/mytopic", `Howdy`, map[string]string{
  541. "Authorization": util.BasicAuth("phil", "mypass"),
  542. })
  543. require.Equal(t, 200, rr.Code)
  544. // Publish a message (as anonymous)
  545. rr = request(t, s, "POST", "/mytopic", `Howdy`, nil)
  546. require.Equal(t, 403, rr.Code)
  547. }
  548. func TestAccount_Reservation_Delete_Messages_And_Attachments(t *testing.T) {
  549. t.Parallel()
  550. conf := newTestConfigWithAuthFile(t)
  551. conf.AuthDefault = user.PermissionReadWrite
  552. s := newTestServer(t, conf)
  553. // Create user with tier
  554. require.Nil(t, s.userManager.AddUser("phil", "mypass", user.RoleUser))
  555. require.Nil(t, s.userManager.AddTier(&user.Tier{
  556. Code: "pro",
  557. MessageLimit: 20,
  558. MessageExpiryDuration: time.Hour,
  559. ReservationLimit: 2,
  560. AttachmentTotalSizeLimit: 10000,
  561. AttachmentFileSizeLimit: 10000,
  562. AttachmentExpiryDuration: time.Hour,
  563. AttachmentBandwidthLimit: 10000,
  564. }))
  565. require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
  566. // Reserve two topics "mytopic1" and "mytopic2"
  567. rr := request(t, s, "POST", "/v1/account/reservation", `{"topic": "mytopic1", "everyone":"deny-all"}`, map[string]string{
  568. "Authorization": util.BasicAuth("phil", "mypass"),
  569. })
  570. require.Equal(t, 200, rr.Code)
  571. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "mytopic2", "everyone":"deny-all"}`, map[string]string{
  572. "Authorization": util.BasicAuth("phil", "mypass"),
  573. })
  574. require.Equal(t, 200, rr.Code)
  575. // Publish a message with attachment to each topic
  576. rr = request(t, s, "POST", "/mytopic1?f=attach.txt", `Howdy`, map[string]string{
  577. "Authorization": util.BasicAuth("phil", "mypass"),
  578. })
  579. require.Equal(t, 200, rr.Code)
  580. m1 := toMessage(t, rr.Body.String())
  581. require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m1.ID))
  582. rr = request(t, s, "POST", "/mytopic2?f=attach.txt", `Howdy`, map[string]string{
  583. "Authorization": util.BasicAuth("phil", "mypass"),
  584. })
  585. require.Equal(t, 200, rr.Code)
  586. m2 := toMessage(t, rr.Body.String())
  587. require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m2.ID))
  588. // Pre-verify message count and file
  589. ms, err := s.messageCache.Messages("mytopic1", sinceAllMessages, false)
  590. require.Nil(t, err)
  591. require.Equal(t, 1, len(ms))
  592. require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m1.ID))
  593. ms, err = s.messageCache.Messages("mytopic2", sinceAllMessages, false)
  594. require.Nil(t, err)
  595. require.Equal(t, 1, len(ms))
  596. require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m2.ID))
  597. // Delete reservation
  598. rr = request(t, s, "DELETE", "/v1/account/reservation/mytopic1", ``, map[string]string{
  599. "X-Delete-Messages": "true",
  600. "Authorization": util.BasicAuth("phil", "mypass"),
  601. })
  602. require.Equal(t, 200, rr.Code)
  603. rr = request(t, s, "DELETE", "/v1/account/reservation/mytopic2", ``, map[string]string{
  604. "X-Delete-Messages": "false",
  605. "Authorization": util.BasicAuth("phil", "mypass"),
  606. })
  607. require.Equal(t, 200, rr.Code)
  608. // Verify that messages and attachments were deleted
  609. // This does not explicitly call the manager!
  610. waitFor(t, func() bool {
  611. ms, err := s.messageCache.Messages("mytopic1", sinceAllMessages, false)
  612. require.Nil(t, err)
  613. return len(ms) == 0 && !util.FileExists(filepath.Join(s.config.AttachmentCacheDir, m1.ID))
  614. })
  615. ms, err = s.messageCache.Messages("mytopic1", sinceAllMessages, false)
  616. require.Nil(t, err)
  617. require.Equal(t, 0, len(ms))
  618. require.NoFileExists(t, filepath.Join(s.config.AttachmentCacheDir, m1.ID))
  619. ms, err = s.messageCache.Messages("mytopic2", sinceAllMessages, false)
  620. require.Nil(t, err)
  621. require.Equal(t, 1, len(ms))
  622. require.Equal(t, m2.ID, ms[0].ID)
  623. require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m2.ID))
  624. }
  625. /*func TestAccount_Persist_UserStats_After_Tier_Change(t *testing.T) {
  626. conf := newTestConfigWithAuthFile(t)
  627. conf.AuthDefault = user.PermissionReadWrite
  628. conf.AuthStatsQueueWriterInterval = 300 * time.Millisecond
  629. s := newTestServer(t, conf)
  630. defer s.closeDatabases()
  631. // Create user with tier
  632. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  633. require.Nil(t, s.userManager.AddTier(&user.Tier{
  634. Code: "starter",
  635. MessageLimit: 10,
  636. }))
  637. require.Nil(t, s.userManager.AddTier(&user.Tier{
  638. Code: "pro",
  639. MessageLimit: 20,
  640. }))
  641. require.Nil(t, s.userManager.ChangeTier("phil", "starter"))
  642. // Publish a message
  643. rr := request(t, s, "POST", "/mytopic", "hi", map[string]string{
  644. "Authorization": util.BasicAuth("phil", "phil"),
  645. })
  646. require.Equal(t, 200, rr.Code)
  647. // Wait for stats queue writer, verify that message stats were persisted
  648. waitFor(t, func() bool {
  649. u, err := s.userManager.User("phil")
  650. require.Nil(t, err)
  651. return int64(1) == u.Stats.Messages
  652. })
  653. // Change tier, make a request (to reset limiters)
  654. require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
  655. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  656. "Authorization": util.BasicAuth("phil", "phil"),
  657. })
  658. require.Equal(t, 200, rr.Code)
  659. account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  660. require.Equal(t, int64(1), account.Stats.Messages) // Is not reset!
  661. // Publish another message
  662. rr = request(t, s, "POST", "/mytopic", "hi", map[string]string{
  663. "Authorization": util.BasicAuth("phil", "phil"),
  664. })
  665. require.Equal(t, 200, rr.Code)
  666. // Verify that message stats were persisted
  667. waitFor(t, func() bool {
  668. u, err := s.userManager.User("phil")
  669. require.Nil(t, err)
  670. return int64(2) == u.Stats.Messages // v.EnqueueUserStats had run!
  671. })
  672. // Stats keep counting
  673. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  674. "Authorization": util.BasicAuth("phil", "phil"),
  675. })
  676. require.Equal(t, 200, rr.Code)
  677. account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  678. require.Equal(t, int64(2), account.Stats.Messages) // Is not reset!
  679. }*/