# PSD Server Project B Authentik Integration Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Convert the accepted Project A installation to public OIDC mode, store owned work sessions in the existing Supabase database's `thoth_sessions` schema, and restore the established Aritmolab-sidebar user journey through the load balancer and Nginx. **Architecture:** Keep the same installation-descriptor path and Compose project so Project A Qdrant/Ollama volumes and bind state remain authoritative. Close ingress, snapshot Project A configuration, configure Authentik and Supabase, replace the protected installation configuration transactionally at the same paths, validate privately, then open the production route and sidebar link. **Tech Stack:** Authentik OAuth2/OIDC Authorization Code + PKCE, ThothII OIDC/session store, Supabase PostgreSQL schema migrations/RLS, Docker Compose, Nginx, load balancer, Aritmolab. --- ## Preconditions - Project A automated and human reports are PASS and explicitly owner-approved. - Application SHA, workspace SHA, images, Project A report digest, and rollback configuration match the accepted evidence. - The production route is closed before authentication/session-storage changes. - All Authentik operations use the installed version's API/OpenAPI contract. Official current references include [OAuth2/OIDC providers](https://docs.goauthentik.io/add-secure-apps/providers/oauth2/), [provider property mappings](https://docs.goauthentik.io/add-secure-apps/providers/property-mappings/), [application bindings](https://docs.goauthentik.io/add-secure-apps/applications/manage_apps/), and [blueprint export](https://docs.goauthentik.io/customize/blueprints/export); installed-version behavior wins over newer documentation. ### Task 1: Freeze Project A and close ingress **Files:** - Read: accepted Project A report - Create: protected Project B transaction root **Step 1: Verify exact Project A state** Run status, doctor, auth check, workspace inspect, vector inspect, Pi status/test, Git SHA, and image identity checks from Project A. Expected: all match the accepted report. **Step 2: Create a protected transaction root** Use `mktemp -d` under the survey-approved protected parent, mode `0700`. Record its path and do not place it in Git. **Step 3: Close production and temporary ingress** Keep or restore a maintenance response at the production ThothII route. Disable the optional Project A test route before changing authentication unless it is needed for a separately approved private preflight. Confirm neither route reaches ThothII. **Step 4: Stop and back up Project A** ```bash --installation backup \ --output /project-a-backup.tar --drain --installation stop ``` Verify the archive using the controller's manifest/checksum contract. Preserve a copy of the exact installation descriptor, env file, override files, auth directory, Nginx fragment, load-balancer route, Aritmolab sidebar file/revision, and relevant Authentik export metadata. ### Task 2: Decide exact Authentik names and roles **Files:** - Create: protected `authentik-change-manifest.yaml` **Step 1: Select the final public origin** Use the live survey result, not historical `.it`/`.com` assumptions. Record exactly one HTTPS origin and callback `/api/auth/oidc/callback`. **Step 2: Select exact groups** Default to dedicated `TOT Users` and `TOT Admin`. Reuse existing groups only if their membership semantics match and the owner approves. Record exact case-sensitive names. **Step 3: Define least privilege** Map user group → `user`, admin group → `admin`. Define a separate service account/token with only the installed Authentik permission needed to view exact group objects. No write, user-management, directory-administration, or superuser permission. **Step 4: Obtain owner approval of the manifest** The manifest contains object names, slugs, intended bindings, callback, scopes, grant types, credential destinations, and rollback action—but no secret values. Do not mutate Authentik before approval. ### Task 3: Export and prepare Authentik **Files:** - Create: protected pre-change Authentik export - Modify: Authentik objects named in the approved manifest **Step 1: Export relevant configuration** Use the installed version's supported blueprint/API export. A worker command such as `ak export_blueprint` is valid only if present in that version. Protect export mode `0600`; remember write-only provider secrets are not included, so backup their custody separately without printing. **Step 2: Verify API credential scope** Use a read-only call to list relevant groups/applications. Expected: administrative creation access for the setup identity and a distinct path for the future group-view service account. Stop if the credential is missing or ambiguous. **Step 3: Create or confirm exact groups** Create missing dedicated groups, or record approved existing group IDs. Do not bulk-copy LDAP or unrelated Authentik memberships. **Step 4: Create the group-catalog service account** Grant only exact group-view permission. Create its token through the approved protected-secret mechanism; write it directly to the ThothII secret destination without displaying it. **Step 5: Create the OIDC provider** Configure a confidential OAuth2/OIDC provider with the exact callback, issuer mode observed as appropriate, Authorization Code, PKCE support, and Device Code only when required for `tht auth check --interactive` and supported by the installed release. Do not enable implicit flow. **Step 6: Configure scopes and direct groups claim** Select `openid`, `profile`, and `email`. Inspect a disposable identity's decoded claim keys through a protected verifier; retain only a redacted shape. Expected: ID token includes direct non-empty `groups: [string, ...]`. If the installed default profile mapping already provides that exact claim, reuse it. Otherwise add a provider scope/property mapping under the requested `profile` scope that returns: ```python return {"groups": [group.name for group in request.user.ak_groups.all()]} ``` Verify the installed mapping merge semantics before activation. Do not add a custom unrequested scope because ThothII requests only `openid`, `profile`, and `email`. **Step 7: Create the Authentik application** Bind it to the provider. Configure display metadata according to local Aritmolab conventions. Do not use an Authentik proxy provider or Nginx forward-auth for ThothII. **Step 8: Create and store the client secret** Write the client secret directly into the protected ThothII secret bundle key `THT_OIDC_CLIENT_SECRET`. Store the service-account token as `THT_AUTHENTIK_API_TOKEN`. Never place either value in the change manifest, shell history, Compose environment, or evidence. ### Task 4: Prepare Supabase schema roles and backup **Files:** - Read: `harness/tht/migrations/sessions/001_schema.sql` - Read: `harness/tht/migrations/sessions/002_security.sql` - Create: protected Supabase backup/evidence - Create: runtime and migrator credential files **Step 1: Confirm the database/schema boundary** Expected: use the surveyed existing Supabase PostgreSQL database; clinical data remains in `datawarehouse`; application sessions use schema `thoth_sessions`; no new database is created. **Step 2: Back up database metadata/data consistently** Use the existing Supabase/PostgreSQL backup procedure before DDL. Record backup ID, timestamp, checksum, and restore command. Do not put a dump in the Git repository. **Step 3: Create or validate dedicated roles** Create one migrator login and one runtime login according to the migration contract. The runtime role must not be superuser, owner, BYPASSRLS, CREATEROLE, CREATEDB, or a member of DWH write roles. The migrator credential remains unavailable to core. **Step 4: Write protected credential files** Create separate mode-0640 runtime-password, migrator-password, and session-CA files with surveyed ownership. Do not use command-line password arguments. **Step 5: Confirm PostgREST exclusion before migration** Record the exact exposed schema list. Expected: `thoth_sessions` absent. If the system exposes all schemas implicitly, stop and resolve the boundary before migration. ### Task 5: Prepare the stable Project B installation configuration **Files:** - Modify at the same stable paths: operator env, installation descriptor, authentication directory - Create: reviewed session-server override copied from `deploy/compose.session-server.yaml.example` - Create: protected server-session workspace config copied from `deploy/workspaces/server-sessions.yaml.example` **Step 1: Preserve the Compose project name** The native controller derives the project name from the absolute installation-descriptor path. Keep that exact path. Do not point Project B at a second descriptor path, because that would create new Qdrant/Ollama named volumes instead of using the Project A accepted state. **Step 2: Stage Project B files beside the live files** Prepare new env/descriptor/override/auth inputs in the protected transaction root. Add the session DB host/port/existing database name/runtime role/migrator role/TLS mode and three secret source paths. Use `verify-full` where hostname/SAN permits; any `verify-ca` exception requires explicit survey evidence and owner approval. **Step 3: Add the server-session override** Copy the current example to a reviewed local file and add it to the existing stable descriptor's overrides before the Git transport override ordering required by the installation. Do not edit the tracked example. **Step 4: Replace local auth state transactionally** With the stack stopped, move the complete Project A auth directory into the protected transaction root, recreate an empty private directory at the same path/ownership/mode, and configure OIDC: ```bash --installation auth configure \ --mode oidc --public-url \ --issuer --client-id \ --authentik-base-url \ --user-group '' --admin-group '' ``` Expected: non-secret `auth.yaml` only; secrets resolved from the protected bundle. **Step 5: Atomically install staged path-only files** Use same-filesystem rename and preserve required ownership/mode. Keep the Project A originals in the transaction root. Run `update --check-only`; on failure restore the originals immediately. ### Task 6: Run and verify session migrations **Files:** - Modify through one-shot migrator: existing database schema `thoth_sessions` **Step 1: Validate migration rendering** ```bash --installation update --check-only ``` Expected: core and `session-migrate` resolve the same core image; core lacks migrator password; only the one-shot service sees it. **Step 2: Run migrations once** ```bash --installation sessions migrate --yes ``` Expected JSON: `"pending":[]` and `"drifted":[]`; `applied` may list `001` and `002` on first use. **Step 3: Run migration status/idempotency again** Run the same command. Expected: no new application and both pending/drifted remain empty. **Step 4: Verify database security** Through bounded catalog queries, prove forced RLS, policies on session tables, runtime role without BYPASSRLS/ownership/DDL, migrator absent from core, and no runtime privileges on unrelated schemas. **Step 5: Recheck PostgREST exclusion** Expected: `thoth_sessions` still absent from exposed schemas and REST endpoints cannot address it. ### Task 7: Validate Authentik and start privately **Files:** - Record: Project B protected evidence **Step 1: Run static configuration validation** Run `update --check-only` and redacted `auth status --json`. Expected: mode OIDC, exact public origin, issuer/client ID/group names, and no secret values. **Step 2: Start while public ingress remains closed** ```bash --installation start --installation status --installation auth check --json --installation doctor --json --installation pi test ``` Expected: OIDC discovery/issuer/JWKS, client-secret access, Authentik catalog token, exact mapped groups, PostgreSQL session storage, workspace, services, workflow, and Pi pass. **Step 3: Run interactive device check when supported** ```bash --installation auth check --interactive ``` Expected: approved identity completes Device Authorization and direct groups claim validates. If the installed provider does not support device flow, record PENDING rather than substituting a token. ### Task 8: Prepare Nginx, TLS, load balancer, and sidebar **Files:** - Modify only survey-approved ThothII Nginx fragment - Modify only survey-approved load-balancer route - Modify only exact Aritmolab sidebar source when its target must change **Step 1: Prepare direct-OIDC Nginx configuration** Follow `docs/install/reverse-proxy-nginx.md`, direct OIDC section. Required behavior: no `auth_request`, no callback rewrite, frontend loopback upstream, Host and HTTPS forwarded headers, HTTP/1.1, buffering/cache off, long SSE read timeout, and `X-Accel-Buffering: no`. **Step 2: Validate the managed certificate** Expected: SAN matches final hostname, validity is current, chain is trusted by approved clients, private-key permissions match local policy, and renewal/generation ownership is recorded. Never copy the key into ThothII. **Step 3: Validate Nginx without opening traffic** ```bash sudo nginx -t curl --fail http://127.0.0.1:/health ``` Use local `--resolve`/Host tests only when they do not bypass the identity behavior being tested. **Step 4: Prepare the load-balancer route** Configure backend/health/TLS according to the surveyed owner procedure, initially disabled or operator-only. Confirm it targets Nginx, never core/Qdrant/Ollama directly. **Step 5: Preserve the Aritmolab link contract** If the existing sidebar target already equals the final origin/path, leave source unchanged and record proof. Otherwise make the smallest reviewed change, test it in Aritmolab's own test/build system, and commit in that repository before deployment. ### Task 9: Open ingress and run OIDC acceptance **Files:** - Complete: `docs/testing/psd-server-project-b-manual.md` **Step 1: Reload Nginx through the established mechanism** Run `nginx -t` immediately before reload. Expected: reload succeeds and unrelated virtual hosts remain healthy. **Step 2: Enable the final load-balancer route** Expected: HTTP redirects to HTTPS; TLS is valid; `/api/auth/oidc/login` redirects to the correct Authentik provider; callback returns to the exact public origin. **Step 3: Test ordinary and admin identities** Start at Aritmolab, authenticate once, then use the sidebar. Expected: no second credential prompt; ordinary user can use sessions but receives 403 for admin operations; admin has only documented permissions. **Step 4: Test no-role and malformed cases** An identity with no mapped group authenticates but receives no application role/403. Missing, malformed, indirect, or ambiguous group claims fail closed with generic browser errors and redacted diagnostics. Do not retain raw claims. **Step 5: Test logout and restart** Verify ThothII logout revokes its own cookie. Document whether the Authentik SSO session remains and therefore allows immediate re-login without credentials; do not claim global logout unless configured and tested. Restart core and verify expected OIDC session behavior. ### Task 10: Verify PostgreSQL ownership and complete F1-F8 **Files:** - Create: protected Project B session evidence **Step 1: Create sessions under two identities** Expected: ordinary users see only their own sessions; cross-user access returns the documented not-found boundary; admin behavior matches `session.read_all/manage_all` permissions. **Step 2: Verify RLS with the runtime path** Use application/API tests and bounded catalog evidence. Never disable RLS for diagnosis. **Step 3: Complete one harmless OIDC PSD session** Use the same approved read-only question or another owner-approved one. Complete F1-F8, validate final SQL, resume once, and confirm session/artifacts/decisions are stored in `thoth_sessions`. **Step 4: Verify ephemeral boundaries** Expected: no chat transcript or SSE stream stored as session artifacts; no vectors in PostgreSQL; Qdrant remains the semantic store. ### Task 11: Test controlled failures and rollback **Files:** - Record: protected rollback evidence **Step 1: Test a reversible provider/catalog failure** Use a controlled, owner-approved method such as a temporary disabled test credential or test object. Expected: auth diagnostics and browser login fail closed, redacted, then pass after restoration. Never break unrelated Authentik applications. **Step 2: Rehearse ingress-first rollback** Close the production route, validate Nginx restoration commands, and prove the protected Project A configuration snapshot is complete. A full rollback need not destroy `thoth_sessions`. **Step 3: Verify additive database rollback boundary** Expected: rollback leaves schema/data intact for evidence and future recovery. No automatic DROP SCHEMA or role deletion. ### Task 12: Close Project B **Files:** - Complete: `docs/testing/evidence/psd-server-project-b-report-template.md` **Step 1: Run final diagnostics** Run status, doctor, auth check, interactive check when supported, workspace inspect, vector inspect, Pi test, Nginx validation, load-balancer health, Supabase migration/security checks, and sidebar test. **Step 2: Remove the Project A temporary endpoint** Remove only its load-balancer route, Nginx fragment, and managed certificate reference according to their owners. Validate/reload and prove the hostname no longer routes. **Step 3: Complete the human guide and report** Every mandatory row must be PASS. Record exact source/image/workspace identities, Authentik object names/IDs, Supabase database plus `thoth_sessions`, public origin, Aritmolab revision, report digest, and `PROJECT_B_PASS` or `PROJECT_B_FAIL`. **Step 4: Handle FAIL safely** On FAIL, close ingress first. Restore Project A files at the same stable paths, move OIDC auth state to protected evidence, restore the local-auth directory, validate, and start Project A privately. Disable new Authentik objects; do not delete them or drop the session schema automatically.