Skip to content

Convert sign-ups stranded at the confirmation step

Self-enrolled directory accounts that never completed the confirmation step stay inactive. This page covers the script that converts them, so that the account's first registry sign-in sends the activation mail.

scripts/directory-convert-unactivated.py runs inside authentik's ak shell. Its default is a read-only dry run. It prints aggregate counts, without names, addresses, account IDs or credentials. No mail is sent by conversion; the account's first registry sign-in sends the activation link.

The script selects inactive internal accounts at the exact path users/self-enrolled with no prior sign-in. It skips administrators, accounts with any authenticator (including an unconfirmed one), and accounts already carrying agentry_email_verified. That last guard avoids turning an operator-disabled account from the new flow back on. Only a legacy account with an absent mark is converted: is_active=True and agentry_email_verified=False, preserving its other attributes. A legacy account disabled before any sign-in is indistinguishable from a stranded sign-up by these fields; the operator must resolve any such accounts before applying the production conversion.

An apply requires the activation switch, the active enrollment write stage, and the registry provider's agentry-verified-email mapping to be live. It locks and rechecks the cohort in one transaction, and refuses a count that differs from the reviewed dry run. Repeating the script leaves the converted accounts alone. Take a directory database backup before applying on production. Run during a quiet enrollment window; pause enrollment and sign-in if necessary so the reviewed count remains stable.

On a compose stack

From the site checkout, with the compose stack running, dry run:

docker exec -i compose-authentik-worker-1 ak shell -c \
  'exec(compile(__import__("sys").stdin.read(), "conversion", "exec"), {"__name__": "__main__"})' \
  < scripts/directory-convert-unactivated.py

Review eligible. Then run with its exact count (replace N):

docker exec -i -e AGENTRY_CONVERT_UNACTIVATED_APPLY=1 \
  -e AGENTRY_CONVERT_UNACTIVATED_EXPECTED_COUNT=N \
  compose-authentik-worker-1 ak shell -c \
  'exec(compile(__import__("sys").stdin.read(), "conversion", "exec"), {"__name__": "__main__"})' \
  < scripts/directory-convert-unactivated.py

Repeat the dry run; eligible must be zero. The acceptance test in scripts/acceptance/directory-convert-unactivated.py runs against the pinned throwaway directory in directory-social.sh. Its fixtures and conversion roll back; it verifies the dry run, switch and count refusals, each excluded account, preservation of attributes, and a repeat run.

On Kubernetes (production)

The dry run only reads. The database write is made by the operator, after the activation deploy.

Registry activation also requires a sending registry notifier: production must render notify.kind: smtp, Postmark's transactional host on port 587 with STARTTLS, and API/worker AGENTRY_SMTP_USER and AGENTRY_SMTP_PASS from the agentry-app Secret. The worker permits Postmark's published SMTP endpoint ranges on that port. scripts/registry-mail-check.sh checks these rendered manifests in CI. Directory mail working does not prove registry mail works: the log notifier accepts a resend without delivering anything. Verify the deployed configuration and SMTP authentication before the real signup check; authentication alone does not prove delivery.

With the production kubeconfig loaded, select the current Ready worker:

kubectl -n agentry get pods -l app.kubernetes.io/component=worker

Replace WORKER_POD with that pod's name and dry run the reviewed script:

kubectl -n agentry exec -i WORKER_POD -- ak shell -c \
  'exec(compile(__import__("sys").stdin.read(), "conversion", "exec"), {"__name__": "__main__"})' \
  < scripts/directory-convert-unactivated.py

After the backup and activation deploy, repeat the dry run and review its count. The operator applies with that exact N:

kubectl -n agentry exec -i WORKER_POD -- env \
  AGENTRY_CONVERT_UNACTIVATED_APPLY=1 \
  AGENTRY_CONVERT_UNACTIVATED_EXPECTED_COUNT=N ak shell -c \
  'exec(compile(__import__("sys").stdin.read(), "conversion", "exec"), {"__name__": "__main__"})' \
  < scripts/directory-convert-unactivated.py

Repeat the dry run and record the counts and the deployment revision. Then prove a real production sign-up reaches the Dashboard without a mail wait, sees the activation banner, observes the five-minute resend limit, and loses the banner after clicking the mailed activation button. Opening the link alone must leave the account unactivated. Verify registry emailVerified becomes true while the directory attribute stays false: the directory claim reads confirmation from the registry and the registry token has no change_user permission. Then erase the approved test account.