test_admin_api_auth.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  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. await app_modules.wbb.db.managed_chats.insert_one(
  58. {
  59. "bot_id": "primary",
  60. "chat_id": -100,
  61. "title": "机器人已退出的群",
  62. "accessible": False,
  63. }
  64. )
  65. delete_without_csrf = await client.delete("/api/admin/v1/chats/-100")
  66. assert delete_without_csrf.status == 403
  67. deleted = await client.delete(
  68. "/api/admin/v1/chats/-100",
  69. headers={"X-CSRF-Token": changed_data["csrf_token"]},
  70. )
  71. assert deleted.status == 200
  72. assert (await deleted.json())["data"] == {
  73. "chat_id": "-100",
  74. "removed": True,
  75. }
  76. assert await app_modules.wbb.db.managed_chats.find_one({"chat_id": -100}) is None
  77. logout = await client.post(
  78. "/api/admin/v1/auth/logout",
  79. headers={"X-CSRF-Token": changed_data["csrf_token"]},
  80. )
  81. assert logout.status == 200
  82. unauthenticated = await client.get("/api/admin/v1/dashboard")
  83. assert unauthenticated.status == 401
  84. finally:
  85. await client.close()
  86. async def test_login_is_rate_limited_with_mongo_naive_datetime(app_modules):
  87. admin_api = app_modules.load("wbb.admin.api")
  88. application = admin_api.build_admin_application()
  89. await application["admin_api"].initialize()
  90. client = TestClient(TestServer(application), cookie_jar=CookieJar(unsafe=True))
  91. await client.start_server()
  92. try:
  93. for _ in range(5):
  94. response = await client.post(
  95. "/api/admin/v1/auth/login",
  96. json={"username": "admin", "password": "wrong"},
  97. )
  98. assert response.status == 401
  99. await app_modules.wbb.db.admin_users.update_one(
  100. {"username": "admin"},
  101. {"$set": {"last_failed_login_at": datetime.now(UTC).replace(tzinfo=None)}},
  102. )
  103. limited = await client.post(
  104. "/api/admin/v1/auth/login",
  105. json={"username": "admin", "password": "qwe0.123456"},
  106. )
  107. assert limited.status == 429
  108. assert (await limited.json())["error"]["code"] == "login_rate_limited"
  109. expired_at = datetime.now(UTC) - timedelta(minutes=16)
  110. await app_modules.wbb.db.admin_users.update_one(
  111. {"username": "admin"},
  112. {"$set": {"last_failed_login_at": expired_at.replace(tzinfo=None)}},
  113. )
  114. login = await client.post(
  115. "/api/admin/v1/auth/login",
  116. json={"username": "admin", "password": "qwe0.123456"},
  117. )
  118. assert login.status == 200
  119. finally:
  120. await client.close()