diff --git a/docs/waves/wave-10.md b/docs/waves/wave-10.md index d0b0155..f1e72af 100644 --- a/docs/waves/wave-10.md +++ b/docs/waves/wave-10.md @@ -97,6 +97,7 @@ misconfigured deploy is indistinguishable from a forgotten password at the login **Done when:** +- [x] the API logs one line at startup saying whether LDAP is configured and which group gates sign-in — configuration only, so an unreachable DC cannot hang startup. This is what `DEPLOYMENT.md`, `DEPLOY-login-portal.md` and `server/README.md` all send people to first; it was specified in this task on Aug 21 and not actually written until Aug 24. - [x] a correct domain password signs in and sets the session cookie — confirmed against the live domain Aug 24, and in `ldap_auth_check` - [x] a wrong password is refused with the generic message - [x] a blank password is refused (guards `T10.1` from the caller's side too) diff --git a/server/app.py b/server/app.py index f15e3c6..ddeef59 100644 --- a/server/app.py +++ b/server/app.py @@ -10,6 +10,7 @@ Interactive docs: http:///api/docs """ import base64 import logging +from contextlib import asynccontextmanager import os import re import uuid @@ -44,7 +45,30 @@ if engine.dialect.name == "sqlite": # Interactive docs are handy in dev but hand an attacker the full API map in prod, # so enable them only on the SQLite dev fallback (production runs on Postgres). _docs_enabled = engine.dialect.name == "sqlite" + + +@asynccontextmanager +async def _lifespan(_app): + """Say, once, whether anyone can sign in at all. + + D13 removed the local password path and left no break-glass, so a broken LDAP + configuration and a forgotten password look identical at the login box. This + line is what tells an operator which one they have, and DEPLOYMENT.md, + DEPLOY-login-portal.md and server/README.md all send people here first: + + docker compose logs api | grep -i "LDAP auth" + + Configuration only — it opens no connection and binds nothing, so startup stays + fast and cannot be made to hang by an unreachable domain controller. Use + `ldap_auth.selftest()` for a reachability check; it validates the certificate + without binding, so it cannot contribute to a lockout either. + """ + log.info("%s", ldap_auth.describe()) + yield + + app = FastAPI( + lifespan=_lifespan, title="Work Package Suite API", docs_url="/api/docs" if _docs_enabled else None, redoc_url=None,