server_account_test.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. package server
  2. import (
  3. "database/sql"
  4. "fmt"
  5. "github.com/stretchr/testify/require"
  6. "heckel.io/ntfy/user"
  7. "heckel.io/ntfy/util"
  8. "io"
  9. "testing"
  10. "time"
  11. )
  12. func TestAccount_Signup_Success(t *testing.T) {
  13. conf := newTestConfigWithAuthFile(t)
  14. conf.EnableSignup = true
  15. s := newTestServer(t, conf)
  16. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  17. require.Equal(t, 200, rr.Code)
  18. rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
  19. "Authorization": util.BasicAuth("phil", "mypass"),
  20. })
  21. require.Equal(t, 200, rr.Code)
  22. token, _ := util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
  23. require.NotEmpty(t, token.Token)
  24. require.True(t, time.Now().Add(71*time.Hour).Unix() < token.Expires)
  25. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  26. "Authorization": util.BearerAuth(token.Token),
  27. })
  28. require.Equal(t, 200, rr.Code)
  29. account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  30. require.Equal(t, "phil", account.Username)
  31. require.Equal(t, "user", account.Role)
  32. }
  33. func TestAccount_Signup_UserExists(t *testing.T) {
  34. conf := newTestConfigWithAuthFile(t)
  35. conf.EnableSignup = true
  36. s := newTestServer(t, conf)
  37. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  38. require.Equal(t, 200, rr.Code)
  39. rr = request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  40. require.Equal(t, 409, rr.Code)
  41. require.Equal(t, 40901, toHTTPError(t, rr.Body.String()).Code)
  42. }
  43. func TestAccount_Signup_LimitReached(t *testing.T) {
  44. conf := newTestConfigWithAuthFile(t)
  45. conf.EnableSignup = true
  46. s := newTestServer(t, conf)
  47. for i := 0; i < 3; i++ {
  48. rr := request(t, s, "POST", "/v1/account", fmt.Sprintf(`{"username":"phil%d", "password":"mypass"}`, i), nil)
  49. require.Equal(t, 200, rr.Code)
  50. }
  51. rr := request(t, s, "POST", "/v1/account", `{"username":"thiswontwork", "password":"mypass"}`, nil)
  52. require.Equal(t, 429, rr.Code)
  53. require.Equal(t, 42906, toHTTPError(t, rr.Body.String()).Code)
  54. }
  55. func TestAccount_Signup_AsUser(t *testing.T) {
  56. conf := newTestConfigWithAuthFile(t)
  57. conf.EnableSignup = true
  58. s := newTestServer(t, conf)
  59. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin))
  60. require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser))
  61. rr := request(t, s, "POST", "/v1/account", `{"username":"emma", "password":"emma"}`, map[string]string{
  62. "Authorization": util.BasicAuth("phil", "phil"),
  63. })
  64. require.Equal(t, 200, rr.Code)
  65. rr = request(t, s, "POST", "/v1/account", `{"username":"marian", "password":"marian"}`, map[string]string{
  66. "Authorization": util.BasicAuth("ben", "ben"),
  67. })
  68. require.Equal(t, 401, rr.Code)
  69. }
  70. func TestAccount_Signup_Disabled(t *testing.T) {
  71. conf := newTestConfigWithAuthFile(t)
  72. conf.EnableSignup = false
  73. s := newTestServer(t, conf)
  74. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  75. require.Equal(t, 400, rr.Code)
  76. require.Equal(t, 40022, toHTTPError(t, rr.Body.String()).Code)
  77. }
  78. func TestAccount_Get_Anonymous(t *testing.T) {
  79. conf := newTestConfigWithAuthFile(t)
  80. conf.VisitorRequestLimitReplenish = 86 * time.Second
  81. conf.VisitorEmailLimitReplenish = time.Hour
  82. conf.VisitorAttachmentTotalSizeLimit = 5123
  83. conf.AttachmentFileSizeLimit = 512
  84. s := newTestServer(t, conf)
  85. s.smtpSender = &testMailer{}
  86. rr := request(t, s, "GET", "/v1/account", "", nil)
  87. require.Equal(t, 200, rr.Code)
  88. account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  89. require.Equal(t, "*", account.Username)
  90. require.Equal(t, string(user.RoleAnonymous), account.Role)
  91. require.Equal(t, "ip", account.Limits.Basis)
  92. require.Equal(t, int64(1004), account.Limits.Messages) // I hate this
  93. require.Equal(t, int64(24), account.Limits.Emails) // I hate this
  94. require.Equal(t, int64(5123), account.Limits.AttachmentTotalSize)
  95. require.Equal(t, int64(512), account.Limits.AttachmentFileSize)
  96. require.Equal(t, int64(0), account.Stats.Messages)
  97. require.Equal(t, int64(1004), account.Stats.MessagesRemaining)
  98. require.Equal(t, int64(0), account.Stats.Emails)
  99. require.Equal(t, int64(24), account.Stats.EmailsRemaining)
  100. rr = request(t, s, "POST", "/mytopic", "", nil)
  101. require.Equal(t, 200, rr.Code)
  102. rr = request(t, s, "POST", "/mytopic", "", map[string]string{
  103. "Email": "phil@ntfy.sh",
  104. })
  105. require.Equal(t, 200, rr.Code)
  106. rr = request(t, s, "GET", "/v1/account", "", nil)
  107. require.Equal(t, 200, rr.Code)
  108. account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  109. require.Equal(t, int64(2), account.Stats.Messages)
  110. require.Equal(t, int64(1002), account.Stats.MessagesRemaining)
  111. require.Equal(t, int64(1), account.Stats.Emails)
  112. require.Equal(t, int64(23), account.Stats.EmailsRemaining)
  113. }
  114. func TestAccount_ChangeSettings(t *testing.T) {
  115. s := newTestServer(t, newTestConfigWithAuthFile(t))
  116. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  117. user, _ := s.userManager.User("phil")
  118. token, _ := s.userManager.CreateToken(user)
  119. rr := request(t, s, "PATCH", "/v1/account/settings", `{"notification": {"sound": "juntos"},"ignored": true}`, map[string]string{
  120. "Authorization": util.BasicAuth("phil", "phil"),
  121. })
  122. require.Equal(t, 200, rr.Code)
  123. rr = request(t, s, "PATCH", "/v1/account/settings", `{"notification": {"delete_after": 86400}, "language": "de"}`, map[string]string{
  124. "Authorization": util.BearerAuth(token.Value),
  125. })
  126. require.Equal(t, 200, rr.Code)
  127. rr = request(t, s, "GET", "/v1/account", `{"username":"marian", "password":"marian"}`, map[string]string{
  128. "Authorization": util.BearerAuth(token.Value),
  129. })
  130. require.Equal(t, 200, rr.Code)
  131. account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  132. require.Equal(t, "de", account.Language)
  133. require.Equal(t, 86400, account.Notification.DeleteAfter)
  134. require.Equal(t, "juntos", account.Notification.Sound)
  135. require.Equal(t, 0, account.Notification.MinPriority) // Not set
  136. }
  137. func TestAccount_Subscription_AddUpdateDelete(t *testing.T) {
  138. s := newTestServer(t, newTestConfigWithAuthFile(t))
  139. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  140. rr := request(t, s, "POST", "/v1/account/subscription", `{"base_url": "http://abc.com", "topic": "def"}`, map[string]string{
  141. "Authorization": util.BasicAuth("phil", "phil"),
  142. })
  143. require.Equal(t, 200, rr.Code)
  144. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  145. "Authorization": util.BasicAuth("phil", "phil"),
  146. })
  147. require.Equal(t, 200, rr.Code)
  148. account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  149. require.Equal(t, 1, len(account.Subscriptions))
  150. require.NotEmpty(t, account.Subscriptions[0].ID)
  151. require.Equal(t, "http://abc.com", account.Subscriptions[0].BaseURL)
  152. require.Equal(t, "def", account.Subscriptions[0].Topic)
  153. require.Equal(t, "", account.Subscriptions[0].DisplayName)
  154. subscriptionID := account.Subscriptions[0].ID
  155. rr = request(t, s, "PATCH", "/v1/account/subscription/"+subscriptionID, `{"display_name": "ding dong"}`, map[string]string{
  156. "Authorization": util.BasicAuth("phil", "phil"),
  157. })
  158. require.Equal(t, 200, rr.Code)
  159. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  160. "Authorization": util.BasicAuth("phil", "phil"),
  161. })
  162. require.Equal(t, 200, rr.Code)
  163. account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  164. require.Equal(t, 1, len(account.Subscriptions))
  165. require.Equal(t, subscriptionID, account.Subscriptions[0].ID)
  166. require.Equal(t, "http://abc.com", account.Subscriptions[0].BaseURL)
  167. require.Equal(t, "def", account.Subscriptions[0].Topic)
  168. require.Equal(t, "ding dong", account.Subscriptions[0].DisplayName)
  169. rr = request(t, s, "DELETE", "/v1/account/subscription/"+subscriptionID, "", map[string]string{
  170. "Authorization": util.BasicAuth("phil", "phil"),
  171. })
  172. require.Equal(t, 200, rr.Code)
  173. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  174. "Authorization": util.BasicAuth("phil", "phil"),
  175. })
  176. require.Equal(t, 200, rr.Code)
  177. account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  178. require.Equal(t, 0, len(account.Subscriptions))
  179. }
  180. func TestAccount_ChangePassword(t *testing.T) {
  181. s := newTestServer(t, newTestConfigWithAuthFile(t))
  182. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  183. rr := request(t, s, "POST", "/v1/account/password", `{"password": "new password"}`, map[string]string{
  184. "Authorization": util.BasicAuth("phil", "phil"),
  185. })
  186. require.Equal(t, 200, rr.Code)
  187. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  188. "Authorization": util.BasicAuth("phil", "phil"),
  189. })
  190. require.Equal(t, 401, rr.Code)
  191. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  192. "Authorization": util.BasicAuth("phil", "new password"),
  193. })
  194. require.Equal(t, 200, rr.Code)
  195. }
  196. func TestAccount_ChangePassword_NoAccount(t *testing.T) {
  197. s := newTestServer(t, newTestConfigWithAuthFile(t))
  198. rr := request(t, s, "POST", "/v1/account/password", `{"password": "new password"}`, nil)
  199. require.Equal(t, 401, rr.Code)
  200. }
  201. func TestAccount_ExtendToken(t *testing.T) {
  202. s := newTestServer(t, newTestConfigWithAuthFile(t))
  203. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  204. rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
  205. "Authorization": util.BasicAuth("phil", "phil"),
  206. })
  207. require.Equal(t, 200, rr.Code)
  208. token, err := util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
  209. require.Nil(t, err)
  210. time.Sleep(time.Second)
  211. rr = request(t, s, "PATCH", "/v1/account/token", "", map[string]string{
  212. "Authorization": util.BearerAuth(token.Token),
  213. })
  214. require.Equal(t, 200, rr.Code)
  215. extendedToken, err := util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
  216. require.Nil(t, err)
  217. require.Equal(t, token.Token, extendedToken.Token)
  218. require.True(t, token.Expires < extendedToken.Expires)
  219. }
  220. func TestAccount_ExtendToken_NoTokenProvided(t *testing.T) {
  221. s := newTestServer(t, newTestConfigWithAuthFile(t))
  222. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  223. rr := request(t, s, "PATCH", "/v1/account/token", "", map[string]string{
  224. "Authorization": util.BasicAuth("phil", "phil"), // Not Bearer!
  225. })
  226. require.Equal(t, 400, rr.Code)
  227. require.Equal(t, 40023, toHTTPError(t, rr.Body.String()).Code)
  228. }
  229. func TestAccount_DeleteToken(t *testing.T) {
  230. s := newTestServer(t, newTestConfigWithAuthFile(t))
  231. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser))
  232. rr := request(t, s, "POST", "/v1/account/token", "", map[string]string{
  233. "Authorization": util.BasicAuth("phil", "phil"),
  234. })
  235. require.Equal(t, 200, rr.Code)
  236. token, err := util.UnmarshalJSON[apiAccountTokenResponse](io.NopCloser(rr.Body))
  237. require.Nil(t, err)
  238. // Delete token failure (using basic auth)
  239. rr = request(t, s, "DELETE", "/v1/account/token", "", map[string]string{
  240. "Authorization": util.BasicAuth("phil", "phil"), // Not Bearer!
  241. })
  242. require.Equal(t, 400, rr.Code)
  243. require.Equal(t, 40023, toHTTPError(t, rr.Body.String()).Code)
  244. // Delete token with wrong token
  245. rr = request(t, s, "DELETE", "/v1/account/token", "", map[string]string{
  246. "Authorization": util.BearerAuth("invalidtoken"),
  247. })
  248. require.Equal(t, 401, rr.Code)
  249. // Delete token with correct token
  250. rr = request(t, s, "DELETE", "/v1/account/token", "", map[string]string{
  251. "Authorization": util.BearerAuth(token.Token),
  252. })
  253. require.Equal(t, 200, rr.Code)
  254. // Cannot get account anymore
  255. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  256. "Authorization": util.BearerAuth(token.Token),
  257. })
  258. require.Equal(t, 401, rr.Code)
  259. }
  260. func TestAccount_Delete_Success(t *testing.T) {
  261. conf := newTestConfigWithAuthFile(t)
  262. conf.EnableSignup = true
  263. s := newTestServer(t, conf)
  264. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  265. require.Equal(t, 200, rr.Code)
  266. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  267. "Authorization": util.BasicAuth("phil", "mypass"),
  268. })
  269. require.Equal(t, 200, rr.Code)
  270. rr = request(t, s, "DELETE", "/v1/account", "", map[string]string{
  271. "Authorization": util.BasicAuth("phil", "mypass"),
  272. })
  273. require.Equal(t, 200, rr.Code)
  274. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  275. "Authorization": util.BasicAuth("phil", "mypass"),
  276. })
  277. require.Equal(t, 401, rr.Code)
  278. }
  279. func TestAccount_Delete_Not_Allowed(t *testing.T) {
  280. conf := newTestConfigWithAuthFile(t)
  281. conf.EnableSignup = true
  282. s := newTestServer(t, conf)
  283. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  284. require.Equal(t, 200, rr.Code)
  285. rr = request(t, s, "DELETE", "/v1/account", "", nil)
  286. require.Equal(t, 401, rr.Code)
  287. }
  288. func TestAccount_Reservation_Add_User_No_Plan_Failure(t *testing.T) {
  289. conf := newTestConfigWithAuthFile(t)
  290. conf.EnableSignup = true
  291. s := newTestServer(t, conf)
  292. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  293. require.Equal(t, 200, rr.Code)
  294. rr = request(t, s, "POST", "/v1/account/access", `{"everyone":"deny-all"}`, map[string]string{
  295. "Authorization": util.BasicAuth("phil", "mypass"),
  296. })
  297. require.Equal(t, 401, rr.Code)
  298. }
  299. func TestAccount_Reservation_Add_Admin_Success(t *testing.T) {
  300. conf := newTestConfigWithAuthFile(t)
  301. conf.EnableSignup = true
  302. s := newTestServer(t, conf)
  303. require.Nil(t, s.userManager.AddUser("phil", "adminpass", user.RoleAdmin))
  304. rr := request(t, s, "POST", "/v1/account/access", `{"everyone":"deny-all"}`, map[string]string{
  305. "Authorization": util.BasicAuth("phil", "adminpass"),
  306. })
  307. require.Equal(t, 200, rr.Code)
  308. }
  309. func TestAccount_Reservation_Add_Remove_User_With_Plan_Success(t *testing.T) {
  310. conf := newTestConfigWithAuthFile(t)
  311. conf.EnableSignup = true
  312. s := newTestServer(t, conf)
  313. // Create user
  314. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  315. require.Equal(t, 200, rr.Code)
  316. // Create a plan (hack!)
  317. db, err := sql.Open("sqlite3", conf.AuthFile)
  318. require.Nil(t, err)
  319. _, err = db.Exec(`
  320. INSERT INTO plan (id, code, messages_limit, emails_limit, attachment_file_size_limit, attachment_total_size_limit, topics_limit)
  321. VALUES (1, 'testplan', 10, 10, 10, 10, 2);
  322. UPDATE user SET plan_id = 1 WHERE user = 'phil';
  323. `)
  324. require.Nil(t, err)
  325. // Reserve two topics
  326. rr = request(t, s, "POST", "/v1/account/access", `{"topic": "mytopic", "everyone":"deny-all"}`, map[string]string{
  327. "Authorization": util.BasicAuth("phil", "mypass"),
  328. })
  329. require.Equal(t, 200, rr.Code)
  330. rr = request(t, s, "POST", "/v1/account/access", `{"topic": "another", "everyone":"read-only"}`, map[string]string{
  331. "Authorization": util.BasicAuth("phil", "mypass"),
  332. })
  333. require.Equal(t, 200, rr.Code)
  334. // Trying to reserve a third should fail
  335. rr = request(t, s, "POST", "/v1/account/access", `{"topic": "yet-another", "everyone":"deny-all"}`, map[string]string{
  336. "Authorization": util.BasicAuth("phil", "mypass"),
  337. })
  338. require.Equal(t, 429, rr.Code)
  339. // Modify existing should still work
  340. rr = request(t, s, "POST", "/v1/account/access", `{"topic": "another", "everyone":"write-only"}`, map[string]string{
  341. "Authorization": util.BasicAuth("phil", "mypass"),
  342. })
  343. require.Equal(t, 200, rr.Code)
  344. // Check account result
  345. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  346. "Authorization": util.BasicAuth("phil", "mypass"),
  347. })
  348. require.Equal(t, 200, rr.Code)
  349. account, _ := util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  350. require.Equal(t, 2, len(account.Reservations))
  351. require.Equal(t, "another", account.Reservations[0].Topic)
  352. require.Equal(t, "write-only", account.Reservations[0].Everyone)
  353. require.Equal(t, "mytopic", account.Reservations[1].Topic)
  354. require.Equal(t, "deny-all", account.Reservations[1].Everyone)
  355. // Delete and re-check
  356. rr = request(t, s, "DELETE", "/v1/account/access/another", "", map[string]string{
  357. "Authorization": util.BasicAuth("phil", "mypass"),
  358. })
  359. require.Equal(t, 200, rr.Code)
  360. rr = request(t, s, "GET", "/v1/account", "", map[string]string{
  361. "Authorization": util.BasicAuth("phil", "mypass"),
  362. })
  363. require.Equal(t, 200, rr.Code)
  364. account, _ = util.UnmarshalJSON[apiAccountResponse](io.NopCloser(rr.Body))
  365. require.Equal(t, 1, len(account.Reservations))
  366. require.Equal(t, "mytopic", account.Reservations[0].Topic)
  367. }
  368. func TestAccount_Reservation_Add_Access_By_Anonymous_Fails(t *testing.T) {
  369. conf := newTestConfigWithAuthFile(t)
  370. conf.AuthDefault = user.PermissionReadWrite
  371. conf.EnableSignup = true
  372. s := newTestServer(t, conf)
  373. // Create user
  374. rr := request(t, s, "POST", "/v1/account", `{"username":"phil", "password":"mypass"}`, nil)
  375. require.Equal(t, 200, rr.Code)
  376. // Create a plan (hack!)
  377. db, err := sql.Open("sqlite3", conf.AuthFile)
  378. require.Nil(t, err)
  379. _, err = db.Exec(`
  380. INSERT INTO plan (id, code, messages_limit, emails_limit, attachment_file_size_limit, attachment_total_size_limit, topics_limit)
  381. VALUES (1, 'testplan', 10, 10, 10, 10, 2);
  382. UPDATE user SET plan_id = 1 WHERE user = 'phil';
  383. `)
  384. require.Nil(t, err)
  385. // Reserve a topic
  386. rr = request(t, s, "POST", "/v1/account/access", `{"topic": "mytopic", "everyone":"deny-all"}`, map[string]string{
  387. "Authorization": util.BasicAuth("phil", "mypass"),
  388. })
  389. require.Equal(t, 200, rr.Code)
  390. // Publish a message
  391. rr = request(t, s, "POST", "/mytopic", `Howdy`, map[string]string{
  392. "Authorization": util.BasicAuth("phil", "mypass"),
  393. })
  394. require.Equal(t, 200, rr.Code)
  395. // Publish a message (as anonymous)
  396. rr = request(t, s, "POST", "/mytopic", `Howdy`, nil)
  397. require.Equal(t, 403, rr.Code)
  398. }