P0: identity layer - units, people, roles, invites, sessions
Adds identity.py (schema, capability map, scrypt passwords, invites, sessions, login throttle, boot seed) and auth.py (login, invite acceptance, account page). app.py gains two imports and one wiring block at EOF; no existing behaviour changes. Units are a table seeded from the site constants. Roles split: leader and member per unit in memberships, owner and admin site-wide in people.global_role, so a unit added later cannot under-grant an admin. tests/smoke_identity.py covers the rules that are invisible when wrong: single-use invites, reissue revoking the prior link, expiry, idle and absolute session bounds, throttling, and the capability split. 49 checks.
This commit is contained in:
+45
@@ -10,6 +10,8 @@ import store
|
||||
import admin_api
|
||||
import nearby
|
||||
import documents
|
||||
import identity
|
||||
import auth
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pack & Troop 73 - greenlanescouts73.org
|
||||
@@ -1037,3 +1039,46 @@ def join_post(parent_name: str = Form(...), email: str = Form(...), phone: str =
|
||||
except Exception:
|
||||
pass
|
||||
return RedirectResponse(url="/join?sent=1", status_code=303)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Identity (P0)
|
||||
# ---------------------------------------------------------------------------
|
||||
# Wired at the END of this module on purpose: auth.PAGE needs page(), which is
|
||||
# defined above, and keeping the whole attachment in one block means the P0
|
||||
# footprint inside this 1000-line file is one place to read and one place to
|
||||
# revert.
|
||||
|
||||
|
||||
def bootstrap_notify(url):
|
||||
"""Push the first owner invite to ntfy. Logged either way - the container
|
||||
log is the copy that survives a missed notification."""
|
||||
if not NTFY_BASE:
|
||||
return False
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if NTFY_TOKEN:
|
||||
headers["Authorization"] = "Bearer " + NTFY_TOKEN
|
||||
try:
|
||||
body = json.dumps({
|
||||
"topic": NTFY_TOPIC,
|
||||
"title": "greenlanescouts73.org owner invite",
|
||||
"message": "First admin account. Single use, expires in %d days.\n%s"
|
||||
% (identity.INVITE_TTL_DAYS, url),
|
||||
"tags": ["key"],
|
||||
"priority": 4,
|
||||
}).encode()
|
||||
rq = urllib.request.Request(NTFY_BASE, data=body, headers=headers)
|
||||
with urllib.request.urlopen(rq, timeout=5) as resp:
|
||||
return resp.status < 400
|
||||
except Exception as e:
|
||||
print("identity: bootstrap ntfy push failed: %s" % e, flush=True)
|
||||
return False
|
||||
|
||||
|
||||
identity.init()
|
||||
auth.PAGE = page
|
||||
app.include_router(auth.router)
|
||||
|
||||
_boot_url, _boot_minted = identity.bootstrap()
|
||||
if _boot_minted and _boot_url:
|
||||
bootstrap_notify(_boot_url)
|
||||
|
||||
Reference in New Issue
Block a user