1
0

server_admin_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. package server
  2. import (
  3. "encoding/json"
  4. "github.com/stretchr/testify/require"
  5. "heckel.io/ntfy/v2/user"
  6. "heckel.io/ntfy/v2/util"
  7. "sync/atomic"
  8. "testing"
  9. "time"
  10. )
  11. func TestVersion_Admin(t *testing.T) {
  12. c := newTestConfigWithAuthFile(t)
  13. c.BuildVersion = "1.2.3"
  14. c.BuildCommit = "abcdef0"
  15. c.BuildDate = "2026-02-08T00:00:00Z"
  16. s := newTestServer(t, c)
  17. defer s.closeDatabases()
  18. // Create admin and regular user
  19. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
  20. require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
  21. // Admin can access /v1/version
  22. rr := request(t, s, "GET", "/v1/version", "", map[string]string{
  23. "Authorization": util.BasicAuth("phil", "phil"),
  24. })
  25. require.Equal(t, 200, rr.Code)
  26. var versionResponse apiVersionResponse
  27. require.Nil(t, json.NewDecoder(rr.Body).Decode(&versionResponse))
  28. require.Equal(t, "1.2.3", versionResponse.Version)
  29. require.Equal(t, "abcdef0", versionResponse.Commit)
  30. require.Equal(t, "2026-02-08T00:00:00Z", versionResponse.Date)
  31. // Non-admin user cannot access /v1/version
  32. rr = request(t, s, "GET", "/v1/version", "", map[string]string{
  33. "Authorization": util.BasicAuth("ben", "ben"),
  34. })
  35. require.Equal(t, 401, rr.Code)
  36. // Unauthenticated user cannot access /v1/version
  37. rr = request(t, s, "GET", "/v1/version", "", nil)
  38. require.Equal(t, 401, rr.Code)
  39. }
  40. func TestUser_AddRemove(t *testing.T) {
  41. s := newTestServer(t, newTestConfigWithAuthFile(t))
  42. defer s.closeDatabases()
  43. // Create admin, tier
  44. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
  45. require.Nil(t, s.userManager.AddTier(&user.Tier{
  46. Code: "tier1",
  47. }))
  48. // Create user via API
  49. rr := request(t, s, "POST", "/v1/users", `{"username": "ben", "password":"ben"}`, map[string]string{
  50. "Authorization": util.BasicAuth("phil", "phil"),
  51. })
  52. require.Equal(t, 200, rr.Code)
  53. // Create user with tier via API
  54. rr = request(t, s, "PUT", "/v1/users", `{"username": "emma", "password":"emma", "tier": "tier1"}`, map[string]string{
  55. "Authorization": util.BasicAuth("phil", "phil"),
  56. })
  57. require.Equal(t, 200, rr.Code)
  58. // Check users
  59. users, err := s.userManager.Users()
  60. require.Nil(t, err)
  61. require.Equal(t, 4, len(users))
  62. require.Equal(t, "phil", users[0].Name)
  63. require.Equal(t, "ben", users[1].Name)
  64. require.Equal(t, user.RoleUser, users[1].Role)
  65. require.Nil(t, users[1].Tier)
  66. require.Equal(t, "emma", users[2].Name)
  67. require.Equal(t, user.RoleUser, users[2].Role)
  68. require.Equal(t, "tier1", users[2].Tier.Code)
  69. require.Equal(t, user.Everyone, users[3].Name)
  70. // Delete user via API
  71. rr = request(t, s, "DELETE", "/v1/users", `{"username": "ben"}`, map[string]string{
  72. "Authorization": util.BasicAuth("phil", "phil"),
  73. })
  74. require.Equal(t, 200, rr.Code)
  75. // Check user was deleted
  76. users, err = s.userManager.Users()
  77. require.Nil(t, err)
  78. require.Equal(t, 3, len(users))
  79. require.Equal(t, "phil", users[0].Name)
  80. require.Equal(t, "emma", users[1].Name)
  81. require.Equal(t, user.Everyone, users[2].Name)
  82. // Reject invalid user change
  83. rr = request(t, s, "PUT", "/v1/users", `{"username": "ben"}`, map[string]string{
  84. "Authorization": util.BasicAuth("phil", "phil"),
  85. })
  86. require.Equal(t, 400, rr.Code)
  87. }
  88. func TestUser_AddWithPasswordHash(t *testing.T) {
  89. s := newTestServer(t, newTestConfigWithAuthFile(t))
  90. defer s.closeDatabases()
  91. // Create admin
  92. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
  93. // Create user via API
  94. rr := request(t, s, "POST", "/v1/users", `{"username": "ben", "hash":"$2a$04$2aPIIqPXQU16OfkSUZH1XOzpu1gsPRKkrfVdFLgWQ.tqb.vtTCuVe"}`, map[string]string{
  95. "Authorization": util.BasicAuth("phil", "phil"),
  96. })
  97. require.Equal(t, 200, rr.Code)
  98. // Check that user can login with password
  99. rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
  100. "Authorization": util.BasicAuth("ben", "ben"),
  101. })
  102. require.Equal(t, 200, rr.Code)
  103. // Check users
  104. users, err := s.userManager.Users()
  105. require.Nil(t, err)
  106. require.Equal(t, 3, len(users))
  107. require.Equal(t, "phil", users[0].Name)
  108. require.Equal(t, user.RoleAdmin, users[0].Role)
  109. require.Equal(t, "ben", users[1].Name)
  110. require.Equal(t, user.RoleUser, users[1].Role)
  111. }
  112. func TestUser_ChangeUserPassword(t *testing.T) {
  113. s := newTestServer(t, newTestConfigWithAuthFile(t))
  114. defer s.closeDatabases()
  115. // Create admin
  116. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
  117. // Create user via API
  118. rr := request(t, s, "POST", "/v1/users", `{"username": "ben", "password": "ben"}`, map[string]string{
  119. "Authorization": util.BasicAuth("phil", "phil"),
  120. })
  121. require.Equal(t, 200, rr.Code)
  122. // Try to login with first password
  123. rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
  124. "Authorization": util.BasicAuth("ben", "ben"),
  125. })
  126. require.Equal(t, 200, rr.Code)
  127. // Change password via API
  128. rr = request(t, s, "PUT", "/v1/users", `{"username": "ben", "password": "ben-two"}`, map[string]string{
  129. "Authorization": util.BasicAuth("phil", "phil"),
  130. })
  131. require.Equal(t, 200, rr.Code)
  132. // Make sure first password fails
  133. rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
  134. "Authorization": util.BasicAuth("ben", "ben"),
  135. })
  136. require.Equal(t, 401, rr.Code)
  137. // Try to login with second password
  138. rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
  139. "Authorization": util.BasicAuth("ben", "ben-two"),
  140. })
  141. require.Equal(t, 200, rr.Code)
  142. }
  143. func TestUser_ChangeUserTier(t *testing.T) {
  144. s := newTestServer(t, newTestConfigWithAuthFile(t))
  145. defer s.closeDatabases()
  146. // Create admin, tier
  147. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
  148. require.Nil(t, s.userManager.AddTier(&user.Tier{
  149. Code: "tier1",
  150. }))
  151. require.Nil(t, s.userManager.AddTier(&user.Tier{
  152. Code: "tier2",
  153. }))
  154. // Create user with tier via API
  155. rr := request(t, s, "POST", "/v1/users", `{"username": "ben", "password":"ben", "tier": "tier1"}`, map[string]string{
  156. "Authorization": util.BasicAuth("phil", "phil"),
  157. })
  158. require.Equal(t, 200, rr.Code)
  159. // Check users
  160. users, err := s.userManager.Users()
  161. require.Nil(t, err)
  162. require.Equal(t, 3, len(users))
  163. require.Equal(t, "phil", users[0].Name)
  164. require.Equal(t, "ben", users[1].Name)
  165. require.Equal(t, user.RoleUser, users[1].Role)
  166. require.Equal(t, "tier1", users[1].Tier.Code)
  167. // Change user tier via API
  168. rr = request(t, s, "PUT", "/v1/users", `{"username": "ben", "tier": "tier2"}`, map[string]string{
  169. "Authorization": util.BasicAuth("phil", "phil"),
  170. })
  171. require.Equal(t, 200, rr.Code)
  172. // Check users again
  173. users, err = s.userManager.Users()
  174. require.Nil(t, err)
  175. require.Equal(t, "tier2", users[1].Tier.Code)
  176. }
  177. func TestUser_ChangeUserPasswordAndTier(t *testing.T) {
  178. s := newTestServer(t, newTestConfigWithAuthFile(t))
  179. defer s.closeDatabases()
  180. // Create admin, tier
  181. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
  182. require.Nil(t, s.userManager.AddTier(&user.Tier{
  183. Code: "tier1",
  184. }))
  185. require.Nil(t, s.userManager.AddTier(&user.Tier{
  186. Code: "tier2",
  187. }))
  188. // Create user with tier via API
  189. rr := request(t, s, "POST", "/v1/users", `{"username": "ben", "password":"ben", "tier": "tier1"}`, map[string]string{
  190. "Authorization": util.BasicAuth("phil", "phil"),
  191. })
  192. require.Equal(t, 200, rr.Code)
  193. // Check users
  194. users, err := s.userManager.Users()
  195. require.Nil(t, err)
  196. require.Equal(t, 3, len(users))
  197. require.Equal(t, "phil", users[0].Name)
  198. require.Equal(t, "ben", users[1].Name)
  199. require.Equal(t, user.RoleUser, users[1].Role)
  200. require.Equal(t, "tier1", users[1].Tier.Code)
  201. // Change user password and tier via API
  202. rr = request(t, s, "PUT", "/v1/users", `{"username": "ben", "password":"ben-two", "tier": "tier2"}`, map[string]string{
  203. "Authorization": util.BasicAuth("phil", "phil"),
  204. })
  205. require.Equal(t, 200, rr.Code)
  206. // Make sure first password fails
  207. rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
  208. "Authorization": util.BasicAuth("ben", "ben"),
  209. })
  210. require.Equal(t, 401, rr.Code)
  211. // Try to login with second password
  212. rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
  213. "Authorization": util.BasicAuth("ben", "ben-two"),
  214. })
  215. require.Equal(t, 200, rr.Code)
  216. // Check new tier
  217. users, err = s.userManager.Users()
  218. require.Nil(t, err)
  219. require.Equal(t, "tier2", users[1].Tier.Code)
  220. }
  221. func TestUser_ChangeUserPasswordWithHash(t *testing.T) {
  222. s := newTestServer(t, newTestConfigWithAuthFile(t))
  223. defer s.closeDatabases()
  224. // Create admin
  225. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
  226. // Create user with tier via API
  227. rr := request(t, s, "POST", "/v1/users", `{"username": "ben", "password":"not-ben"}`, map[string]string{
  228. "Authorization": util.BasicAuth("phil", "phil"),
  229. })
  230. require.Equal(t, 200, rr.Code)
  231. // Try to login with first password
  232. rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
  233. "Authorization": util.BasicAuth("ben", "not-ben"),
  234. })
  235. require.Equal(t, 200, rr.Code)
  236. // Change user password and tier via API
  237. rr = request(t, s, "PUT", "/v1/users", `{"username": "ben", "hash":"$2a$04$2aPIIqPXQU16OfkSUZH1XOzpu1gsPRKkrfVdFLgWQ.tqb.vtTCuVe"}`, map[string]string{
  238. "Authorization": util.BasicAuth("phil", "phil"),
  239. })
  240. require.Equal(t, 200, rr.Code)
  241. // Try to login with second password
  242. rr = request(t, s, "POST", "/v1/account/token", "", map[string]string{
  243. "Authorization": util.BasicAuth("ben", "ben"),
  244. })
  245. require.Equal(t, 200, rr.Code)
  246. }
  247. func TestUser_DontChangeAdminPassword(t *testing.T) {
  248. s := newTestServer(t, newTestConfigWithAuthFile(t))
  249. defer s.closeDatabases()
  250. // Create admin
  251. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
  252. require.Nil(t, s.userManager.AddUser("admin", "admin", user.RoleAdmin, false))
  253. // Try to change password via API
  254. rr := request(t, s, "PUT", "/v1/users", `{"username": "admin", "password": "admin-new"}`, map[string]string{
  255. "Authorization": util.BasicAuth("phil", "phil"),
  256. })
  257. require.Equal(t, 403, rr.Code)
  258. }
  259. func TestUser_AddRemove_Failures(t *testing.T) {
  260. s := newTestServer(t, newTestConfigWithAuthFile(t))
  261. defer s.closeDatabases()
  262. // Create admin
  263. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
  264. require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
  265. // Cannot create user with invalid username
  266. rr := request(t, s, "POST", "/v1/users", `{"username": "not valid", "password":"ben"}`, map[string]string{
  267. "Authorization": util.BasicAuth("phil", "phil"),
  268. })
  269. require.Equal(t, 400, rr.Code)
  270. // Cannot create user if user already exists
  271. rr = request(t, s, "POST", "/v1/users", `{"username": "phil", "password":"phil"}`, map[string]string{
  272. "Authorization": util.BasicAuth("phil", "phil"),
  273. })
  274. require.Equal(t, 40901, toHTTPError(t, rr.Body.String()).Code)
  275. // Cannot create user with invalid tier
  276. rr = request(t, s, "POST", "/v1/users", `{"username": "emma", "password":"emma", "tier": "invalid"}`, map[string]string{
  277. "Authorization": util.BasicAuth("phil", "phil"),
  278. })
  279. require.Equal(t, 40030, toHTTPError(t, rr.Body.String()).Code)
  280. // Cannot delete user as non-admin
  281. rr = request(t, s, "DELETE", "/v1/users", `{"username": "ben"}`, map[string]string{
  282. "Authorization": util.BasicAuth("ben", "ben"),
  283. })
  284. require.Equal(t, 401, rr.Code)
  285. // Delete user via API
  286. rr = request(t, s, "DELETE", "/v1/users", `{"username": "ben"}`, map[string]string{
  287. "Authorization": util.BasicAuth("phil", "phil"),
  288. })
  289. require.Equal(t, 200, rr.Code)
  290. }
  291. func TestAccess_AllowReset(t *testing.T) {
  292. c := newTestConfigWithAuthFile(t)
  293. c.AuthDefault = user.PermissionDenyAll
  294. s := newTestServer(t, c)
  295. defer s.closeDatabases()
  296. // User and admin
  297. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
  298. require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
  299. // Subscribing not allowed
  300. rr := request(t, s, "GET", "/gold/json?poll=1", "", map[string]string{
  301. "Authorization": util.BasicAuth("ben", "ben"),
  302. })
  303. require.Equal(t, 403, rr.Code)
  304. // Grant access
  305. rr = request(t, s, "POST", "/v1/users/access", `{"username": "ben", "topic":"gold", "permission":"ro"}`, map[string]string{
  306. "Authorization": util.BasicAuth("phil", "phil"),
  307. })
  308. require.Equal(t, 200, rr.Code)
  309. // Now subscribing is allowed
  310. rr = request(t, s, "GET", "/gold/json?poll=1", "", map[string]string{
  311. "Authorization": util.BasicAuth("ben", "ben"),
  312. })
  313. require.Equal(t, 200, rr.Code)
  314. // Reset access
  315. rr = request(t, s, "DELETE", "/v1/users/access", `{"username": "ben", "topic":"gold"}`, map[string]string{
  316. "Authorization": util.BasicAuth("phil", "phil"),
  317. })
  318. require.Equal(t, 200, rr.Code)
  319. // Subscribing not allowed (again)
  320. rr = request(t, s, "GET", "/gold/json?poll=1", "", map[string]string{
  321. "Authorization": util.BasicAuth("ben", "ben"),
  322. })
  323. require.Equal(t, 403, rr.Code)
  324. }
  325. func TestAccess_AllowReset_NonAdminAttempt(t *testing.T) {
  326. c := newTestConfigWithAuthFile(t)
  327. c.AuthDefault = user.PermissionDenyAll
  328. s := newTestServer(t, c)
  329. defer s.closeDatabases()
  330. // User
  331. require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
  332. // Grant access fails, because non-admin
  333. rr := request(t, s, "POST", "/v1/users/access", `{"username": "ben", "topic":"gold", "permission":"ro"}`, map[string]string{
  334. "Authorization": util.BasicAuth("ben", "ben"),
  335. })
  336. require.Equal(t, 401, rr.Code)
  337. }
  338. func TestAccess_AllowReset_KillConnection(t *testing.T) {
  339. c := newTestConfigWithAuthFile(t)
  340. c.AuthDefault = user.PermissionDenyAll
  341. s := newTestServer(t, c)
  342. defer s.closeDatabases()
  343. // User and admin, grant access to "gol*" topics
  344. require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleAdmin, false))
  345. require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
  346. require.Nil(t, s.userManager.AllowAccess("ben", "gol*", user.PermissionRead)) // Wildcard!
  347. start, timeTaken := time.Now(), atomic.Int64{}
  348. go func() {
  349. rr := request(t, s, "GET", "/gold/json", "", map[string]string{
  350. "Authorization": util.BasicAuth("ben", "ben"),
  351. })
  352. require.Equal(t, 200, rr.Code)
  353. timeTaken.Store(time.Since(start).Milliseconds())
  354. }()
  355. time.Sleep(500 * time.Millisecond)
  356. // Reset access
  357. rr := request(t, s, "DELETE", "/v1/users/access", `{"username": "ben", "topic":"gol*"}`, map[string]string{
  358. "Authorization": util.BasicAuth("phil", "phil"),
  359. })
  360. require.Equal(t, 200, rr.Code)
  361. // Wait for connection to be killed; this will fail if the connection is never killed
  362. waitFor(t, func() bool {
  363. return timeTaken.Load() >= 500
  364. })
  365. }