test_admin_api_auth.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. from __future__ import annotations
  2. from datetime import UTC, datetime, timedelta
  3. from aiohttp import CookieJar
  4. from aiohttp.test_utils import TestClient, TestServer
  5. async def test_login_forced_password_change_csrf_and_session(app_modules):
  6. admin_api = app_modules.load("wbb.admin.api")
  7. application = admin_api.build_admin_application()
  8. await application["admin_api"].initialize()
  9. client = TestClient(TestServer(application), cookie_jar=CookieJar(unsafe=True))
  10. await client.start_server()
  11. try:
  12. invalid = await client.post(
  13. "/api/admin/v1/auth/login",
  14. json={"username": "admin", "password": "wrong"},
  15. )
  16. assert invalid.status == 401
  17. assert (await invalid.json())["error"]["code"] == "invalid_credentials"
  18. login = await client.post(
  19. "/api/admin/v1/auth/login",
  20. json={"username": "admin", "password": "qwe0.123456"},
  21. )
  22. assert login.status == 200
  23. login_data = (await login.json())["data"]
  24. assert login_data["must_change_password"] is True
  25. blocked = await client.get("/api/admin/v1/dashboard")
  26. assert blocked.status == 428
  27. assert (await blocked.json())["error"]["code"] == "password_change_required"
  28. no_csrf = await client.put(
  29. "/api/admin/v1/auth/password",
  30. json={
  31. "current_password": "qwe0.123456",
  32. "new_password": "changed-pass-123",
  33. },
  34. )
  35. assert no_csrf.status == 403
  36. changed = await client.put(
  37. "/api/admin/v1/auth/password",
  38. headers={"X-CSRF-Token": login_data["csrf_token"]},
  39. json={
  40. "current_password": "qwe0.123456",
  41. "new_password": "changed-pass-123",
  42. },
  43. )
  44. assert changed.status == 200
  45. changed_data = (await changed.json())["data"]
  46. assert changed_data["must_change_password"] is False
  47. unconfirmed = await client.patch(
  48. "/api/admin/v1/chats/-100/profile",
  49. headers={"X-CSRF-Token": changed_data["csrf_token"]},
  50. json={"title": "New title"},
  51. )
  52. assert unconfirmed.status == 409
  53. assert (await unconfirmed.json())["error"]["code"] == "confirmation_required"
  54. dashboard = await client.get("/api/admin/v1/dashboard")
  55. assert dashboard.status == 200
  56. assert "counts" in (await dashboard.json())["data"]
  57. logout = await client.post(
  58. "/api/admin/v1/auth/logout",
  59. headers={"X-CSRF-Token": changed_data["csrf_token"]},
  60. )
  61. assert logout.status == 200
  62. unauthenticated = await client.get("/api/admin/v1/dashboard")
  63. assert unauthenticated.status == 401
  64. finally:
  65. await client.close()
  66. async def test_login_is_rate_limited_with_mongo_naive_datetime(app_modules):
  67. admin_api = app_modules.load("wbb.admin.api")
  68. application = admin_api.build_admin_application()
  69. await application["admin_api"].initialize()
  70. client = TestClient(TestServer(application), cookie_jar=CookieJar(unsafe=True))
  71. await client.start_server()
  72. try:
  73. for _ in range(5):
  74. response = await client.post(
  75. "/api/admin/v1/auth/login",
  76. json={"username": "admin", "password": "wrong"},
  77. )
  78. assert response.status == 401
  79. await app_modules.wbb.db.admin_users.update_one(
  80. {"username": "admin"},
  81. {"$set": {"last_failed_login_at": datetime.now(UTC).replace(tzinfo=None)}},
  82. )
  83. limited = await client.post(
  84. "/api/admin/v1/auth/login",
  85. json={"username": "admin", "password": "qwe0.123456"},
  86. )
  87. assert limited.status == 429
  88. assert (await limited.json())["error"]["code"] == "login_rate_limited"
  89. expired_at = datetime.now(UTC) - timedelta(minutes=16)
  90. await app_modules.wbb.db.admin_users.update_one(
  91. {"username": "admin"},
  92. {"$set": {"last_failed_login_at": expired_at.replace(tzinfo=None)}},
  93. )
  94. login = await client.post(
  95. "/api/admin/v1/auth/login",
  96. json={"username": "admin", "password": "qwe0.123456"},
  97. )
  98. assert login.status == 200
  99. finally:
  100. await client.close()