Skip to main content

Reconciliation audit for Alliance Auth's Discord integration.

Project description

aa-discord-audit

Reconciliation audit for Alliance Auth's Discord integration. Compares the actual role assignments in the configured Discord guild against the state expressed in Alliance Auth (Groups + State per user) and closes the gap through which moderators can hand-assign AA-named roles to users AA does not know about.

Safety posture

The audit is safe by default:

  • The first run after install is locked to dry-run regardless of operator settings. Releasing the lock requires an explicit InitialAuditAcknowledgement (admin or shell only).
  • The default policy is report for every category — destructive actions are opt-in.
  • Audit-trail rows (AuditRun, AuditFinding, AuditInvocation, ConfigChangeLog) are append-only at the manager and instance layers; bulk update() / bulk_update() are blocked.
  • Webhook URLs are treated as credentials and redacted from logs, exception messages, and persisted argv.

How it works

Each guild member is classified into one category:

Category Meaning
unknown_guest Discord member AA knows nothing about
linked_no_perm Identity known to AA but lacks discord.access_discord
bot_filtered Configured bot account — never acted upon

The operator maps each category to one action:

Action Behaviour
report Record the finding; no Discord-side change
strip Remove AA-managed roles
strip_kick Remove AA-managed roles, then kick from guild

The mapping is the AA_DISCORD_AUDIT_POLICY setting; per-group and per-state overrides nest inside each category.

Installation

pip install aa-discord-audit

In your Auth local.py:

INSTALLED_APPS += ["aa_discord_audit"]

MIDDLEWARE += [
    "aa_discord_audit.current_user.CurrentUserMiddleware",
]

Then run migrations:

python manage.py migrate aa_discord_audit

The CurrentUserMiddleware is mandatory — apps.ready() raises ImproperlyConfigured if it is missing. It is what lets the ConfigChangeLog signal handler attribute admin edits to a real user instead of <system>.

Quick start

  1. Grant aa_discord_audit.run_audit to the operator role that runs audits.

  2. Run a dry-run audit:

    python manage.py audit_discord_roles --action report
    
  3. Review findings under Discord Audit → Audit runs in the Auth dashboard.

  4. Release the first-run lock through the admin: create an InitialAuditAcknowledgement row (requires aa_discord_audit.acknowledge_initial_audit).

  5. Re-run with the destructive action of your choice when ready.

Permissions

Codename Gates
aa_discord_audit.run_audit management command, beat task, run delete
aa_discord_audit.acknowledge_initial_audit release the first-run dry-run lock
aa_discord_audit.manage_discord_identity DiscordIdentity admin
aa_discord_audit.manage_role_exception ManagedRoleException admin
aa_discord_audit.manage_protected_member ProtectedDiscordMember admin
aa_discord_audit.manage_bot_account_uid BotAccountUid admin
aa_discord_audit.manage_finding_override FindingActionOverride admin
aa_discord_audit.view_auditrun (and friends) read-only audit-trail in the Auth dashboard

The manage_* codenames are split per blast radius so a junior with manage_bot_account_uid cannot also defang the audit by editing ManagedRoleException.

Settings

All settings are optional. Defaults are safe.

# Action policy. Bare-string form below is shorthand for
# {"default": "<action>"}; use the nested form for per-group / per-state
# overrides keyed by AA group name and state name.
AA_DISCORD_AUDIT_POLICY = {
    "unknown_guest":  "report",
    "linked_no_perm": "report",
    # "linked_no_perm": {
    #     "default":  "strip",
    #     "by_state": {"Guest": "report"},
    #     "by_group": {"Directors": "report"},
    # },
}

# AA-notify fan-out to permission holders.
AA_DISCORD_AUDIT_NOTIFY_ADMINS = True

# Discord webhook for run summaries. Treat as a credential.
AA_DISCORD_AUDIT_WEBHOOK_URL = None

# uids skipped as bot accounts (in addition to the BotAccountUid admin
# table).
AA_DISCORD_AUDIT_BOT_UIDS = []

# Auto-discover bot accounts by Discord nickname heuristics. Off by
# default: the explicit BotAccountUid table is the recommended path.
AA_DISCORD_AUDIT_AUTO_DISCOVER_BY_NICKNAME = False

# Retention. 0 disables pruning; the validator refuses 0 unless the
# acknowledged flag below is also set.
AA_DISCORD_AUDIT_RUN_RETENTION_DAYS = 180
AA_DISCORD_AUDIT_RETENTION_OPT_OUT_ACKNOWLEDGED = False

# Per-run deadline. The Celery task's soft_time_limit follows.
AA_DISCORD_AUDIT_RUN_DEADLINE_MINUTES = 60

# Rolling 24h rate limit on accepted audit triggers per user.
# DISABLED is an opt-out gate; refuses to take effect unless
# explicitly toggled.
AA_DISCORD_AUDIT_RUN_RATE_LIMIT_PER_DAY = 6
AA_DISCORD_AUDIT_RUN_RATE_LIMIT_DISABLED = False

# Discord webhook delivery tuning.
AA_DISCORD_AUDIT_WEBHOOK_TIMEOUT = 5
AA_DISCORD_AUDIT_WEBHOOK_MAX_RETRIES = 3

# Opt-in: bulk PATCH role strip. Faster on large guilds; off by default
# while we collect operator feedback on Discord-side rate-limit shape.
AA_DISCORD_AUDIT_USE_BULK_ROLE_STRIP = False

Management commands

Command Purpose
audit_discord_roles Primary entry point. --action {report,strip,strip_kick}.
audit_discord_roles --resume <run_id> Re-walk PENDING findings of an existing run.
audit_discord_roles --abandon <run_id> Mark a stuck run as ABANDONED.
audit_discord_roles --diff <run_id> Compare current state to a historical run.
audit_discord_roles --explain <member_id> Per-member classification (read-only).
audit_discord_roles --policy-preview <json> Project a hypothetical policy.
audit_discord_roles --from-fixture <path> Replay against a JSON snapshot.
audit_benchmark Synthetic-load sizing benchmark (see docs/performance.md).
prune_audit_runs Retention pruning.

Operator dashboard

Mounted under the Auth main nav as Discord Audit. Read-only views:

  • Audit runs — list + per-run detail with the per-finding table.
  • Per-finding explain — classification, resolved action, and which override layer (per-uid override, per-group / per-state policy, ProtectedDiscordMember, first-run lock) decided the outcome.
  • Invocations — every CLI / beat trigger including refused attempts. Surfaces rate-limit usage.
  • Config change log — every operator edit to the operator-managed config tables. The audit-the-auditor trail.

Documentation

  • docs/runbook.md — operator runbook: bot Discord permissions, pre-flight checklist, releasing the first-run lock, incident playbooks, diagnostic toggles.
  • docs/performance.mdaudit_benchmark reference numbers and sizing implications.

Development

make dev         # uv sync --all-groups + pre-commit install
make test        # uv run nox -s tests
make lint        # uv run nox -s lint
make typecheck   # mypy + basedpyright
make coverage    # term + html + xml report
make package     # uv build

Toolchain is uv-only. Line length is 79 (Python) / 120 (Markdown).

Requirements

  • Python 3.10–3.13
  • Django 4.2
  • Alliance Auth 4.x

Translations

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

aa_discord_audit-0.1.0a3.tar.gz (145.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

aa_discord_audit-0.1.0a3-py3-none-any.whl (177.3 kB view details)

Uploaded Python 3

File details

Details for the file aa_discord_audit-0.1.0a3.tar.gz.

File metadata

  • Download URL: aa_discord_audit-0.1.0a3.tar.gz
  • Upload date:
  • Size: 145.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for aa_discord_audit-0.1.0a3.tar.gz
Algorithm Hash digest
SHA256 0a1b6196dcc96ed7fda6f4e4fb9f01bae1445cef0b61ab152a9c970182d820c8
MD5 1ec1e671e73d9bb7fedda43ab18d39b8
BLAKE2b-256 110ebf9465c38eac97bf6da175416e8521d44cf132e0b98b4ca68624fa03fc92

See more details on using hashes here.

File details

Details for the file aa_discord_audit-0.1.0a3-py3-none-any.whl.

File metadata

  • Download URL: aa_discord_audit-0.1.0a3-py3-none-any.whl
  • Upload date:
  • Size: 177.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for aa_discord_audit-0.1.0a3-py3-none-any.whl
Algorithm Hash digest
SHA256 68ce7872d67f4a19ec7b6aa9b82e76f7212341f4758a72d98c917f2eefaff500
MD5 b443c8510c504be69930b9d70d90f1fe
BLAKE2b-256 79dbe9c04a3ee54595c1d7a2994c5bc5ea5313f3df6d967e6d51b33e2caffe26

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page