apiVersion: v1 kind: ConfigMap metadata: name: zitadel-action-create-user namespace: django data: createUser.js: | function createUser(ctx, api) { let http = require('zitadel/http'); let logger = require('zitadel/log'); const BACKEND_HOST = 'http://backend-svc.django.svc.cluster.local:80/api' const USER_COMPANIES = [1] const BASIC_AUTH = '__BASIC_AUTH__' logger.log('Creating external user in backend'); logger.log('Context: ' + JSON.stringify(ctx)); const userData = { first_name: ctx.v1.user.human.firstName, last_name: ctx.v1.user.human.lastName, email: ctx.v1.user.human.email, username: ctx.v1.user.username, companies: USER_COMPANIES, departments: [], user_permissions: [] }; logger.log('Sending user data: ' + JSON.stringify(userData)); const response = http.fetch(BACKEND_HOST + '/core/admin/users/external/', { method: 'POST', body: userData, headers: { 'Content-Type': 'application/json', 'Authorization': BASIC_AUTH } }); if (response.status != 200 && response.status != 201) { logger.log('Error response status: ' + response.status); logger.log('Error response body: ' + response.text()); throw "Backend API error: " + response.status; } const metadata = response.json(); logger.log('Metadata received: ' + JSON.stringify(metadata)); Object.keys(metadata).forEach(function(key) { var value = metadata[key]; logger.log('Setting metadata - Key: ' + key + ', Value: ' + value); api.v1.user.appendMetadata(key, value); }); logger.log('External user successfully created and metadata set'); } apply.py: | import os import sys import httpx ZITADEL = os.environ["ZITADEL_URL"] HEADERS = { "Host": os.environ["ZITADEL_HOST"], "X-Forwarded-Proto": "https", "Authorization": "Bearer " + open("/vault/secrets/zitadel-token").read().strip(), "Content-Type": "application/json", } ORG_NAME = os.environ["ZITADEL_ORG_NAME"] ACTION_NAME = os.environ["ACTION_NAME"] FLOW_TYPE = os.environ["FLOW_TYPE"] TRIGGER_TYPE = os.environ["TRIGGER_TYPE"] client = httpx.Client(base_url=ZITADEL, headers=HEADERS, timeout=30) def call(method, path, body=None, org_id=None, allow=()): headers = {"x-zitadel-orgid": org_id} if org_id else {} r = client.request(method, path, json=body, headers=headers) if r.status_code in allow: return None if r.status_code >= 300: print(f"{method} {path} -> {r.status_code}: {r.text}") sys.exit(1) return r.json() if r.content else {} me = call("GET", "/auth/v1/users/me")["user"] print(f"token user {me.get('userName')} id={me.get('id')}") org = None found_orgs = call("POST", "/admin/v1/orgs/_search", {"queries": []}, allow=(403,)) if found_orgs is not None: orgs = found_orgs.get("result", []) org = next((o for o in orgs if o.get("name", "").lower() == ORG_NAME.lower()), None) if org is None: print(f"org '{ORG_NAME}' not found, available: {[o.get('name') for o in orgs]}") sys.exit(1) else: org = call("GET", "/management/v1/orgs/me")["org"] print(f"no instance permissions, using token org '{org.get('name')}'") if org.get("name", "").lower() != ORG_NAME.lower(): print(f"token org is '{org.get('name')}', not '{ORG_NAME}'") sys.exit(1) org_id = org["id"] print(f"org {org['name']} id={org_id}") script = open("/opt/action/createUser.js").read().replace( "__BASIC_AUTH__", "Basic " + open("/vault/secrets/backend-basic").read().strip() ) body = {"name": ACTION_NAME, "script": script, "timeout": "10s", "allowedToFail": False} found = call( "POST", "/management/v1/actions/_search", {"queries": [{"actionNameQuery": {"name": ACTION_NAME, "method": "TEXT_QUERY_METHOD_EQUALS"}}]}, org_id, ).get("result", []) if found: action_id = found[0]["id"] call("PUT", f"/management/v1/actions/{action_id}", body, org_id) print(f"action {ACTION_NAME} updated id={action_id}") else: action_id = call("POST", "/management/v1/actions", body, org_id)["id"] print(f"action {ACTION_NAME} created id={action_id}") call("POST", f"/management/v1/flows/{FLOW_TYPE}/trigger/{TRIGGER_TYPE}", {"actionIds": [action_id]}, org_id) print(f"flow {FLOW_TYPE} trigger {TRIGGER_TYPE} -> {ACTION_NAME}")