diff --git a/ansible/playbooks/update-recovery-flow.yml b/ansible/playbooks/update-recovery-flow.yml deleted file mode 100644 index 9575387..0000000 --- a/ansible/playbooks/update-recovery-flow.yml +++ /dev/null @@ -1,63 +0,0 @@ ---- -# Deploy password recovery flow with email notifications -- name: Deploy password recovery flow - hosts: all - gather_facts: no - become: yes - - vars: - authentik_api_token: "ak_DtA2LG1Z9shl-tw9r0cs34B1G9l8Lpz76GxLf-4OBiUWbiHbAVJ04GYLcZ30" - client_domain: "dev.vrije.cloud" - - tasks: - - name: Create blueprints directory - file: - path: /opt/config/authentik/blueprints - state: directory - mode: '0755' - - - name: Copy recovery flow blueprint - copy: - src: ../roles/authentik/files/recovery-flow.yaml - dest: /opt/config/authentik/blueprints/recovery-flow.yaml - mode: '0644' - register: blueprint_copied - - - name: Copy blueprint into authentik-worker container - shell: | - docker cp /opt/config/authentik/blueprints/recovery-flow.yaml authentik-worker:/blueprints/recovery-flow.yaml - when: blueprint_copied.changed - - - name: Copy blueprint into authentik-server container - shell: | - docker cp /opt/config/authentik/blueprints/recovery-flow.yaml authentik-server:/blueprints/recovery-flow.yaml - when: blueprint_copied.changed - - - name: Restart authentik-worker to force blueprint discovery - shell: docker restart authentik-worker - when: blueprint_copied.changed - - - name: Wait for blueprint to be applied - shell: | - sleep 30 - docker exec authentik-server curl -sf -H 'Authorization: Bearer {{ authentik_api_token }}' \ - 'http://localhost:9000/api/v3/flows/instances/?slug=default-recovery-flow' - register: flow_check - retries: 6 - delay: 10 - until: flow_check.rc == 0 - no_log: true - - - name: Display success message - debug: - msg: | - ✓ Password recovery flow deployed successfully! - - Users can now reset their passwords by: - 1. Going to https://auth.{{ client_domain }}/if/flow/default-recovery-flow/ - 2. Entering their email address - 3. Receiving a recovery link via email - 4. Clicking the link and setting a new password - - The recovery link expires in 30 minutes. - Emails are sent via Mailgun SMTP (noreply@mg.vrije.cloud) diff --git a/ansible/roles/authentik/files/configure_recovery_flow.py b/ansible/roles/authentik/files/configure_recovery_flow.py deleted file mode 100644 index 3b200df..0000000 --- a/ansible/roles/authentik/files/configure_recovery_flow.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python3 -""" -Configure Authentik recovery flow. -Verifies that the default recovery flow exists (Authentik creates it by default). -The recovery flow is used when clicking "Create recovery link" in the UI. -""" -import sys -import json -import urllib.request -import urllib.error - -def api_request(base_url, token, path, method='GET', data=None): - """Make API request to Authentik""" - url = f"{base_url}{path}" - headers = { - 'Authorization': f'Bearer {token}', - 'Content-Type': 'application/json' - } - - request_data = json.dumps(data).encode() if data else None - req = urllib.request.Request(url, data=request_data, headers=headers, method=method) - - try: - with urllib.request.urlopen(req, timeout=30) as resp: - return resp.status, json.loads(resp.read().decode()) - except urllib.error.HTTPError as e: - error_body = e.read().decode() - try: - error_data = json.loads(error_body) - except: - error_data = {'error': error_body} - return e.code, error_data - -def main(): - if len(sys.argv) != 3: - print(json.dumps({'error': 'Usage: configure_recovery_flow.py '}), file=sys.stderr) - sys.exit(1) - - base_url = sys.argv[1] - token = sys.argv[2] - - # Get the default recovery flow (created by Authentik by default) - status, flows_response = api_request(base_url, token, '/api/v3/flows/instances/') - if status != 200: - print(json.dumps({'error': 'Failed to list flows', 'details': flows_response}), file=sys.stderr) - sys.exit(1) - - recovery_flow = next((f for f in flows_response.get('results', []) - if f.get('designation') == 'recovery'), None) - - if not recovery_flow: - print(json.dumps({'error': 'No recovery flow found - Authentik should create one by default'}), file=sys.stderr) - sys.exit(1) - - flow_slug = recovery_flow['slug'] - flow_pk = recovery_flow['pk'] - - print(json.dumps({ - 'success': True, - 'message': 'Recovery flow configured', - 'flow_slug': flow_slug, - 'flow_pk': flow_pk, - 'note': 'Using Authentik default recovery flow' - })) - -if __name__ == '__main__': - main() diff --git a/ansible/roles/authentik/files/custom-flows.yaml b/ansible/roles/authentik/files/custom-flows.yaml index 9bbd61b..226db83 100644 --- a/ansible/roles/authentik/files/custom-flows.yaml +++ b/ansible/roles/authentik/files/custom-flows.yaml @@ -2,7 +2,7 @@ version: 1 metadata: name: custom-flow-configuration labels: - blueprints.goauthentik.io/description: "Configure invitation, recovery, and 2FA enforcement" + blueprints.goauthentik.io/description: "Configure invitation and 2FA enforcement" blueprints.goauthentik.io/instantiate: "true" entries: @@ -26,15 +26,7 @@ entries: evaluate_on_plan: true re_evaluate_policies: false - # 3. SET RECOVERY FLOW IN BRAND - # Configures the default brand to use the recovery flow - - model: authentik_core.brand - identifiers: - domain: authentik-default - attrs: - flow_recovery: !Find [authentik_flows.flow, [designation, recovery]] - - # 4. ENFORCE 2FA CONFIGURATION + # 3. ENFORCE 2FA CONFIGURATION # Updates MFA validation stage to force users to configure TOTP - model: authentik_stages_authenticator_validate.authenticatorvalidatestage identifiers: diff --git a/ansible/roles/authentik/files/recovery-flow.yaml b/ansible/roles/authentik/files/recovery-flow.yaml deleted file mode 100644 index 7cb3e0a..0000000 --- a/ansible/roles/authentik/files/recovery-flow.yaml +++ /dev/null @@ -1,166 +0,0 @@ -version: 1 -metadata: - name: password-recovery-flow - labels: - blueprints.goauthentik.io/description: "Password recovery flow with email link" - blueprints.goauthentik.io/instantiate: "true" - -entries: - # 1. CREATE RECOVERY FLOW - - attrs: - designation: recovery - name: Password Recovery - title: Reset your password - authentication: none - denied_action: message_continue - layout: stacked - identifiers: - slug: default-recovery-flow - model: authentik_flows.flow - id: recovery-flow - - # 2. CREATE IDENTIFICATION STAGE - # Asks user for their email address - - attrs: - user_fields: - - email - case_insensitive_matching: true - show_matched_user: false - password_stage: null - enrollment_flow: null - recovery_flow: !KeyOf recovery-flow - identifiers: - name: default-recovery-identification - id: identification-stage - model: authentik_stages_identification.identificationstage - - # 3. CREATE EMAIL STAGE - # Sends recovery link via email - - attrs: - use_global_settings: true - host: smtp.mailgun.org - port: 587 - username: "" - password: "" - use_tls: true - use_ssl: false - timeout: 30 - from_address: "noreply@mg.vrije.cloud" - token_expiry: 30 - subject: "Password Reset Request" - template: | - Hello, - - You have requested to reset your password. Click the link below to continue: - - {{ recovery_link }} - - This link will expire in 30 minutes. - - If you did not request this password reset, please ignore this email. - - Best regards, - The Team - activate_user_on_success: true - identifiers: - name: default-recovery-email - id: email-stage - model: authentik_stages_email.emailstage - - # 4. CREATE PROMPT STAGE FOR NEW PASSWORD - # Collects new password from user - - attrs: - order: 0 - placeholder: "New Password" - placeholder_expression: false - required: true - type: password - field_key: password - label: "New Password" - identifiers: - name: default-recovery-field-password - id: prompt-field-password - model: authentik_stages_prompt.prompt - - - attrs: - order: 1 - placeholder: "Confirm New Password" - placeholder_expression: false - required: true - type: password - field_key: password_repeat - label: "Confirm New Password" - identifiers: - name: default-recovery-field-password-repeat - id: prompt-field-password-repeat - model: authentik_stages_prompt.prompt - - - attrs: - fields: - - !KeyOf prompt-field-password - - !KeyOf prompt-field-password-repeat - validation_policies: [] - identifiers: - name: default-recovery-prompt - id: prompt-stage - model: authentik_stages_prompt.promptstage - - # 5. CREATE USER WRITE STAGE - # Updates user's password - - attrs: - user_creation_mode: never_create - create_users_as_inactive: false - create_users_group: null - user_path_template: "" - identifiers: - name: default-recovery-user-write - id: user-write-stage - model: authentik_stages_user_write.userwritestage - - # 6. BIND IDENTIFICATION STAGE TO FLOW (order 10) - - attrs: - evaluate_on_plan: true - re_evaluate_policies: false - invalid_response_action: retry - identifiers: - order: 10 - stage: !KeyOf identification-stage - target: !KeyOf recovery-flow - model: authentik_flows.flowstagebinding - - # 7. BIND EMAIL STAGE TO FLOW (order 20) - - attrs: - evaluate_on_plan: true - re_evaluate_policies: false - identifiers: - order: 20 - stage: !KeyOf email-stage - target: !KeyOf recovery-flow - model: authentik_flows.flowstagebinding - - # 8. BIND PROMPT STAGE TO FLOW (order 30) - - attrs: - evaluate_on_plan: true - re_evaluate_policies: false - identifiers: - order: 30 - stage: !KeyOf prompt-stage - target: !KeyOf recovery-flow - model: authentik_flows.flowstagebinding - - # 9. BIND USER WRITE STAGE TO FLOW (order 40) - - attrs: - evaluate_on_plan: true - re_evaluate_policies: false - identifiers: - order: 40 - stage: !KeyOf user-write-stage - target: !KeyOf recovery-flow - model: authentik_flows.flowstagebinding - - # 10. SET AS DEFAULT RECOVERY FLOW IN BRAND - - attrs: - flow_recovery: !KeyOf recovery-flow - identifiers: - domain: authentik-default - model: authentik_tenants.tenant diff --git a/ansible/roles/authentik/tasks/flows.yml b/ansible/roles/authentik/tasks/flows.yml index 533b5b0..bf6a652 100644 --- a/ansible/roles/authentik/tasks/flows.yml +++ b/ansible/roles/authentik/tasks/flows.yml @@ -1,5 +1,5 @@ --- -# Configure Authentik flows (invitation, recovery, 2FA) via Blueprints +# Configure Authentik flows (invitation, 2FA) via Blueprints - name: Use bootstrap token for API access set_fact: @@ -35,7 +35,6 @@ loop: - custom-flows.yaml - enrollment-flow.yaml - - recovery-flow.yaml register: blueprints_copied - name: Copy blueprints into authentik-worker container @@ -44,7 +43,6 @@ loop: - custom-flows.yaml - enrollment-flow.yaml - - recovery-flow.yaml when: blueprints_copied.changed - name: Copy blueprints into authentik-server container @@ -53,7 +51,6 @@ loop: - custom-flows.yaml - enrollment-flow.yaml - - recovery-flow.yaml when: blueprints_copied.changed - name: Wait for blueprint to be discovered and applied @@ -99,14 +96,6 @@ changed_when: false failed_when: false -- name: Verify brand recovery flow was set - shell: | - docker exec authentik-server curl -sf -H "Authorization: Bearer {{ authentik_api_token }}" \ - "http://localhost:9000/api/v3/core/brands/" | \ - python3 -c "import sys, json; data = json.load(sys.stdin); brand = data['results'][0] if data['results'] else {}; print(json.dumps({'recovery_flow_set': brand.get('flow_recovery') is not None}))" - register: recovery_check - changed_when: false - failed_when: false - name: Display flows configuration status debug: @@ -119,21 +108,19 @@ Blueprints Deployed: - /blueprints/custom-flows.yaml (2FA enforcement) - /blueprints/enrollment-flow.yaml (invitation-only registration) - - /blueprints/recovery-flow.yaml (password reset via email) ✓ Blueprints Deployed: {{ blueprints_copied.changed }} ✓ Blueprints Applied: {{ 'Yes' if 'successfully' in blueprint_wait.stdout else 'In Progress' }} Verification: {{ invitation_check.stdout | default('Invitation stage: Checking...') }} - {{ recovery_check.stdout | default('Recovery flow: Checking...') }} Note: Authentik applies blueprints asynchronously. Changes should be visible within 1-2 minutes. + Recovery flows must be configured manually in Authentik admin UI. Flow URLs: - Enrollment: https://{{ authentik_domain }}/if/flow/default-enrollment-flow/ - - Recovery: https://{{ authentik_domain }}/if/flow/default-recovery-flow/ Email configuration is active - emails sent via Mailgun SMTP. ======================================== diff --git a/ansible/roles/authentik/tasks/main.yml b/ansible/roles/authentik/tasks/main.yml index 7800355..9aa1383 100644 --- a/ansible/roles/authentik/tasks/main.yml +++ b/ansible/roles/authentik/tasks/main.yml @@ -17,7 +17,7 @@ when: mailgun_smtp_user is defined or (client_secrets.mailgun_smtp_user is defined and client_secrets.mailgun_smtp_user != "" and "PLACEHOLDER" not in client_secrets.mailgun_smtp_user) tags: ['authentik', 'email'] -- name: Include flows configuration (recovery, invitation) +- name: Include flows configuration (invitation, 2FA) include_tasks: flows.yml when: authentik_bootstrap | default(true) tags: ['authentik', 'flows']