#!/usr/bin/env python3 """Setup Authentik OIDC integration for La Suite Docs (ld2). Creates an OAuth2 provider, application, and test user in Authentik, then inserts the client secret and updates the Docs env file with Authentik OIDC endpoints. """ import os import sys sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) from lib.abra import app_secret_insert from lib.authentik import AuthentikAdmin from lib.env import apply_env_overrides, get_abra_env_path, read_env_file from lib.models import load_default_instance from lib.secrets import load_secrets # Configuration PROVIDER_NAME = "lasuite-docs" APP_SLUG = "lasuite-docs" CLIENT_ID = "docs" TEST_USER = "testuser" TEST_PASS = "testpass123" TEST_EMAIL = f"{TEST_USER}@test.example.com" def main(): inst = load_default_instance() ak_domain = inst.default_domain("authentik") # Docs instance uses a custom domain (ld2) to avoid conflict with keycloak-backed docs DOCS_DOMAIN = f"ld2.{inst.domain_suffix}" ak_url = f"https://{ak_domain}" # Get Authentik admin token from synced secrets ak_secrets = load_secrets(ak_domain) ak_token = ak_secrets["admin_token"] ak = AuthentikAdmin(ak_url, ak_token) # Resolve Authentik UUIDs uuids = ak.resolve_uuids() # Step 1: Create OAuth2 provider provider_pk, client_secret = ak.ensure_provider( PROVIDER_NAME, CLIENT_ID, redirect_uris=[{ "matching_mode": "strict", "url": f"https://{DOCS_DOMAIN}/api/v1.0/callback/", }], uuids=uuids, ) # Step 2: Create application ak.ensure_application("La Suite Docs", APP_SLUG, provider_pk, f"https://{DOCS_DOMAIN}") # Step 3: Create test user user_pk = ak.ensure_user(TEST_USER, TEST_EMAIL, TEST_PASS) app_password = ak.ensure_app_password(user_pk) # Step 4: Insert client secret into Docs via abra print("=== Insert OIDC client secret into Docs (ld2) ===", flush=True) env_path = get_abra_env_path(inst.server, DOCS_DOMAIN) if not env_path.exists(): print(f" WARNING: Docs env file not found at {env_path}", flush=True) print(f" Create the app first: abra app new lasuite-docs --server {inst.server} --domain {DOCS_DOMAIN} --no-input", flush=True) print(" Skipping secret insertion and env update", flush=True) else: env_data = read_env_file(env_path) current_version = env_data.get("SECRET_OIDC_RPCS_VERSION", "v1") next_num = int(current_version.lstrip("v")) + 1 next_version = f"v{next_num}" print(f" Current secret version: {current_version}", flush=True) print(f" Inserting as: {next_version}", flush=True) app_secret_insert(DOCS_DOMAIN, "oidc_rpcs", next_version, client_secret) # Step 5: Update Docs env with Authentik OIDC settings print("=== Update Docs OIDC settings in env file ===", flush=True) apply_env_overrides(env_path, { "SECRET_OIDC_RPCS_VERSION": next_version, "AUTH_DOMAIN": ak_domain, "OIDC_OP_JWKS_ENDPOINT": f"https://{ak_domain}/application/o/{APP_SLUG}/jwks/", "OIDC_OP_AUTHORIZATION_ENDPOINT": f"https://{ak_domain}/application/o/authorize/", "OIDC_OP_TOKEN_ENDPOINT": f"https://{ak_domain}/application/o/token/", "OIDC_OP_USER_ENDPOINT": f"https://{ak_domain}/application/o/userinfo/", "OIDC_RP_CLIENT_ID": CLIENT_ID, }) # Step 6: Write credentials file script_dir = os.path.dirname(os.path.abspath(__file__)) creds_file = os.path.join(script_dir, f"authentik-test-credentials.{inst.domain_suffix}.toml") print(f"=== Write credentials to {creds_file} ===", flush=True) with open(creds_file, "w") as f: f.write(f'# Authentik OIDC credentials for La Suite Docs (ld2) test instance\n') f.write(f'#\n') f.write(f'# Authentik instance: {ak_domain}\n') f.write(f'# Application slug: {APP_SLUG}\n') f.write(f'# Created by: setup_docs_integration.py\n') f.write(f'\n') f.write(f'# Authentik admin\n') f.write(f'ak_token = "{ak_token}"\n') f.write(f'\n') f.write(f'# OIDC provider\n') f.write(f'ak_app_slug = "{APP_SLUG}"\n') f.write(f'ak_client_id = "{CLIENT_ID}"\n') f.write(f'ak_client_secret = "{client_secret}"\n') f.write(f'\n') f.write(f'# Authentik OIDC endpoints\n') f.write(f'ak_token_endpoint = "https://{ak_domain}/application/o/token/"\n') f.write(f'ak_userinfo_endpoint = "https://{ak_domain}/application/o/userinfo/"\n') f.write(f'ak_discovery_endpoint = "https://{ak_domain}/application/o/{APP_SLUG}/.well-known/openid-configuration"\n') f.write(f'\n') f.write(f'# Test user (password for browser login, app_password for password grant)\n') f.write(f'ak_test_user = "{TEST_USER}"\n') f.write(f'ak_test_pass = "{TEST_PASS}"\n') f.write(f'ak_test_app_password = "{app_password}"\n') f.write(f'ak_test_email = "{TEST_EMAIL}"\n') f.write(f'\n') f.write(f'# Docs instance\n') f.write(f'docs_domain = "{DOCS_DOMAIN}"\n') print(f" Written to {creds_file}", flush=True) print("", flush=True) print("=== Authentik OIDC integration setup complete ===", flush=True) print("", flush=True) print("Next steps:", flush=True) print(f" 1. Redeploy Docs: abra app deploy {DOCS_DOMAIN} --chaos --force --no-input", flush=True) print(f" 2. Run OIDC test: python3 recipe-info/authentik/tests/oidc_integration.py", flush=True) if __name__ == "__main__": main()