server_account_test.go 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830
  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. rr = request(t, s, "POST", "/mytopic", "", nil)
  136. require.Equal(t, 200, rr.Code)
  137. rr = request(t, s, "POST", "/mytopic", "", map[string]string{
  138. "Email": "phil@ntfy.sh",
  139. })
  140. require.Equal(t, 200, rr.Code)
  141. rr = request(t, s, "GET", "/v1/account", "", nil)
  142. require.Equal(t, 200, rr.Code)
  143. account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  144. require.Equal(t, int64(2), account.Stats.Messages)
  145. require.Equal(t, int64(1002), account.Stats.MessagesRemaining)
  146. require.Equal(t, int64(1), account.Stats.Emails)
  147. require.Equal(t, int64(23), account.Stats.EmailsRemaining)
  148. }
  149. func TestAccount_ChangeSettings(t *testing.T) {
  150. s := newTestServer(t, newTestConfigWithAuthFile(t))
  151. defer s.closeDatabases()
  152. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  153. u, _ := s.userManager.User("phil")
  154. token, _ := s.userManager.CreateToken(u.ID, "", time.Unix(0, 0), netip.IPv4Unspecified())
  155. rr := request(t, s, "PATCH", "/v1/account/settings", `{"notification": {"sound": "juntos"},"ignored": true}`, map[string]string{
  156. "Authorization": util.BasicAuth("phil", "phil"),
  157. })
  158. require.Equal(t, 200, rr.Code)
  159. rr = request(t, s, "PATCH", "/v1/account/settings", `{"notification": {"delete_after": 86400}, "language": "de"}`, map[string]string{
  160. "Authorization": util.BearerAuth(token.Value),
  161. })
  162. require.Equal(t, 200, rr.Code)
  163. rr = request(t, s, "GET", "/v1/account", `{"username":"marian", "password":"marian"}`, map[string]string{
  164. "Authorization": util.BearerAuth(token.Value),
  165. })
  166. require.Equal(t, 200, rr.Code)
  167. account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  168. require.Equal(t, "de", account.Language)
  169. require.Equal(t, util.Int(86400), account.Notification.DeleteAfter)
  170. require.Equal(t, util.String("juntos"), account.Notification.Sound)
  171. require.Nil(t, account.Notification.MinPriority) // Not set
  172. }
  173. func TestAccount_Subscription_AddUpdateDelete(t *testing.T) {
  174. s := newTestServer(t, newTestConfigWithAuthFile(t))
  175. defer s.closeDatabases()
  176. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  177. rr := request(t, s, "POST", "/v1/account/subscription", `{"base_url": "http://abc.com", "topic": "def"}`, map[string]string{
  178. "Authorization": util.BasicAuth("phil", "phil"),
  179. })
  180. require.Equal(t, 200, rr.Code)
  181. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  182. "Authorization": util.BasicAuth("phil", "phil"),
  183. })
  184. require.Equal(t, 200, rr.Code)
  185. account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  186. require.Equal(t, 1, len(account.Subscriptions))
  187. require.Equal(t, "http://abc.com", account.Subscriptions[0].BaseURL)
  188. require.Equal(t, "def", account.Subscriptions[0].Topic)
  189. require.Nil(t, account.Subscriptions[0].DisplayName)
  190. rr = request(t, s, "PATCH", "/v1/account/subscription", `{"base_url": "http://abc.com", "topic": "def", "display_name": "ding dong"}`, map[string]string{
  191. "Authorization": util.BasicAuth("phil", "phil"),
  192. })
  193. require.Equal(t, 200, rr.Code)
  194. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  195. "Authorization": util.BasicAuth("phil", "phil"),
  196. })
  197. require.Equal(t, 200, rr.Code)
  198. account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  199. require.Equal(t, 1, len(account.Subscriptions))
  200. require.Equal(t, "http://abc.com", account.Subscriptions[0].BaseURL)
  201. require.Equal(t, "def", account.Subscriptions[0].Topic)
  202. require.Equal(t, util.String("ding dong"), account.Subscriptions[0].DisplayName)
  203. rr = request(t, s, "DELETE", "/v1/account/subscription", "", map[string]string{
  204. "Authorization": util.BasicAuth("phil", "phil"),
  205. "X-BaseURL": "http://abc.com",
  206. "X-Topic": "def",
  207. })
  208. require.Equal(t, 200, rr.Code)
  209. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  210. "Authorization": util.BasicAuth("phil", "phil"),
  211. })
  212. require.Equal(t, 200, rr.Code)
  213. account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  214. require.Equal(t, 0, len(account.Subscriptions))
  215. }
  216. func TestAccount_ChangePassword(t *testing.T) {
  217. s := newTestServer(t, newTestConfigWithAuthFile(t))
  218. defer s.closeDatabases()
  219. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  220. rr := request(t, s, "POST", "/v1/account/password", `{"password": "WRONG", "new_password": ""}`, map[string]string{
  221. "Authorization": util.BasicAuth("phil", "phil"),
  222. })
  223. require.Equal(t, 400, rr.Code)
  224. rr = request(t, s, "POST", "/v1/account/password", `{"password": "WRONG", "new_password": "new password"}`, map[string]string{
  225. "Authorization": util.BasicAuth("phil", "phil"),
  226. })
  227. require.Equal(t, 400, rr.Code)
  228. require.Equal(t, 40026, toHTTPError(t, rr.Body.String()).Code)
  229. rr = request(t, s, "POST", "/v1/account/password", `{"password": "phil", "new_password": "new password"}`, map[string]string{
  230. "Authorization": util.BasicAuth("phil", "phil"),
  231. })
  232. require.Equal(t, 200, rr.Code)
  233. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  234. "Authorization": util.BasicAuth("phil", "phil"),
  235. })
  236. require.Equal(t, 401, rr.Code)
  237. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  238. "Authorization": util.BasicAuth("phil", "new password"),
  239. })
  240. require.Equal(t, 200, rr.Code)
  241. }
  242. func TestAccount_ChangePassword_NoAccount(t *testing.T) {
  243. s := newTestServer(t, newTestConfigWithAuthFile(t))
  244. defer s.closeDatabases()
  245. rr := request(t, s, "POST", "/v1/account/password", `{"password": "new password"}`, nil)
  246. require.Equal(t, 401, rr.Code)
  247. }
  248. func TestAccount_ExtendToken(t *testing.T) {
  249. s := newTestServer(t, newTestConfigWithAuthFile(t))
  250. defer s.closeDatabases()
  251. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  252. rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
  253. "Authorization": util.BasicAuth("phil", "phil"),
  254. })
  255. require.Equal(t, 200, rr.Code)
  256. token, err := util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
  257. require.Nil(t, err)
  258. time.Sleep(time.Second)
  259. rr = request(t, s, "PATCH", "/v1/account/token", "", map[string]string{
  260. "Authorization": util.BearerAuth(token.Token),
  261. })
  262. require.Equal(t, 200, rr.Code)
  263. extendedToken, err := util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
  264. require.Nil(t, err)
  265. require.Equal(t, token.Token, extendedToken.Token)
  266. require.True(t, token.Expires < extendedToken.Expires)
  267. expires := time.Now().Add(999 * time.Hour)
  268. body := fmt.Sprintf(`{"token":"%s", "label":"some label", "expires": %d}`, token.Token, expires.Unix())
  269. rr = request(t, s, "PATCH", "/v1/account/token", body, map[string]string{
  270. "Authorization": util.BearerAuth(token.Token),
  271. })
  272. require.Equal(t, 200, rr.Code)
  273. token, err = util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
  274. require.Nil(t, err)
  275. require.Equal(t, "some label", token.Label)
  276. require.Equal(t, expires.Unix(), token.Expires)
  277. }
  278. func TestAccount_ExtendToken_NoTokenProvided(t *testing.T) {
  279. s := newTestServer(t, newTestConfigWithAuthFile(t))
  280. defer s.closeDatabases()
  281. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  282. rr := request(t, s, "PATCH", "/v1/account/token", "", map[string]string{
  283. "Authorization": util.BasicAuth("phil", "phil"), // Not Bearer!
  284. })
  285. require.Equal(t, 400, rr.Code)
  286. require.Equal(t, 40023, toHTTPError(t, rr.Body.String()).Code)
  287. }
  288. func TestAccount_DeleteToken(t *testing.T) {
  289. s := newTestServer(t, newTestConfigWithAuthFile(t))
  290. defer s.closeDatabases()
  291. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  292. rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
  293. "Authorization": util.BasicAuth("phil", "phil"),
  294. })
  295. require.Equal(t, 200, rr.Code)
  296. token, err := util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
  297. require.Nil(t, err)
  298. require.True(t, token.Expires > time.Now().Add(71*time.Hour).Unix())
  299. // Delete token failure (using basic auth)
  300. rr = request(t, s, "DELETE", "/v1/account/token", "", map[string]string{
  301. "Authorization": util.BasicAuth("phil", "phil"), // Not Bearer!
  302. })
  303. require.Equal(t, 400, rr.Code)
  304. require.Equal(t, 40023, toHTTPError(t, rr.Body.String()).Code)
  305. // Delete token with wrong token
  306. rr = request(t, s, "DELETE", "/v1/account/token", "", map[string]string{
  307. "Authorization": util.BearerAuth("invalidtoken"),
  308. })
  309. require.Equal(t, 401, rr.Code)
  310. // Delete token with correct token
  311. rr = request(t, s, "DELETE", "/v1/account/token", "", map[string]string{
  312. "Authorization": util.BearerAuth(token.Token),
  313. })
  314. require.Equal(t, 200, rr.Code)
  315. // Cannot get account anymore
  316. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  317. "Authorization": util.BearerAuth(token.Token),
  318. })
  319. require.Equal(t, 401, rr.Code)
  320. }
  321. func TestAccount_Delete_Success(t *testing.T) {
  322. conf := newTestConfigWithAuthFile(t)
  323. conf.EnableSignup = true
  324. s := newTestServer(t, conf)
  325. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  326. require.Equal(t, 200, rr.Code)
  327. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  328. "Authorization": util.BasicAuth("phil", "mypass"),
  329. })
  330. require.Equal(t, 200, rr.Code)
  331. rr = request(t, s, "DELETE", "/v1/account", `{"password":"mypass"}`, map[string]string{
  332. "Authorization": util.BasicAuth("phil", "mypass"),
  333. })
  334. require.Equal(t, 200, rr.Code)
  335. // Account was marked deleted
  336. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  337. "Authorization": util.BasicAuth("phil", "mypass"),
  338. })
  339. require.Equal(t, 401, rr.Code)
  340. // Cannot re-create account, since still exists
  341. rr = request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  342. require.Equal(t, 409, rr.Code)
  343. }
  344. func TestAccount_Delete_Not_Allowed(t *testing.T) {
  345. conf := newTestConfigWithAuthFile(t)
  346. conf.EnableSignup = true
  347. s := newTestServer(t, conf)
  348. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  349. require.Equal(t, 200, rr.Code)
  350. rr = request(t, s, "DELETE", "/v1/account", "", nil)
  351. require.Equal(t, 401, rr.Code)
  352. rr = request(t, s, "DELETE", "/v1/account", `{"password":"mypass"}`, nil)
  353. require.Equal(t, 401, rr.Code)
  354. rr = request(t, s, "DELETE", "/v1/account", `{"password":"INCORRECT"}`, map[string]string{
  355. "Authorization": util.BasicAuth("phil", "mypass"),
  356. })
  357. require.Equal(t, 400, rr.Code)
  358. require.Equal(t, 40026, toHTTPError(t, rr.Body.String()).Code)
  359. }
  360. func TestAccount_Reservation_AddWithoutTierFails(t *testing.T) {
  361. conf := newTestConfigWithAuthFile(t)
  362. conf.EnableSignup = true
  363. s := newTestServer(t, conf)
  364. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  365. require.Equal(t, 200, rr.Code)
  366. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic":"mytopic", "everyone":"deny-all"}`, map[string]string{
  367. "Authorization": util.BasicAuth("phil", "mypass"),
  368. })
  369. require.Equal(t, 401, rr.Code)
  370. }
  371. func TestAccount_Reservation_AddAdminSuccess(t *testing.T) {
  372. conf := newTestConfigWithAuthFile(t)
  373. conf.EnableSignup = true
  374. s := newTestServer(t, conf)
  375. // A user, an admin, and a reservation walk into a bar
  376. require.Nil(t, s.userManager.AddTier(&user.Tier{
  377. Code: "pro",
  378. ReservationLimit: 2,
  379. }))
  380. require.Nil(t, s.userManager.AddUser("noadmin1", "pass", user.RoleUser))
  381. require.Nil(t, s.userManager.ChangeTier("noadmin1", "pro"))
  382. require.Nil(t, s.userManager.AddReservation("noadmin1", "mytopic", user.PermissionDenyAll))
  383. require.Nil(t, s.userManager.AddUser("noadmin2", "pass", user.RoleUser))
  384. require.Nil(t, s.userManager.ChangeTier("noadmin2", "pro"))
  385. require.Nil(t, s.userManager.AddUser("phil", "adminpass", user.RoleAdmin))
  386. // Admin can reserve topic
  387. rr := request(t, s, "POST", "/v1/account/reservation", `{"topic":"sometopic","everyone":"deny-all"}`, map[string]string{
  388. "Authorization": util.BasicAuth("phil", "adminpass"),
  389. })
  390. require.Equal(t, 200, rr.Code)
  391. // User cannot reserve already reserved topic
  392. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic":"mytopic","everyone":"deny-all"}`, map[string]string{
  393. "Authorization": util.BasicAuth("noadmin2", "pass"),
  394. })
  395. require.Equal(t, 409, rr.Code)
  396. // Admin cannot reserve already reserved topic
  397. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic":"mytopic","everyone":"deny-all"}`, map[string]string{
  398. "Authorization": util.BasicAuth("phil", "adminpass"),
  399. })
  400. require.Equal(t, 409, rr.Code)
  401. reservations, err := s.userManager.Reservations("phil")
  402. require.Nil(t, err)
  403. require.Equal(t, 1, len(reservations))
  404. require.Equal(t, "sometopic", reservations[0].Topic)
  405. reservations, err = s.userManager.Reservations("noadmin1")
  406. require.Nil(t, err)
  407. require.Equal(t, 1, len(reservations))
  408. require.Equal(t, "mytopic", reservations[0].Topic)
  409. reservations, err = s.userManager.Reservations("noadmin2")
  410. require.Nil(t, err)
  411. require.Equal(t, 0, len(reservations))
  412. }
  413. func TestAccount_Reservation_AddRemoveUserWithTierSuccess(t *testing.T) {
  414. conf := newTestConfigWithAuthFile(t)
  415. conf.EnableSignup = true
  416. s := newTestServer(t, conf)
  417. // Create user
  418. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  419. require.Equal(t, 200, rr.Code)
  420. // Create a tier
  421. require.Nil(t, s.userManager.AddTier(&user.Tier{
  422. Code: "pro",
  423. MessageLimit: 123,
  424. MessageExpiryDuration: 86400 * time.Second,
  425. EmailLimit: 32,
  426. ReservationLimit: 2,
  427. AttachmentFileSizeLimit: 1231231,
  428. AttachmentTotalSizeLimit: 123123,
  429. AttachmentExpiryDuration: 10800 * time.Second,
  430. AttachmentBandwidthLimit: 21474836480,
  431. }))
  432. require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
  433. // Reserve two topics
  434. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "mytopic", "everyone":"deny-all"}`, map[string]string{
  435. "Authorization": util.BasicAuth("phil", "mypass"),
  436. })
  437. require.Equal(t, 200, rr.Code)
  438. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "another", "everyone":"read-only"}`, map[string]string{
  439. "Authorization": util.BasicAuth("phil", "mypass"),
  440. })
  441. require.Equal(t, 200, rr.Code)
  442. // Trying to reserve a third should fail
  443. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "yet-another", "everyone":"deny-all"}`, map[string]string{
  444. "Authorization": util.BasicAuth("phil", "mypass"),
  445. })
  446. require.Equal(t, 429, rr.Code)
  447. // Modify existing should still work
  448. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "another", "everyone":"write-only"}`, map[string]string{
  449. "Authorization": util.BasicAuth("phil", "mypass"),
  450. })
  451. require.Equal(t, 200, rr.Code)
  452. // Check account result
  453. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  454. "Authorization": util.BasicAuth("phil", "mypass"),
  455. })
  456. require.Equal(t, 200, rr.Code)
  457. account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  458. require.Equal(t, "pro", account.Tier.Code)
  459. require.Equal(t, int64(123), account.Limits.Messages)
  460. require.Equal(t, int64(86400), account.Limits.MessagesExpiryDuration)
  461. require.Equal(t, int64(32), account.Limits.Emails)
  462. require.Equal(t, int64(2), account.Limits.Reservations)
  463. require.Equal(t, int64(1231231), account.Limits.AttachmentFileSize)
  464. require.Equal(t, int64(123123), account.Limits.AttachmentTotalSize)
  465. require.Equal(t, int64(10800), account.Limits.AttachmentExpiryDuration)
  466. require.Equal(t, int64(21474836480), account.Limits.AttachmentBandwidth)
  467. require.Equal(t, 2, len(account.Reservations))
  468. require.Equal(t, "another", account.Reservations[0].Topic)
  469. require.Equal(t, "write-only", account.Reservations[0].Everyone)
  470. require.Equal(t, "mytopic", account.Reservations[1].Topic)
  471. require.Equal(t, "deny-all", account.Reservations[1].Everyone)
  472. // Delete and re-check
  473. rr = request(t, s, "DELETE", "/v1/account/reservation/another", "", map[string]string{
  474. "Authorization": util.BasicAuth("phil", "mypass"),
  475. })
  476. require.Equal(t, 200, rr.Code)
  477. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  478. "Authorization": util.BasicAuth("phil", "mypass"),
  479. })
  480. require.Equal(t, 200, rr.Code)
  481. account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  482. require.Equal(t, 1, len(account.Reservations))
  483. require.Equal(t, "mytopic", account.Reservations[0].Topic)
  484. }
  485. func TestAccount_Reservation_PublishByAnonymousFails(t *testing.T) {
  486. conf := newTestConfigWithAuthFile(t)
  487. conf.AuthDefault = user.PermissionReadWrite
  488. conf.EnableSignup = true
  489. s := newTestServer(t, conf)
  490. // Create user with tier
  491. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  492. require.Equal(t, 200, rr.Code)
  493. require.Nil(t, s.userManager.AddTier(&user.Tier{
  494. Code: "pro",
  495. MessageLimit: 20,
  496. ReservationLimit: 2,
  497. }))
  498. require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
  499. // Reserve a topic
  500. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "mytopic", "everyone":"deny-all"}`, map[string]string{
  501. "Authorization": util.BasicAuth("phil", "mypass"),
  502. })
  503. require.Equal(t, 200, rr.Code)
  504. // Publish a message
  505. rr = request(t, s, "POST", "/mytopic", `Howdy`, map[string]string{
  506. "Authorization": util.BasicAuth("phil", "mypass"),
  507. })
  508. require.Equal(t, 200, rr.Code)
  509. // Publish a message (as anonymous)
  510. rr = request(t, s, "POST", "/mytopic", `Howdy`, nil)
  511. require.Equal(t, 403, rr.Code)
  512. }
  513. func TestAccount_Reservation_Delete_Messages_And_Attachments(t *testing.T) {
  514. conf := newTestConfigWithAuthFile(t)
  515. conf.AuthDefault = user.PermissionReadWrite
  516. s := newTestServer(t, conf)
  517. // Create user with tier
  518. require.Nil(t, s.userManager.AddUser("phil", "mypass", user.RoleUser))
  519. require.Nil(t, s.userManager.AddTier(&user.Tier{
  520. Code: "pro",
  521. MessageLimit: 20,
  522. MessageExpiryDuration: time.Hour,
  523. ReservationLimit: 2,
  524. AttachmentTotalSizeLimit: 10000,
  525. AttachmentFileSizeLimit: 10000,
  526. AttachmentExpiryDuration: time.Hour,
  527. AttachmentBandwidthLimit: 10000,
  528. }))
  529. require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
  530. // Reserve two topics "mytopic1" and "mytopic2"
  531. rr := request(t, s, "POST", "/v1/account/reservation", `{"topic": "mytopic1", "everyone":"deny-all"}`, map[string]string{
  532. "Authorization": util.BasicAuth("phil", "mypass"),
  533. })
  534. require.Equal(t, 200, rr.Code)
  535. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "mytopic2", "everyone":"deny-all"}`, map[string]string{
  536. "Authorization": util.BasicAuth("phil", "mypass"),
  537. })
  538. require.Equal(t, 200, rr.Code)
  539. // Publish a message with attachment to each topic
  540. rr = request(t, s, "POST", "/mytopic1?f=attach.txt", `Howdy`, map[string]string{
  541. "Authorization": util.BasicAuth("phil", "mypass"),
  542. })
  543. require.Equal(t, 200, rr.Code)
  544. m1 := toMessage(t, rr.Body.String())
  545. require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m1.ID))
  546. rr = request(t, s, "POST", "/mytopic2?f=attach.txt", `Howdy`, map[string]string{
  547. "Authorization": util.BasicAuth("phil", "mypass"),
  548. })
  549. require.Equal(t, 200, rr.Code)
  550. m2 := toMessage(t, rr.Body.String())
  551. require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m2.ID))
  552. // Delete reservation
  553. rr = request(t, s, "DELETE", "/v1/account/reservation/mytopic1", ``, map[string]string{
  554. "X-Delete-Messages": "true",
  555. "Authorization": util.BasicAuth("phil", "mypass"),
  556. })
  557. require.Equal(t, 200, rr.Code)
  558. rr = request(t, s, "DELETE", "/v1/account/reservation/mytopic2", ``, map[string]string{
  559. "X-Delete-Messages": "false",
  560. "Authorization": util.BasicAuth("phil", "mypass"),
  561. })
  562. require.Equal(t, 200, rr.Code)
  563. // Verify that messages and attachments were deleted
  564. // This does not explicitly call the manager!
  565. time.Sleep(time.Second)
  566. ms, err := s.messageCache.Messages("mytopic1", sinceAllMessages, false)
  567. require.Nil(t, err)
  568. require.Equal(t, 0, len(ms))
  569. require.NoFileExists(t, filepath.Join(s.config.AttachmentCacheDir, m1.ID))
  570. ms, err = s.messageCache.Messages("mytopic2", sinceAllMessages, false)
  571. require.Nil(t, err)
  572. require.Equal(t, 1, len(ms))
  573. require.Equal(t, m2.ID, ms[0].ID)
  574. require.FileExists(t, filepath.Join(s.config.AttachmentCacheDir, m2.ID))
  575. }
  576. func TestAccount_Reservation_Add_Kills_Other_Subscribers(t *testing.T) {
  577. conf := newTestConfigWithAuthFile(t)
  578. conf.AuthDefault = user.PermissionReadWrite
  579. conf.EnableSignup = true
  580. s := newTestServer(t, conf)
  581. defer s.closeDatabases()
  582. // Create user with tier
  583. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  584. require.Equal(t, 200, rr.Code)
  585. require.Nil(t, s.userManager.AddTier(&user.Tier{
  586. Code: "pro",
  587. MessageLimit: 20,
  588. ReservationLimit: 2,
  589. }))
  590. require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
  591. // Subscribe anonymously
  592. anonCh, userCh := make(chan bool), make(chan bool)
  593. go func() {
  594. rr := request(t, s, "GET", "/mytopic/json", ``, nil) // This blocks until it's killed!
  595. require.Equal(t, 200, rr.Code)
  596. messages := toMessages(t, rr.Body.String())
  597. require.Equal(t, 2, len(messages)) // This is the meat. We should NOT receive the second message!
  598. require.Equal(t, "open", messages[0].Event)
  599. require.Equal(t, "message before reservation", messages[1].Message)
  600. anonCh <- true
  601. log.Info("Anonymous subscription ended")
  602. }()
  603. // Subscribe with user
  604. go func() {
  605. rr := request(t, s, "GET", "/mytopic/json", ``, map[string]string{ // Blocks!
  606. "Authorization": util.BasicAuth("phil", "mypass"),
  607. })
  608. require.Equal(t, 200, rr.Code)
  609. messages := toMessages(t, rr.Body.String())
  610. require.Equal(t, 3, len(messages))
  611. require.Equal(t, "open", messages[0].Event)
  612. require.Equal(t, "message before reservation", messages[1].Message)
  613. require.Equal(t, "message after reservation", messages[2].Message)
  614. userCh <- true
  615. log.Info("User subscription ended")
  616. }()
  617. // Publish message (before reservation)
  618. time.Sleep(2 * time.Second) // Wait for subscribers
  619. rr = request(t, s, "POST", "/mytopic", "message before reservation", nil)
  620. require.Equal(t, 200, rr.Code)
  621. time.Sleep(2 * time.Second) // Wait for subscribers to receive message
  622. // Reserve a topic
  623. rr = request(t, s, "POST", "/v1/account/reservation", `{"topic": "mytopic", "everyone":"deny-all"}`, map[string]string{
  624. "Authorization": util.BasicAuth("phil", "mypass"),
  625. })
  626. require.Equal(t, 200, rr.Code)
  627. // Everyone but phil should be killed
  628. select {
  629. case <-anonCh:
  630. case <-time.After(5 * time.Second):
  631. t.Fatal("Waiting for anonymous subscription to be killed failed")
  632. }
  633. // Publish a message
  634. rr = request(t, s, "POST", "/mytopic", "message after reservation", map[string]string{
  635. "Authorization": util.BasicAuth("phil", "mypass"),
  636. })
  637. require.Equal(t, 200, rr.Code)
  638. // Kill user Go routine
  639. s.topics["mytopic"].CancelSubscribers("<invalid>")
  640. select {
  641. case <-userCh:
  642. case <-time.After(5 * time.Second):
  643. t.Fatal("Waiting for user subscription to be killed failed")
  644. }
  645. }
  646. func TestAccount_Persist_UserStats_After_Tier_Change(t *testing.T) {
  647. conf := newTestConfigWithAuthFile(t)
  648. conf.AuthDefault = user.PermissionReadWrite
  649. conf.AuthStatsQueueWriterInterval = 200 * time.Millisecond
  650. s := newTestServer(t, conf)
  651. defer s.closeDatabases()
  652. // Create user with tier
  653. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  654. require.Nil(t, s.userManager.AddTier(&user.Tier{
  655. Code: "starter",
  656. MessageLimit: 10,
  657. }))
  658. require.Nil(t, s.userManager.AddTier(&user.Tier{
  659. Code: "pro",
  660. MessageLimit: 20,
  661. }))
  662. require.Nil(t, s.userManager.ChangeTier("phil", "starter"))
  663. // Publish a message
  664. rr := request(t, s, "POST", "/mytopic", "hi", map[string]string{
  665. "Authorization": util.BasicAuth("phil", "phil"),
  666. })
  667. require.Equal(t, 200, rr.Code)
  668. // Wait for stats queue writer
  669. time.Sleep(300 * time.Millisecond)
  670. // Verify that message stats were persisted
  671. u, err := s.userManager.User("phil")
  672. require.Nil(t, err)
  673. require.Equal(t, int64(1), u.Stats.Messages)
  674. // Change tier, make a request (to reset limiters)
  675. require.Nil(t, s.userManager.ChangeTier("phil", "pro"))
  676. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  677. "Authorization": util.BasicAuth("phil", "phil"),
  678. })
  679. require.Equal(t, 200, rr.Code)
  680. account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  681. require.Equal(t, int64(1), account.Stats.Messages) // Is not reset!
  682. // Publish another message
  683. rr = request(t, s, "POST", "/mytopic", "hi", map[string]string{
  684. "Authorization": util.BasicAuth("phil", "phil"),
  685. })
  686. require.Equal(t, 200, rr.Code)
  687. // Verify that message stats were persisted
  688. time.Sleep(300 * time.Millisecond)
  689. u, err = s.userManager.User("phil")
  690. require.Nil(t, err)
  691. require.Equal(t, int64(2), u.Stats.Messages) // v.EnqueueUserStats had run!
  692. // Stats keep counting
  693. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  694. "Authorization": util.BasicAuth("phil", "phil"),
  695. })
  696. require.Equal(t, 200, rr.Code)
  697. account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  698. require.Equal(t, int64(2), account.Stats.Messages) // Is not reset!
  699. }