mz_chatbot / scripts /check_auth.py
Shurem's picture
auth system added
bf0bce6
Raw History Blame Contribute Delete
5.62 kB
"""
check_auth.py — offline checks for login (services/auth.py). No network, no database.
uv run python scripts/check_auth.py
Covers the session token (sign / verify / tamper / expiry / wrong secret), the
company-domain rule, and verify_google_credential() against FAKE Google replies
(wrong audience, unverified email, wrong domain, wrong hd, good). A real Google
sign-in still needs a Google Client ID and is tested in the browser.
"""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from app.config import settings # noqa: E402
from app.services import auth # noqa: E402
FAILED = 0
def check(label: str, ok: bool) -> None:
global FAILED
FAILED += 0 if ok else 1
print(f" [{'ok ' if ok else 'FAIL'}] {label}")
class FakeResp:
def __init__(self, status: int, data: dict):
self.status_code = status
self._data = data
def json(self):
return self._data
def google_reply(**overrides) -> dict:
base = {
"aud": "client-123", "iss": "https://accounts.google.com", "email_verified": "true",
"email": "ali@elipsestudio.com", "name": "Ali Khan", "picture": "http://x/p.png", "hd": "elipsestudio.com",
}
base.update(overrides)
return base
def expect_error(credential_reply: dict | None, status: int = 200) -> bool:
auth.requests.get = lambda *a, **k: FakeResp(status, credential_reply or {}) # type: ignore[assignment]
try:
auth.verify_google_credential("tok")
return False
except auth.AuthError:
return True
def main() -> int:
settings.auth_secret = "s" * 40
settings.google_client_id = "client-123"
settings.allowed_email_domains = "elipsestudio.com, other.org"
settings.allowed_emails = "" # ignore whatever the real .env lists, so this test is the same on every machine
settings.auth_token_days = 7
print("\nsession token")
tok = auth.create_token(7, "ali@elipsestudio.com", "Ali Khan")
claims = auth.decode_token(tok)
check("a fresh token verifies and carries the user id", claims is not None and claims["sub"] == "7")
check("three dot-separated JWT parts", tok.count(".") == 2)
h, p, sig = tok.split(".")
check("tampered payload is rejected", auth.decode_token(f"{h}.{p[:-2]}xx.{sig}") is None)
check("tampered signature is rejected", auth.decode_token(f"{h}.{p}.{sig[:-2]}xx") is None)
check("an expired token is rejected", auth.decode_token(auth.create_token(7, "a@elipsestudio.com", "A", days=-1)) is None)
check("garbage is rejected, not raised", auth.decode_token("not-a-token") is None and auth.decode_token("") is None)
settings.auth_secret = "t" * 40
check("a token signed with another secret is rejected", auth.decode_token(tok) is None)
settings.auth_secret = "s" * 40
settings.auth_secret = ""
check("no AUTH_SECRET -> cannot issue a token", _raises(lambda: auth.create_token(1, "a@b.c", "A")))
check("no AUTH_SECRET -> nothing verifies", auth.decode_token(tok) is None)
settings.auth_secret = "s" * 40
print("\ncompany domain")
check("elipsestudio.com allowed", auth.email_domain_allowed("Ali@ElipseStudio.com"))
check("second configured domain allowed", auth.email_domain_allowed("x@other.org"))
check("gmail.com refused", not auth.email_domain_allowed("ali@gmail.com"))
check("look-alike domain refused", not auth.email_domain_allowed("ali@elipsestudio.com.evil.io"))
check("sub-domain refused", not auth.email_domain_allowed("ali@mail.elipsestudio.com"))
check("no @ refused", not auth.email_domain_allowed("elipsestudio.com"))
print("\napproved emails (ALLOWED_EMAILS)")
settings.allowed_emails = "Boss@Gmail.com, sara@partner.io"
check("a listed email is allowed, case-insensitive", auth.email_allowed("boss@gmail.com"))
check("an unlisted gmail is still refused", not auth.email_allowed("other@gmail.com"))
check("a company-domain email is still allowed by domain", auth.email_allowed("x@elipsestudio.com"))
check("a look-alike of a listed email is refused", not auth.email_allowed("boss@gmail.com.evil.io"))
settings.allowed_emails = ""
print("\nverify_google_credential (fake Google replies)")
real_get = auth.requests.get
try:
auth.requests.get = lambda *a, **k: FakeResp(200, google_reply()) # type: ignore[assignment]
who = auth.verify_google_credential("tok")
check("a good reply returns email/name/picture", who["email"] == "ali@elipsestudio.com" and who["name"] == "Ali Khan")
check("Google says invalid (HTTP 400)", expect_error({}, 400))
check("wrong audience refused", expect_error(google_reply(aud="someone-else")))
check("wrong issuer refused", expect_error(google_reply(iss="https://evil.example")))
check("unverified email refused", expect_error(google_reply(email_verified="false")))
check("gmail address refused", expect_error(google_reply(email="ali@gmail.com", hd="")))
check("wrong hd refused even with a good-looking email", expect_error(google_reply(hd="evil.io")))
settings.google_client_id = ""
check("no GOOGLE_CLIENT_ID -> clear error", expect_error(google_reply()))
finally:
auth.requests.get = real_get
print()
print("All checks passed." if not FAILED else f"{FAILED} check(s) FAILED")
return 1 if FAILED else 0
def _raises(fn) -> bool:
try:
fn()
except auth.AuthError:
return True
return False
if __name__ == "__main__":
sys.exit(main())