Repository navigation
Conversation
Critical fixes (were exploitable): - Fix wrong Discourse API field names: silenced/suspended → silenced_till/suspended_till (old fields returned None, allowing silenced/suspended users through) - Fix RTBF email domain: @example.invalid → @anonymized.invalid (verified in source) - Remove RTBF username/name heuristics (false positives); email domain only - Decouple is_staff from Discourse admin flag; use dedicated staff_group - Add require_2fa=true to SSO request for protocol-layer 2FA enforcement - Scope team queries to configured organizer (Team names not globally unique) High-priority hardening: - All API errors now fail secure (no silent continue with defaults) - API key required; backend invisible without it - Add nonce time expiry (10 min) alongside single-use pop - Handle JSONDecodeError on API response - Parse resp.json() once (was called twice) - Add startup config validation (HTTPS, secret length, host_prefix, template) - Warn on staff_group collision with Discourse automatic groups Spec & docs: - SPECIFICATION.md v2: terminology section, rejection matrix, verified assumptions (cross-referenced against Discourse and pretix source), all 5 design decisions resolved, deployment checklist added - FUTURE_WORK.md: security hardening, robustness, feature ideas - Add requests to pyproject.toml dependencies Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Team template: default changed from "Ansible Meetup Staff - {city}"
to "Ansible Meetup Organisers - {city}" to match main project.
Config validation: host_prefix empty check moved to backend.py
_config_errors so backend becomes invisible (matching spec §5.1).
Enrichment API: raw_data.get('user', raw_data) simplified to raw_data
(Discourse Admin API uses root: false, returns user object directly).
Logging: R5 nonce rejection now distinguishes missing vs mismatch vs
expired with age details. R7 2FA rejection now includes username.
i18n: verbose_name wrapped in gettext_lazy().
Version bump: 1.0.0 → 2.0.0 (matches spec version).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- team_template: "Staff" → "Organisers" throughout (7 occurrences) - Step 9: document root: false (no wrapper needed), add warning about second_factor_enabled suppression in SSO-enabled Discourse - R5: split into R5a/R5b/R5c for missing/mismatch/expired with age - R7: add username to log fields - Section 5.1: host_prefix validation now disables backend - Deployment checklist: add discourse_connect_provider_claims check Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| | `host_prefix` | No | `meetup-host` | Group name prefix identifying meetup hosts. **Must not be empty.** | | ||
| | `staff_group` | No | `meetup-admin` | Discourse group name that grants pretix `is_staff` (exact match, case-insensitive). **Must not collide with Discourse automatic group names** (`staff`, `admins`, `moderators`, `trust_level_*`, `everyone`, etc.). | | ||
| | `team_template` | No | `Ansible Meetup Organisers - {city}` | Python format string. **Must contain `{city}`.** | | ||
| | `enforce_2fa_privileged` | No | `true` | Require 2FA for privileged users. Enforced at two layers: DiscourseConnect protocol (`require_2fa`) and enrichment API (`second_factor_enabled`). | |
There was a problem hiding this comment.
We should simplify 2FA. If we miss configured something we could have a subtle access bug here.
- 2FA is a hard requirement for
organisersandstaff - No check for the rest
- No configuration option
| | `api_username` | No | `system` | Discourse username for API requests. | | ||
| | `organizer` | Yes | — | Pretix organizer slug. Team queries are scoped to this organizer. Required because pretix Team names are not globally unique. | | ||
| | `host_prefix` | No | `meetup-host` | Group name prefix identifying meetup hosts. **Must not be empty.** | | ||
| | `staff_group` | No | `meetup-admin` | Discourse group name that grants pretix `is_staff` (exact match, case-insensitive). **Must not collide with Discourse automatic group names** (`staff`, `admins`, `moderators`, `trust_level_*`, `everyone`, etc.). | |
There was a problem hiding this comment.
Hard code staff_group (meetup-staff)
We don't need this to be configurable, prevent miss configuration that would give everybody admin (and therefore being able to see all real names & email addresses)
Also, I think we are inconsistent between staff and admin, need to check what term Prefix prefers.
| return redirect(reverse(_LOGIN_URL)) | ||
|
|
||
| # Step 6: Group parsing | ||
| groups_set = {g.strip() for g in parsed_sso.get('groups', '').split(',') if g.strip()} |
There was a problem hiding this comment.
Do we want to pre-filter this to only include meetup-organisers- and meetup-staff?
| API_KEY = config.get('discourse_auth', 'api_key', fallback='') | ||
| API_USER = config.get('discourse_auth', 'api_username', fallback='system') | ||
| HOST_PREFIX = config.get('discourse_auth', 'host_prefix', fallback='meetup-host').lower() | ||
| STAFF_GROUP = config.get('discourse_auth', 'staff_group', fallback='meetup-admin').lower() |
There was a problem hiding this comment.
Hardcode staff group
| TEAM_TEMPLATE = config.get('discourse_auth', 'team_template', fallback='Ansible Meetup Organisers - {city}') | ||
| ENFORCE_2FA = config.get('discourse_auth', 'enforce_2fa_privileged', fallback='true').lower() == 'true' | ||
| API_TIMEOUT = int(config.get('discourse_auth', 'api_timeout', fallback='10')) | ||
| ORGANIZER_SLUG = config.get('discourse_auth', 'organizer', fallback='') |
There was a problem hiding this comment.
Hard code organizers prefix
| api_key = <discourse-admin-api-key> | ||
| api_username = system | ||
| organizer = ansible-meetups | ||
| host_prefix = meetup-host |
There was a problem hiding this comment.
This is the wrong term
Also needs validating it matches ^meetup-organisers-[a-z]+$
| @@ -0,0 +1,781 @@ | |||
| # Technical Specification: pretix-discourse-auth | |||
There was a problem hiding this comment.
Need to look at Prefix Session times, and set via scripts/provision_environment.py in ansible-community/meetup-pretix-discourse-tooling#1
| - User identity (email, username, display name) | ||
| - Group memberships (which cities a host manages, and whether they hold pretix staff access) | ||
| - Moderation status (silenced, suspended) | ||
| - 2FA enforcement (via `require_2fa` in the DiscourseConnect protocol) |
There was a problem hiding this comment.
I think this needs work, and likely simplification
- Remove all code related to 2FA as a configuration option
- At the time pretix establishes the session, Discourse MUST attest that the authentication satisfied the required second-factor policy (ie admin & organisers)
This Pretix plugin authenticates users through DiscourseConnect and grants only the Pretix access supported by verified Forum claims and Pretix-owned account state.
Access policy
meetup-organisers-{city-slug}claims listed in the plugin's explicit city-to-team mapping grant membership in the corresponding city organizer team. A missing or ambiguous mapped team blocks that organizer login before user provisioning; ordinary attendee logins do not depend on the city-team configuration.Ansible Meetup Staffevent team, and Pretix accounts withis_staff=truemust have a signed DiscourseConnectconfirmed_2fa=trueassertion at session creation. The plugin always sendsrequire_2fa=true; there is no 2FA configuration switch.Ansible Meetup Staffand grant or revoke site-wideis_staff. The plugin ignores Forum staff groups and never changesis_staff.Security and maintenance