docs(readme): add Adopting in another app; neutral examples (#3) #5

Merged
KofTwentyTwo merged 2 commits from docs/GH-3-adopting-in-another-app into develop 2026-10-12 11:20:00 +00:00
6 changed files with 30 additions and 9 deletions

No files matched your search

+3
View File
@@ -4,6 +4,9 @@ All notable changes to this project are documented here. Format: [Keep a Changel
## [Unreleased]
### Documentation
- README: "Adopting in another app" section (coordinates, configuration, secrets, permissions, limitations) linking the QQQ adoption checklist; examples use neutral names instead of one host's (#3).
### Added (phase 1, read-only MVP)
- Tables: `authentikGroupMember` (virtual membership table: by group, by user, by key, or a bounded sweep), `authentikSession` and `authentikEvent` (both need `identity.audit.read`).
- `GroupGraph` and `EffectivePermissionResolver`: groups, including inherited parents, become QQQ permissions. `identity.*` and catalog wildcards are expanded; unknown names are reported, never granted. Groups show inherited superuser status and effective permissions; users show effective permissions.
+21 -3
View File
@@ -21,8 +21,8 @@ Every write is refused by `AuthentikGuard` in this release.
AuthentikQBitConfig config = new AuthentikQBitConfig()
.withBaseUrl("https://auth.example.com") // host root; /api/v3 is appended
.withApiToken("${env.AUTHENTIK_API_TOKEN}") // service-account token, resolved by the qbit
.withAppKey("kof22-admin") // service account: qqq-kof22-admin-idadmin
.withApplicationsInScope(List.of("kof22-website-prod"));
.withAppKey("my-app-admin") // service account: qqq-my-app-admin-idadmin
.withApplicationsInScope(List.of("my-app-prod"));
new AuthentikQBitProducer().withQBitConfig(config).produce(qInstance);
```
@@ -44,7 +44,25 @@ The full `identity.*` set is in DESIGN.md section 5.2.
### Authentik side
The qbit's API token belongs to a dedicated Authentik service account whose role holds only the read permissions it needs. The bootstrap blueprint that creates it lives in the host's GitOps repo. The kof22 version is [`src/test/resources/blueprints/kof22-identity-bootstrap.yaml`](src/test/resources/blueprints/kof22-identity-bootstrap.yaml), which the contract tests exercise.
The qbit's API token belongs to a dedicated Authentik service account whose role holds only the read permissions it needs. The bootstrap blueprint that creates it lives in the host's GitOps repo. One host's version, which the contract tests exercise, is [`src/test/resources/blueprints/kof22-identity-bootstrap.yaml`](src/test/resources/blueprints/kof22-identity-bootstrap.yaml); treat it as an example, not something to apply as is.
## Adopting in another app
See also the QQQ [adoption checklist](https://github.com/QRun-IO/qqq/blob/develop/docs/migration/adoption-checklist.adoc) (section 5 covers this QBit).
| Item | What to do |
|---|---|
| Coordinates | `com.kingsrook.qbits:qbit-authentik`. No release candidate or release is on Maven Central yet; CircleCI publishes snapshots from `develop`. Build from source or use a snapshot until `0.1.0` ships. |
| QQQ version | QQQ 4.1 (see `pom.xml` for the exact RC). Java 21. |
| Infrastructure | Authentik, contract-tested against 2026.8.3. The API must be reachable from the app at `baseUrl` (`/api/v3` is appended). |
| Authentik setup | A dedicated service account (default username `qqq-<appKey>-idadmin`) whose role holds only the read permissions the QBit needs, created by a bootstrap blueprint in the host's GitOps repository. `src/test/resources/blueprints/kof22-identity-bootstrap.yaml` is a worked example from one host, not something to apply as is. |
| Registration | `new AuthentikQBitProducer().withQBitConfig(config).produce(qInstance)`. Produces the `authentikApi` API backend, read-only tables (`authentikUser`, `authentikGroup`, `authentikGroupMember`, `authentikSession`, `authentikEvent`), the drift report widget, and the `authentikAdmin` ("Identity") app, which the host may nest. |
| Required config | `baseUrl`, `apiToken`, `appKey` (lower-case letters, digits, dashes; chooses the service-account name), `applicationsInScope` (Authentik application slugs). Optional: `userScopePathStartsWith`, `groupScopeNamePrefix`, `mirrorBackendName` (phase 1 `MIRROR`), `throttleRequestsPerSecond` (default 10). |
| Secrets / env | `apiToken` as a `${env.X}` reference (the example uses `AUTHENTIK_API_TOKEN`); `bootstrapOperators` defaults to `${env.AUTHENTIK_QBIT_BOOTSTRAP_OPERATORS}` and `webhookSecret` to `${env.AUTHENTIK_WEBHOOK_SECRET}`. Keep them in the host's secret store, never in config files. |
| Permissions | `identity.users.read` (Identity app and tables), `identity.audit.read` (sensitive fields and tables); the full `identity.*` set is in DESIGN.md section 5.2. Granted through the Authentik group attribute `qqq.io/permissions`, read by `AuthentikGroupPermissionMapper`. |
| Storage | None for the read-only line, except the mirror tables when `MIRROR` is enabled on `mirrorBackendName`. |
| Known limitations | Read-only: every write is refused by `AuthentikGuard` until phases 2-3. `RBAC`, `WEBHOOK` and `SNAPSHOT_EXPORT` are refused by config validation. |
| Upgrading | First release line; nothing to migrate. |
## Build
@@ -121,7 +121,7 @@ public class AuthentikQBitConfig implements QBitConfig
if(!StringUtils.hasContent(appKey) || !APP_KEY_PATTERN.matcher(appKey).matches())
{
errors.add("appKey is required: lower-case letters, digits and dashes, e.g. kof22-admin");
errors.add("appKey is required: lower-case letters, digits and dashes, e.g. my-app-admin");
}
if(tableNamePrefix != null && !tableNamePrefix.isEmpty() && !PREFIX_PATTERN.matcher(tableNamePrefix).matches())
@@ -23,8 +23,8 @@ import java.util.function.LongSupplier;
/*******************************************************************************
** Self-imposed client-side rate limit (DESIGN.md section 9.3). Authentik does
** not throttle authenticated API calls at all, and kof22's Authentik shares the
** website's Postgres, so the qbit limits itself: a token bucket refilled at
** not throttle authenticated API calls at all, and an Authentik may share the
** Postgres of other applications, so the qbit limits itself: a token bucket refilled at
** requestsPerSecond, holding at most burst tokens. acquire() blocks until a
** token is available. Shared by all requests of one qbit instance.
*******************************************************************************/
@@ -32,8 +32,8 @@ import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/*******************************************************************************
** The "Identity" app (DESIGN.md section 8.1): the qbit's tables (and, from
** phase 1, its widgets), gated by identity.users.read. Hosts re-parent it, e.g.
** kof22 nests it under Admin; its name stays the same. Processes are only
** phase 1, its widgets), gated by identity.users.read. Hosts may re-parent it,
** for example under an Admin app; its name stays the same. Processes are only
** ever table or record actions, never navigation entries.
*******************************************************************************/
public class AuthentikAppMetaDataProducer extends QBitComponentMetaDataProducer<QAppMetaData, AuthentikQBitConfig>
@@ -44,7 +44,7 @@ import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
** Sign-in permissions from Authentik groups (DESIGN.md section 5.6). Use as
** (or extend in) the host's OAuth2 authentication customizer.
**
** 1. Group names come from the token's "groups" claim (kof22's profile
** 1. Group names come from the token's "groups" claim (a typical profile
** mapping emits direct groups only).
** 2. Each group is resolved with its ancestors through the cached group
** graph; the union of qqq.io/permissions becomes session permissions,