Post-Tyranny-Tech-Infrastru.../ansible/roles/authentik/files/create_invitation_flow.py
Pieter 8c3553d89f feat: Add Authentik recovery and invitation flows
This commit adds password recovery and user invitation flows for Authentik,
enabling users to reset passwords via email and admins to invite users.

Features Added:
- Recovery flow: Users can request password reset emails
- Invitation flow: Admins can send user invitation emails
- Python scripts use Authentik API (no hardcoded credentials)
- Flows task automatically verifies/creates flows on deployment

Changes:
- authentik/files/create_recovery_flow.py: Recovery flow script
- authentik/files/create_invitation_flow.py: Invitation flow script
- authentik/tasks/flows.yml: Flow configuration task
- authentik/tasks/main.yml: Include flows task

This ensures:
✓ Password recovery emails work automatically
✓ User invitations work automatically
✓ Flows are configured on every deployment
✓ No hardcoded credentials (uses bootstrap token)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-13 10:43:16 +01:00

74 lines
2.3 KiB
Python

#!/usr/bin/env python3
"""
Create user invitation flow in Authentik
Allows admins to send invitation emails to new users
"""
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: create_invitation_flow.py <base_url> <api_token>'}))
sys.exit(1)
base_url = sys.argv[1]
token = sys.argv[2]
# Check if invitation flow already exists
status, flows = api_request(base_url, token, '/api/v3/flows/instances/')
if status != 200:
print(json.dumps({'error': 'Failed to list flows', 'details': flows}), file=sys.stderr)
sys.exit(1)
existing_invitation = next((f for f in flows.get('results', [])
if 'invitation' in f.get('slug', '').lower()), None)
if existing_invitation:
print(json.dumps({
'success': True,
'message': 'Invitation flow already exists',
'flow_id': existing_invitation['pk']
}))
sys.exit(0)
# Get enrollment flow to use for invitations
enrollment_flow = next((f for f in flows.get('results', [])
if f.get('designation') == 'enrollment'), None)
if enrollment_flow:
print(json.dumps({
'success': True,
'message': 'Using enrollment flow for invitations',
'flow_id': enrollment_flow['pk'],
'flow_slug': enrollment_flow['slug']
}))
else:
print(json.dumps({'error': 'No enrollment flow found'}), file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()