Commit Graph
25 Commits
Author SHA1 Message Date
james.maesandClaude Opus 5.5 0df7bb49e2 Merge origin/develop into feature/health-checks; pin QQQ 4.1.0-RC.8
RC.8 ships the central health kernel (QRun-IO/qqq#1207).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:15:11 -05:00
james.maesandClaude Opus 5.5 5cf668f8e3 feat(help): ship the qbit-authentik help bundle through QQQ help bundles, with coverage gate
Host-neutral help (deployment-specific facts removed), registered with
HelpBundles, gated by HelpBundleAssertions. QQQ 4.1.0-RC.7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:17:00 -05:00
james.maesandClaude Opus 5.5 f0648d1ef2 feat(help): guided empty states for every authentik table
Add help/qbit-authentik.yaml (QQQ help-bundle format) for the Identity app and
all 15 tables, applied by AuthentikHelp (STAND-IN for core #1198). API-backed
tables say rows are read live and what an empty list means. Add
AuthentikTablesGuardrailTest (used at runtime, explained, documented).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:44:21 -05:00
james.maesandClaude Opus 5.5 24a97823a9 docs: Authentik health checks
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:55:18 -05:00
james.maes 65aa816660 Merge remote-tracking branch 'origin/develop' into feature/token-scopes 2026-10-14 07:57:21 -05:00
james.maes 89fcdec2ab Merge pull request #27 from QRun-IO/feature/guardrails-rc6
build: QQQ 4.1.0-RC.6, core table feature permissions, guardrails (#20)
2026-10-14 07:51:11 -05:00
james.maesandClaude Opus 5.5 02c18de8c1 build: QQQ 4.1.0-RC.6, real table feature permissions, guardrails enforced
Move to the released 4.1.0-RC.6, which includes core
TableFeaturePermissionsMetaData (QRun-IO/qqq#1136). The policy catalog
discovers features through core only; the test-only injection overload
is removed. The policy feature tests register real metadata instead of
the synthetic catalog, so the expired STAND-IN marker is gone.

Adopt qbit-build-parent 2.1.0 with the guardrails enforced and strict
raw-route mode.

Closes #20 (architecture review QRun-IO/qqq#1150, finding F19).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 07:43:51 -05:00
james.maes eb74cd12db Merge remote-tracking branch 'origin/feature/token-management' into feature/token-scopes 2026-10-14 07:21:06 -05:00
james.maes 9dfef79929 Merge remote-tracking branch 'origin/develop' into feature/token-management
# Conflicts:
#	CHANGELOG.md
2026-10-14 07:20:40 -05:00
james.maesandClaude Opus 5.5 7f8e742120 feat(tokens): scopes, last used, reminders and admin views (DESIGN.md section 13, phase 6c)
- Scopes: Create Token takes read only, apps and tables when
  tokenScopesEnabled (validated against the instance and the operator's
  own access). AuthentikTokenScopes reads the signed qqq_token claim in
  both mappers (and AuthentikTokenScopes.apply for hosts with their own
  customizer): no row = unscoped, deleted token = no permissions, any
  verification failure = no permissions. STAND-IN for core
  QCredentialScope (qqq#1168, RC.7): permissions are intersected; the
  core scope is attached by reflection when present (empty dimension =
  null = unrestricted).
- Last used from Authentik login events (method token, 90 days, cached
  5 minutes); createdAt and scopeSummary from the scope row.
- Daily expiry reminders (7 days, 1 day) through the optional Alerts
  producer, deduplicated on the scope row; guard-bypass alert.
- Admin widget on the app home (authentik.tokens.manage): by owner, by
  age, unused for tokenUnusedDays, deactivated owners; bulk Revoke Token
  on the list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:26:53 -05:00
james.maesandClaude Opus 5.5 26a449c9e6 test(tokens): contract tests on Authentik 2026.8.3 and service-account lifetime on the user
- TokenContractIT (9 tests) against the production token blueprint
  (verbatim copy of cluster-gitops kof22-qqq-tokens.yaml): caller-owned
  create, PATCH defaults, expiry cap (brand, groups, user), token
  exchange as the user via the broker, the deny policy for protected
  users, the qqq_token claim (profile only), service_account + set_key +
  PATCH, the detective alarm, and the qbit lifecycle with the one-time
  key absent from audit rows and events.
- Findings now in DESIGN.md: service_account and set_key need add_token
  (safe: a non-superuser create is always the caller's); set_key events
  use the model reference shape.
- Service-account lifetime cap is a user attribute
  (serviceAccountTokenLifetime, default days=365), not a group.
- Test fixtures carry the production role; SuperuserFloorIT accepts the
  400 from /recovery/ with reset_user_password (the qbit never calls it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:59:00 -05:00
james.maesandClaude Opus 5.5 de7c015081 feat(tokens): access-token management (DESIGN.md section 13, phase 6b)
TOKENS feature, on by default with writes (AUTHENTIK_QBIT_TOKENS=false
opts out). Create, rotate, revoke and extend tokens, and create service
accounts, as record and table actions with the standard Review/Result.

- People's tokens are created as that person through the token broker
  (RFC 8693 exchange); service accounts through service_account, set_key
  and PATCH (always intent, user and expires, with a race guard).
- Guardrails: protected owners (superusers, authentik Admins,
  identity-admins, qqq.io/protected, the qbit account, configurable
  groups), self-only for people, required capped expiry, optional
  two-person approval for service-account tokens.
- One-time key via core one-time process output (stand-in until
  qqq#1162 ships; refuses at Review when unavailable), never in tables,
  audit, events, logs or process state; usage text from config.
- authentik_token_scope table; authentik.tokens.manage permission.
- In-memory Authentik models the 2026.8.3 token semantics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:58:46 -05:00
james.maes 38d9776f25 Merge remote-tracking branch 'origin/develop' into feature/arch-rc5
# Conflicts:
#	CHANGELOG.md
2026-10-13 20:18:53 -05:00
james.maesandClaude Opus 5.5 d138c5c911 chore(deps): build on QQQ 4.1.0-RC.5
Move the core baseline from 4.1.0-RC.2 to the released 4.1.0-RC.5. No
source changes were needed. Comments now reflect core's opt-in permission
enforcement in 4.1. The synthetic feature-permission catalog in tests is
tagged STAND-IN(core=QRun-IO/qqq#1136, removeBy=4.1.0-RC.6), because
TableFeaturePermissionsMetaData is on develop but not in RC.5.

Refs #20 (architecture review QRun-IO/qqq#1150, finding F19).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:51:47 -05:00
james.maesandClaude Opus 5.5 d9431e7e6b docs(spec): access tokens as QQQ's personal access tokens (revision 6)
Owner decisions (2026-10-13) 23-30: tokens managed in the app and stored
in Authentik, shown once, self-service through Preferences, on by default
with an opt-out, guardrails (a)-(f), optional per-token scopes enforced
through core QCredentialScope (qqq#1153), reminders, last used, admin
views and bulk revoke.

Decision 30 records the owner-approved mechanism revision: Authentik
2026.8.3 always creates a token for the caller unless it is a superuser,
so people's tokens are created as that person through RFC 8693 token
exchange (token broker with a deny policy), service accounts through
service_account, set_key and PATCH. The qbit role gains change_token and
set_token_key and never holds add_token or view_token_key.

Adds docs/ACCESS-TOKENS.md ("Using access tokens").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:42:28 -05:00
james.maesandClaude Opus 5.5 d3b740785f feat(policy): grant table feature permissions (analyze, comment, ...)
The catalog lists each table's feature permissions, read generically from
PermissionsHelper.getAllAvailablePermissions (core
TableFeaturePermissionsMetaData), as a features array per table resource.
Policies grant them by name; the * wildcard still means the five
standard actions only, so existing policies grant nothing new.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:30:15 -05:00
james.maesandClaude Opus 5.5 a79f2d2d4b feat(crud): phase 5c full user and group CRUD
- Edit User: username (validated, unique) and other attributes (JSON object;
  qqq.io/ and goauthentik.io/ keys kept and refused; blank keeps them; needs
  authentik.audit.read, which also gates showing them). Edit Group: other
  attributes the same way.
- Delete User and Delete Group behind the new DELETE feature (off by default;
  Authentik delete_user/delete_group): typed confirmation, never yourself, a
  protected account, a superuser group or the last identity administrator; a
  group needs no members and no child groups; its policy row goes with it.
- Tests on the in-memory Authentik and a delete contract test on 2026.8.3;
  README, STATUS, CHANGELOG, CLAUDE.md invariant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:56:23 -05:00
james.maesandClaude Sonnet 5.5 645f782e42 fix(security): bootstrap operators keyed on immutable ids, never email
Users can change their own email in Authentik and emails are not unique, so
bootstrapOperators (emails) let a user become an identity operator by setting
their email to the owner's. Replaced by bootstrapOperatorSubjects: token sub
(email-looking subs ignored) or, with a verified email, the single active user
with that email whose pk or uuid is configured. Emails in config fail validation.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 17:02:09 -05:00
james.maesandClaude Sonnet 5.5 ebd56d6ca4 feat: write contract tests, docs, status; recovery email prerequisites
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-12 16:54:45 -05:00
james.maesandClaude Opus 5.5 338b591a7f fix(naming): rename identity.* permissions to authentik.* (owner naming rule)
Permissions follow the owner's full-name rule (2026-10-12; spec #1065
amended in 8ee607610): identity.users.read -> authentik.users.read, etc.,
same suffixes; the wildcard is authentik.*.

- IdentityPermission names and WILDCARD renamed; @Deprecated
  LEGACY_PREFIX, LEGACY_WILDCARD and getLegacyShortName() keep the old
  names for upgrade scripts. Legacy names are never granted: the resolver
  reports them as unknown and the drift report warns on them.
- UPGRADE.md: rename table, update qqq.io/permissions group attributes
  (UI or blueprint) before deploying, sample SQL for host-stored grants,
  sign users out afterwards.
- Code comments, drift messages, test fixtures, the test bootstrap
  blueprint, README, CHANGELOG and docs/DESIGN.md use authentik.*.
  The identity-admins group and the "Identity" app label are unchanged.
- Tests: IdentityPermissionTest (names, session form, legacy names not
  resolved), resolver test that legacy grants are unknown and grant
  nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:29:15 -05:00
james.maesandClaude Opus 5.5 ecad3e2e5b fix(naming): snake_case authentik_ table names (owner naming rule)
Table names are now lowercase snake_case prefixed with the qbit name:
authentik_user, authentik_group, authentik_group_member,
authentik_session, authentik_event (were authentikUser, ...).

- AuthentikQBitConfig.applyTablePrefix(name) = snake(prefix) + "_" +
  snake(name), plus a toSnakeCase helper. applyPrefix keeps the camelCase
  join for apps, widgets, the backend and processes (authentikAdmin,
  authentikApi), as spec #1065 says.
- @Deprecated AuthentikTables.LEGACY_*_TABLE_NAME constants hold the old
  names for upgrade scripts.
- UPGRADE.md covers stored references (permissions, saved views, audit
  table names, links). API-backed tables, so no DDL.
- README, CHANGELOG and docs/DESIGN.md use the new names.
- Tests: snake helper, prefix composition (default, empty, custom
  camelCase prefix), producer and contract tests on the new names.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:16:37 -05:00
james.maesandClaude Opus 5.5 941ab10f0f docs(readme): add Adopting in another app; neutral examples (#3) (#5)
* docs(readme): add Adopting in another app; neutral examples (#3)

Adds the adoption table proposed in #3, verified against the config and
metadata producers, linking the QQQ adoption checklist. README and Javadoc
examples use neutral names (my-app-admin, my-app-prod); the kof22 test
blueprints are described as one host's example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* docs(changelog): record the adoption section (#3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:20:00 -05:00
james.maesandClaude Opus 5.5 75c66a8f8d Phase 1: read-only MVP (memberships, sessions, events, effective permissions, drift report, mapper) (#4)
* feat: phase 0 foundations (read-only client, contract tests on Authentik 2026.8.3)

- AuthentikQBitConfig (validated, secrets resolved by the qbit, never
  serialized or printed) and AuthentikQBitProducer (API backend, read-only
  authentikUser / authentikGroup tables, Identity app).
- AuthentikActionUtil on qqq-backend-module-api: bearer auth, page/page_size
  paging with pagination.count, key lookups by GET, server-side user scope,
  AuthentikFilterMapper (rejects whatever Authentik cannot evaluate),
  10 rps token-bucket throttle, 429/5xx retries, error mapping, and logging
  that never carries the token, query strings, successful GET bodies or
  sensitive bodies (AuthentikRedactor).
- AuthentikGuard: identity.* read checks, sensitive fields behind
  identity.audit.read, all writes refused in this release, protected-account
  detection.
- Pinned Authentik 2026.8.3 schema, SchemaDriftTest / SchemaDriftIT over
  the API surface used, and a weekly drift workflow against new releases.
- Testcontainers harness (Authentik server + worker, Postgres, Mailpit) with
  the kof22-shaped blueprints, fixture recorder, and contract tests (b), (c),
  (d), (h) plus the access-check, sign-in policy, webhook and token findings.
- Owner checklist, rehearsal script and blueprint lint for the cluster-gitops
  migration (KofTwentyTwo/cluster-gitops#72).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: phase 1 read-only MVP (memberships, sessions, events, effective permissions, drift report)

- authentikGroupMember: virtual membership table served from a group
  (include_users), a user (groups_obj) or a bounded sweep; filters by group
  pk/name, user pk/username or key; user scope checked per member.
- authentikSession and authentikEvent, behind identity.audit.read.
- GroupGraph + EffectivePermissionResolver: groups and inherited parents to
  QQQ permissions; identity.* and catalog wildcards expanded, unknown names
  reported and never granted. Groups show inherited superuser status and
  effective permissions; users show effective permissions.
- Identity drift report widget (identity.admin): version, blueprint status,
  service-account shape and self-minted tokens, break-glass, last identity
  admin, qqq.io/* attribute validity, and service-account changes to
  protected or superuser objects.
- Contract tests on Authentik 2026.8.3 (real session via the flow executor;
  the drift report flags SuperuserFloorIT's lockout probes) and recorded
  fixtures for the new unit tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: group permission mapper; harden phase 0/1 after code review

- AuthentikGroupPermissionMapper: token groups claim -> QQQ permissions via
  the group graph and resolver; static fallbacks and bootstrapOperators;
  Authentik unreachable grants only the fallbacks, never everything.
- Review fixes (general and silent-failure reviews):
  - plain-text-only search, because Authentik's QLSearch could reach
    sensitive fields
  - thread-safe group graph
  - a failed group-graph read shows "Unavailable", not empty permissions
  - malformed qqq.io/* attributes are reported by the drift report
  - operator filters may not widen the user scope; membership rows are
    always scope-checked
  - a missing pagination object is an error
  - sorting by the key falls back to the default; strict booleans; ".."
    keys refused
  - https required except loopback / allowInsecureHttp; groupScopeNamePrefix
    refused until implemented
  - drift: unrunnable checks are ERROR "result unknown"; service-account
    tokens filtered server-side, zero visible is a warning; deleted or
    vanished targets flagged; only human users count as identity admins;
    break-glass re-checked client-side
  - redact error bodies before flattening; version check retried after a
    failed fetch
- rehearse-phase0.sh: portable under Git Bash, waits for both blueprints,
  derived is_superuser; passed on a synthetic production-like dump.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 06:08:37 -05:00
james.maesandClaude Opus 5.5 6afe1b045a chore: scaffold qbit-authentik (build, CI, docs, design)
Set up from qbit-template-extension with the build used by
qbit-sql-workbench and qbit-quick-search: qbit-build-parent 2.0.0 with the
qqq 4.1.0-RC.2 BOM profile, Maven wrapper, Testcontainers ITs under
failsafe, JaCoCo gate, and the qqq-orb 0.6.8 CircleCI workflows (develop
snapshots, release/* RCs, v* releases). docs/DESIGN.md is the approved spec
(QRun-IO/qqq#1065). README, CLAUDE.md and AGENTS.md describe the layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 19:54:03 -05:00
james.maes f1e73d3bcf Initial commit 2026-10-11 19:01:09 -05:00