#!/usr/bin/env python3 """Setup Authentik OIDC integration for CryptPad SSO. Creates an OAuth2 provider, application, and test user in Authentik, then updates the CryptPad env file with SSO settings and inserts the client secret as a Docker secret. """ 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 = "cryptpad" APP_SLUG = "cryptpad" CLIENT_ID = "cryptpad" TEST_USER = "testuser" TEST_PASS = "testpass123" TEST_EMAIL = f"{TEST_USER}@test.example.com" def main(): inst = load_default_instance() cpad_domain = inst.default_domain("cryptpad") ak_domain = inst.default_domain("authentik") 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://{cpad_domain}/ssoauth", }], uuids=uuids, ) # Step 2: Create application ak.ensure_application("CryptPad", APP_SLUG, provider_pk, f"https://{cpad_domain}") # Step 3: Ensure test user with APP_PASSWORD user_pk = ak.ensure_user(TEST_USER, TEST_EMAIL, TEST_PASS) app_password = ak.ensure_app_password(user_pk) # Step 4: Update CryptPad env with SSO settings print("=== Update CryptPad SSO settings in env file ===", flush=True) env_path = get_abra_env_path(inst.server, cpad_domain) if not env_path.exists(): print(f" WARNING: CryptPad env file not found at {env_path}", flush=True) print(f" Create the app first: abra app new cryptpad --server {inst.server} --domain {cpad_domain} --no-input", flush=True) print(" Skipping env update", flush=True) else: oidc_url = f"{ak_url}/application/o/{APP_SLUG}" overrides = { "SSO_ENABLED": "true", "SSO_PROVIDER_NAME": "Authentik", "SSO_OIDC_URL": oidc_url, "SSO_CLIENT_ID": CLIENT_ID, } # Remove old SSO_CLIENT_SECRET env var if present (now a Docker secret) env_data = read_env_file(env_path) if "SSO_CLIENT_SECRET" in env_data: print(" Removing old SSO_CLIENT_SECRET env var (now a Docker secret)", flush=True) # Read file lines and filter out SSO_CLIENT_SECRET with open(env_path) as f: lines = f.readlines() with open(env_path, "w") as f: for line in lines: if not line.strip().startswith("SSO_CLIENT_SECRET="): f.write(line) apply_env_overrides(env_path, overrides) # Ensure SSO_CLIENT_SECRET_VERSION exists env_data = read_env_file(env_path) if "SSO_CLIENT_SECRET_VERSION" not in env_data: with open(env_path, "a") as f: f.write("SSO_CLIENT_SECRET_VERSION=v1\n") # Insert client secret as Docker secret if not already present try: existing = load_secrets(cpad_domain).get("sso_client_s") except Exception: existing = None if existing: print(" Secret sso_client_s already exists in local testsecrets, skipping insert", flush=True) else: env_data = read_env_file(env_path) current_version = env_data.get("SSO_CLIENT_SECRET_VERSION", "v1") print(f" Inserting sso_client_s {current_version} ...", flush=True) app_secret_insert(cpad_domain, "sso_client_s", current_version, client_secret) print(f" Inserted sso_client_s {current_version}", flush=True) # Step 5: 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 CryptPad SSO 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_authentik_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'# CryptPad instance\n') f.write(f'cpad_domain = "{cpad_domain}"\n') print(f" Written to {creds_file}", flush=True) print("", flush=True) print("=== Authentik OIDC integration setup for CryptPad complete ===", flush=True) print("", flush=True) print("Next steps:", flush=True) print(f" 1. Redeploy CryptPad: abra app deploy {cpad_domain} --chaos --force --no-input", flush=True) print(f" 2. Wait ~2min for SSO plugin to install and CryptPad to rebuild", flush=True) print(f" 3. Run OIDC test: python3 recipe-info/cryptpad/tests/oidc_login.py", flush=True) if __name__ == "__main__": main()