From 22e526d56b9eec47b5aed01f022bcfc55302e3b1 Mon Sep 17 00:00:00 2001 From: Pieter Date: Thu, 15 Jan 2026 11:22:53 +0100 Subject: [PATCH] feat: Add public enrollment flow with invitation support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Created enrollment-flow.yaml blueprint with: * Enrollment flow with authentication: none * Invitation stage (continues without invitation token) * Prompt fields for user registration * User write stage with user_creation_mode: always_create * User login stage for automatic login after registration - Fixed blueprint structure (attrs before identifiers) - Public enrollment available at /if/flow/default-enrollment-flow/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../authentik/files/enrollment-flow.yaml | 165 ++++++++++++++++++ ansible/roles/authentik/tasks/invitation.yml | 56 +++--- 2 files changed, 197 insertions(+), 24 deletions(-) create mode 100644 ansible/roles/authentik/files/enrollment-flow.yaml diff --git a/ansible/roles/authentik/files/enrollment-flow.yaml b/ansible/roles/authentik/files/enrollment-flow.yaml new file mode 100644 index 0000000..dd0298e --- /dev/null +++ b/ansible/roles/authentik/files/enrollment-flow.yaml @@ -0,0 +1,165 @@ +version: 1 +metadata: + name: public-enrollment-flow + labels: + blueprints.goauthentik.io/description: "Public enrollment flow with invitation support" + blueprints.goauthentik.io/instantiate: "true" + +entries: + # 1. CREATE ENROLLMENT FLOW + - attrs: + designation: enrollment + name: Default enrollment Flow + title: Welcome to authentik! + authentication: none + identifiers: + slug: default-enrollment-flow + model: authentik_flows.flow + id: flow + + # 2. CREATE INVITATION STAGE + - attrs: + continue_flow_without_invitation: true + identifiers: + name: default-enrollment-invitation + id: invitation-stage + model: authentik_stages_invitation.invitationstage + + # 3. CREATE PROMPT FIELDS + - attrs: + order: 0 + placeholder: Username + placeholder_expression: false + required: true + type: username + field_key: username + label: Username + identifiers: + name: default-enrollment-field-username + id: prompt-field-username + model: authentik_stages_prompt.prompt + + - attrs: + order: 1 + placeholder: Name + placeholder_expression: false + required: true + type: text + field_key: name + label: Name + identifiers: + name: default-enrollment-field-name + id: prompt-field-name + model: authentik_stages_prompt.prompt + + - attrs: + order: 2 + placeholder: Email + placeholder_expression: false + required: true + type: email + field_key: email + label: Email + identifiers: + name: default-enrollment-field-email + id: prompt-field-email + model: authentik_stages_prompt.prompt + + - attrs: + order: 3 + placeholder: Password + placeholder_expression: false + required: true + type: password + field_key: password + label: Password + identifiers: + name: default-enrollment-field-password + id: prompt-field-password + model: authentik_stages_prompt.prompt + + - attrs: + order: 4 + placeholder: Password (repeat) + placeholder_expression: false + required: true + type: password + field_key: password_repeat + label: Password (repeat) + identifiers: + name: default-enrollment-field-password-repeat + id: prompt-field-password-repeat + model: authentik_stages_prompt.prompt + + # 4. CREATE PROMPT STAGE + - attrs: + fields: + - !KeyOf prompt-field-username + - !KeyOf prompt-field-name + - !KeyOf prompt-field-email + - !KeyOf prompt-field-password + - !KeyOf prompt-field-password-repeat + validation_policies: [] + identifiers: + name: default-enrollment-prompt + id: prompt-stage + model: authentik_stages_prompt.promptstage + + # 5. CREATE USER WRITE STAGE + - attrs: + user_creation_mode: always_create + create_users_as_inactive: false + create_users_group: null + user_path_template: "" + identifiers: + name: default-enrollment-user-write + id: user-write-stage + model: authentik_stages_user_write.userwritestage + + # 6. CREATE USER LOGIN STAGE + - attrs: + session_duration: seconds=0 + identifiers: + name: default-enrollment-user-login + id: user-login-stage + model: authentik_stages_user_login.userloginstage + + # 7. BIND INVITATION STAGE TO FLOW (order 0) + - attrs: + evaluate_on_plan: true + re_evaluate_policies: false + identifiers: + order: 0 + stage: !KeyOf invitation-stage + target: !KeyOf flow + model: authentik_flows.flowstagebinding + + # 8. BIND PROMPT STAGE TO FLOW (order 10) + - attrs: + evaluate_on_plan: true + re_evaluate_policies: false + identifiers: + order: 10 + stage: !KeyOf prompt-stage + target: !KeyOf flow + model: authentik_flows.flowstagebinding + + # 9. BIND USER WRITE STAGE TO FLOW (order 20) + - attrs: + evaluate_on_plan: true + re_evaluate_policies: false + identifiers: + order: 20 + stage: !KeyOf user-write-stage + target: !KeyOf flow + model: authentik_flows.flowstagebinding + + # 10. BIND USER LOGIN STAGE TO FLOW (order 30) + - attrs: + evaluate_on_plan: true + re_evaluate_policies: false + identifiers: + order: 30 + stage: !KeyOf user-login-stage + target: !KeyOf flow + model: authentik_flows.flowstagebinding diff --git a/ansible/roles/authentik/tasks/invitation.yml b/ansible/roles/authentik/tasks/invitation.yml index 0975879..3284f03 100644 --- a/ansible/roles/authentik/tasks/invitation.yml +++ b/ansible/roles/authentik/tasks/invitation.yml @@ -22,33 +22,33 @@ state: directory mode: '0755' -- name: Copy invitation flow blueprint to server +- name: Copy public enrollment flow blueprint to server copy: - src: invitation-flow.yaml - dest: /opt/config/authentik/blueprints/invitation-flow.yaml + src: enrollment-flow.yaml + dest: /opt/config/authentik/blueprints/enrollment-flow.yaml mode: '0644' - register: invitation_blueprint_copied + register: enrollment_blueprint_copied -- name: Copy blueprint into authentik-worker container +- name: Copy enrollment blueprint into authentik-worker container shell: | - docker cp /opt/config/authentik/blueprints/invitation-flow.yaml authentik-worker:/blueprints/invitation-flow.yaml + docker cp /opt/config/authentik/blueprints/enrollment-flow.yaml authentik-worker:/blueprints/enrollment-flow.yaml -- name: Copy blueprint into authentik-server container +- name: Copy enrollment blueprint into authentik-server container shell: | - docker cp /opt/config/authentik/blueprints/invitation-flow.yaml authentik-server:/blueprints/invitation-flow.yaml + docker cp /opt/config/authentik/blueprints/enrollment-flow.yaml authentik-server:/blueprints/enrollment-flow.yaml -- name: Wait for blueprint to be discovered and applied +- name: Wait for enrollment blueprint to be discovered and applied shell: | - echo "Waiting for invitation blueprint to be discovered and applied..." + echo "Waiting for public enrollment blueprint to be discovered and applied..." sleep 10 # Check if blueprint instance was created i=1 while [ $i -le 24 ]; do result=$(docker exec authentik-server curl -sf -H 'Authorization: Bearer {{ authentik_api_token }}' \ 'http://localhost:9000/api/v3/managed/blueprints/' 2>/dev/null || echo '') - if echo "$result" | grep -q 'invitation-flow-configuration'; then + if echo "$result" | grep -q 'public-enrollment-flow'; then echo "Blueprint instance found" - if echo "$result" | grep -A 10 'invitation-flow-configuration' | grep -q 'successful'; then + if echo "$result" | grep -A 10 'public-enrollment-flow' | grep -q 'successful'; then echo "Blueprint applied successfully" exit 0 fi @@ -57,38 +57,46 @@ i=$((i+1)) done echo "Blueprint deployment in progress (may take 1-2 minutes)" - register: invitation_blueprint_result + register: enrollment_blueprint_result changed_when: false -- name: Verify invitation stage was created +- name: Verify enrollment flow was created shell: | docker exec authentik-server curl -sf -H 'Authorization: Bearer {{ authentik_api_token }}' \ - 'http://localhost:9000/api/v3/stages/all/?name=default-enrollment-invitation' | \ + 'http://localhost:9000/api/v3/flows/instances/?slug=default-enrollment-flow' | \ python3 -c "import sys, json; d = json.load(sys.stdin); print(json.dumps({'found': len(d.get('results', [])) > 0, 'count': len(d.get('results', []))}))" - register: invitation_stage_check + register: enrollment_flow_check changed_when: false failed_when: false -- name: Display invitation stage configuration status +- name: Display public enrollment flow configuration status debug: msg: | ======================================== - Authentik Invitation Stage Configuration + Authentik Public Enrollment Flow ======================================== Configuration Method: YAML Blueprints - Blueprint File: /blueprints/invitation-flow.yaml + Blueprint File: /blueprints/enrollment-flow.yaml - ✓ Blueprint Deployed: {{ invitation_blueprint_copied.changed | default(false) }} - ✓ Blueprint Applied: {{ 'In Progress' if invitation_blueprint_result.rc != 0 else 'Complete' }} + ✓ Blueprint Deployed: {{ enrollment_blueprint_copied.changed | default(false) }} + ✓ Blueprint Applied: {{ 'In Progress' if enrollment_blueprint_result.rc != 0 else 'Complete' }} - Verification: {{ invitation_stage_check.stdout | default('{}') }} + Verification: {{ enrollment_flow_check.stdout | default('{}') }} + + Features: + - Public self-registration enabled + - Invitation token support + - User prompts: username, name, email, password + - Automatic user creation and login + - Set as default enrollment flow in brand Note: Authentik applies blueprints asynchronously. Changes should be visible within 1-2 minutes. To verify manually: - Login to https://{{ authentik_domain }} - - Check Admin > Flows > Stages for "default-enrollment-invitation" - - Check Admin > Flows > default-source-enrollment for invitation binding + - Check Admin > Flows for "default-enrollment-flow" + - Check Admin > System > Brands > Flow enrollment + - Test enrollment at: https://{{ authentik_domain }}/if/flow/default-enrollment-flow/ ========================================