From 7e7e41cbef41c86361c3b3ca10da44b115bcab91 Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Wed, 17 Jun 2026 07:35:23 +0000 Subject: [PATCH] fix(authentik): derive username from email in tripit-enrollment (user_write needs it) The passwordless enrollment prompt collects only email+name, so user_write aborted with 'Aborting write to empty username' (ak-stage-access-denied). Add an expression policy on the user_write binding (evaluate_on_plan=false + re_evaluate_policies=true, like guest.tf) that sets prompt_data['username'] = the entered email before the write. Verified the failure live via the flow executor API. Co-Authored-By: Claude Opus 4.8 --- stacks/authentik/tripit-flows.tf | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/stacks/authentik/tripit-flows.tf b/stacks/authentik/tripit-flows.tf index 534a4d4d..a3889dc9 100644 --- a/stacks/authentik/tripit-flows.tf +++ b/stacks/authentik/tripit-flows.tf @@ -117,7 +117,34 @@ resource "authentik_flow_stage_binding" "tripit_enroll_20_write" { target = authentik_flow.tripit_enrollment.uuid stage = authentik_stage_user_write.tripit_enroll_write.id order = 20 + # Run the username-from-email policy (below) at stage-execution time, when + # prompt_data is populated — not at plan time. Mirrors guest.tf's pre-stage + # context-mutation pattern. + evaluate_on_plan = false + re_evaluate_policies = true } + +# Passwordless, email-only signup collects no username, but user_write aborts on +# an empty username ("Aborting write to empty username"). Derive the username +# from the entered email just before user_write runs. Mutating flow_plan.context +# is the canonical mutable path — a plain request.context mutation would not +# propagate to the stage (see guest.tf's pending_user note). +resource "authentik_policy_expression" "tripit_username_from_email" { + name = "tripit-enrollment-username-from-email" + expression = trimspace(<<-EOT + pd = request.context["flow_plan"].context.setdefault("prompt_data", {}) + pd["username"] = pd.get("email", "") + return True + EOT + ) +} + +resource "authentik_policy_binding" "tripit_username_before_write" { + target = authentik_flow_stage_binding.tripit_enroll_20_write.id + policy = authentik_policy_expression.tripit_username_from_email.id + order = 0 +} + # order 30 (email-verification binding) is in tripit-email-blueprint.tf — see note above resource "authentik_flow_stage_binding" "tripit_enroll_40_passkey" { target = authentik_flow.tripit_enrollment.uuid