Remove automated recovery flow configuration

Automated recovery flow setup via blueprints was too complex and
unreliable. Recovery flows (password reset via email) must now be
configured manually in Authentik admin UI.

Changes:
- Removed recovery-flow.yaml blueprint
- Removed configure_recovery_flow.py script
- Removed update-recovery-flow.yml playbook
- Updated flows.yml to remove recovery references
- Updated custom-flows.yaml to remove brand recovery flow config
- Updated comments to reflect manual recovery flow requirement

Automated configuration still includes:
- Enrollment flow with invitation support
- 2FA/MFA enforcement
- OIDC provider for Nextcloud
- Email configuration via SMTP

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Pieter 2026-01-17 09:57:07 +01:00
parent 6cd6d7cc79
commit dc14b12688
6 changed files with 5 additions and 322 deletions

View file

@ -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)

View file

@ -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 <base_url> <api_token>'}), 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()

View file

@ -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:

View file

@ -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

View file

@ -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.
========================================

View file

@ -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']