Invite minting, invitee onboarding, and version footer #42

Manually merged
marvin8 merged 5 commits from feat/issue-22-invite-minting-onboarding into main 2026-10-17 08:35:46 +00:00
Collaborator

Closes #22
Closes #41

Invite minting (admin-only, /settings) — new Invites section: optional note, 1/7/30-day expiry (7 default), full https://…/invite/{code} URL shown once for copy-paste (rendered directly from the POST so the code never lands in history or access logs), plus a table of outstanding invites for re-copying. Route gated by require_admin; section hidden from non-admins.

Onboarding — redeeming an invite now signs the new user in immediately (session minted with the existing helpers, cookie set exactly as login does) and lands on /settings to connect Jellyfin — no forced second login. Expired/used/unknown invites render a friendly page telling the invitee to ask for a fresh link, replacing the bare JSON 410/404.

Footer — every page carries a footer with the running version (from installed distribution metadata), a link to the source, and a link to the docs for that exact version ({docs_base_url}/{version}/, matching the mike per-version layout). Two new settings: XIQUE_SOURCE_URL, XIQUE_DOCS_BASE_URL (README + docs/config.md updated).

Tests: 13 new tryke tests (minting gating/expiry/listing, sign-in on redeem, rendered 404/410 pages, footer); full uv run nox green (9 sessions).

Closes #22 Closes #41 **Invite minting (admin-only, `/settings`)** — new Invites section: optional note, 1/7/30-day expiry (7 default), full `https://…/invite/{code}` URL shown once for copy-paste (rendered directly from the POST so the code never lands in history or access logs), plus a table of outstanding invites for re-copying. Route gated by `require_admin`; section hidden from non-admins. **Onboarding** — redeeming an invite now signs the new user in immediately (session minted with the existing helpers, cookie set exactly as login does) and lands on `/settings` to connect Jellyfin — no forced second login. Expired/used/unknown invites render a friendly page telling the invitee to ask for a fresh link, replacing the bare JSON 410/404. **Footer** — every page carries a footer with the running version (from installed distribution metadata), a link to the source, and a link to the docs for that exact version (`{docs_base_url}/{version}/`, matching the mike per-version layout). Two new settings: `XIQUE_SOURCE_URL`, `XIQUE_DOCS_BASE_URL` (README + docs/config.md updated). Tests: 13 new tryke tests (minting gating/expiry/listing, sign-in on redeem, rendered 404/410 pages, footer); full `uv run nox` green (9 sessions).
Admins can now mint single-use invite links from a new section on
/settings: optional note, 1/7/30-day expiry (7 by default), and the full
/invite/{code} URL shown for handing to the invitee. Outstanding invites
(unused and unexpired) are listed so links can be re-copied without
minting duplicates. The route is gated by require_admin and the section
is hidden from non-admins.
Redeeming an invite now issues the new account's session on the spot and
lands on /settings to connect Jellyfin, instead of bouncing through
/login to retype the just-chosen password. Expired, already-used, and
unknown invites get a rendered page explaining what happened and asking
the invitee to request a fresh link, replacing the bare JSON errors.
🚸 Add version, source, and docs footer to every page
All checks were successful
/ gitleaks (pull_request) Successful in 44s
/ pr-review (pull_request) Successful in 3m21s
/ checks (pull_request) Successful in 5m44s
/ publish-container (pull_request) Has been skipped
/ deploy-docs (pull_request) Has been skipped
/ container (pull_request) Successful in 2m8s
6b990dae07
Every page now carries a footer naming the running version (read from
the installed distribution metadata) with links to the source
repository and the documentation for that exact version
(docs_base_url/version/, matching the mike per-version layout). Two new
XIQUE_* settings, XIQUE_SOURCE_URL and XIQUE_DOCS_BASE_URL, let forks
repoint the links.
forgejo-actions left a comment

WuMing

Found 1 issue(s). See inline comments below.

## WuMing Found **1** issue(s). See inline comments below.
@ -467,3 +501,2 @@
if user_id is None:
raise HTTPException(status_code=status.HTTP_410_GONE, detail="invite no longer usable")
return RedirectResponse("/login", status_code=status.HTTP_303_SEE_OTHER)
return _render_invite_unavailable(request, "expired", status.HTTP_410_GONE)

code [MEDIUM]

When redeem_invite returns None because the invite was already used (but still within its expiry window), the endpoint incorrectly renders the "expired" page instead of "used". The user would see a misleading message. The code should determine the correct reason (used vs. expired) – either by checking the invite state again before the fallback, or by returning "used" when user_id is None.

**code** [MEDIUM] When redeem_invite returns None because the invite was already used (but still within its expiry window), the endpoint incorrectly renders the "expired" page instead of "used". The user would see a misleading message. The code should determine the correct reason (used vs. expired) – either by checking the invite state again before the fallback, or by returning "used" when user_id is None. <!-- wuming:sha256:d1d392a4d7d3a1d265a3e477cbedc13b91d4f3bf1062fc8b651d3a3b74d85f63 -->
🐛 Say used, not expired, when an invite claim is lost
All checks were successful
/ gitleaks (pull_request) Successful in 13s
/ pr-review (pull_request) Successful in 2m43s
/ checks (pull_request) Successful in 4m4s
/ publish-container (pull_request) Has been skipped
/ deploy-docs (pull_request) Has been skipped
/ container (pull_request) Successful in 1m38s
52c30553ae
A redeem_invite claim can be lost because a concurrent request consumed
the invite, not because it expired. Re-fetch the invite after a lost
claim and classify the page from its actual state.
Author
Collaborator

src/xique/web/routes/auth.py

When redeem_invite returns None because the invite was already used (but still within its expiry window), the endpoint incorrectly renders the "expired" page instead of "used".

✅ Fixed in commit 52c3055 — after a lost claim the route re-fetches the invite and classifies the page from its actual state (used vs expired), with a race-simulation test pinning the used case.

[`src/xique/web/routes/auth.py`](https://forge.marvin8.zone/marvin8/xique/pulls/42#issuecomment-5959) > When redeem_invite returns None because the invite was already used (but still within its expiry window), the endpoint incorrectly renders the "expired" page instead of "used". ✅ Fixed in commit `52c3055` — after a lost claim the route re-fetches the invite and classifies the page from its actual state (used vs expired), with a race-simulation test pinning the used case.
forgejo-actions left a comment

WuMing

Found 2 issue(s). See inline comments below.

## WuMing Found **2** issue(s). See inline comments below.
@ -470,0 +503,4 @@
reason = "used" if invite is not None and invite["used_by"] is not None else "expired"
return _render_invite_unavailable(request, reason, status.HTTP_410_GONE)
request_settings: Settings = request.app.state.settings
session_token = new_session_token()

security [MEDIUM]

A09: Security Logging and Monitoring Failures: Successful invite redemption and auto-login is not logged. Log security events (user creation via invite, session issuance) for monitoring.

**security** [MEDIUM] A09: Security Logging and Monitoring Failures: Successful invite redemption and auto-login is not logged. Log security events (user creation via invite, session issuance) for monitoring. <!-- wuming:sha256:4e7e2ba699948657db04dba3fd08198b40999fc4e47b36372a49e9ed14a71e58 -->
@ -371,0 +470,4 @@
_invite_expiry(int(expires_days)),
cleaned_note,
)
minted_invite_url = f"{str(request.base_url).rstrip('/')}/invite/{code}"

security [MEDIUM]

A09: Security Logging and Monitoring Failures: Invite minting success is not logged. Log admin actions (minting code, expiry, note) for audit trails and abuse detection.

**security** [MEDIUM] A09: Security Logging and Monitoring Failures: Invite minting success is not logged. Log admin actions (minting code, expiry, note) for audit trails and abuse detection. <!-- wuming:sha256:a7f5182fb9cae066c891840b28fbd592375273a019bfeced4acb7d56ad3e81ce -->
📢 Audit-log invite minting and redemption
All checks were successful
/ gitleaks (pull_request) Successful in 15s
/ pr-review (pull_request) Successful in 1m8s
/ checks (pull_request) Successful in 4m13s
/ publish-container (pull_request) Has been skipped
/ deploy-docs (pull_request) Has been skipped
/ container (pull_request) Successful in 1m36s
b61c684372
Invite minting logs the acting admin, the chosen expiry, and the note —
never the secret code itself. Invite redemption logs the account
creation and session issuance. Rotation with configurable retention
lands with #23.
Author
Collaborator

src/xique/web/routes/auth.py

A09: Security Logging and Monitoring Failures: Successful invite redemption and auto-login is not logged. Log security events (user creation via invite, session issuance) for monitoring.

✅ Fixed in commit b61c684 — successful redemption logs the created username and that a session was issued, at INFO, with a test asserting the coverage. The session token and password are never logged.

[`src/xique/web/routes/auth.py`](https://forge.marvin8.zone/marvin8/xique/pulls/42#issuecomment-5964) > A09: Security Logging and Monitoring Failures: Successful invite redemption and auto-login is not logged. Log security events (user creation via invite, session issuance) for monitoring. ✅ Fixed in commit `b61c684` — successful redemption logs the created username and that a session was issued, at INFO, with a test asserting the coverage. The session token and password are never logged.
Author
Collaborator

src/xique/web/routes/auth.py

A09: Security Logging and Monitoring Failures: Successful invite redemption and auto-login is not logged. Log security events (user creation via invite, session issuance) for monitoring.

✅ Fixed in commit b61c684 — successful redemption logs the created username and that a session was issued, at INFO, with a test asserting the coverage. The session token and password are never logged.

[`src/xique/web/routes/auth.py`](https://forge.marvin8.zone/marvin8/xique/pulls/42#issuecomment-5964) > A09: Security Logging and Monitoring Failures: Successful invite redemption and auto-login is not logged. Log security events (user creation via invite, session issuance) for monitoring. ✅ Fixed in commit `b61c684` — successful redemption logs the created username and that a session was issued, at INFO, with a test asserting the coverage. The session token and password are never logged.
marvin8 approved these changes 2026-10-17 08:34:54 +00:00
marvin8 manually merged commit 04497e2608 into main 2026-10-17 08:35:46 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
marvin8/xique!42
No description provided.