#!/bin/sh set -e # Cameleer SaaS — Vendor Seed Script # Creates the saas-vendor global role and vendor user. # Run ONCE on the hosted SaaS environment AFTER standard bootstrap. # NOT part of docker-compose.yml — invoked manually or by CI. LOGTO_ENDPOINT="${LOGTO_ENDPOINT:-http://logto:3001}" MGMT_API_RESOURCE="https://default.logto.app/api" API_RESOURCE_INDICATOR="https://api.cameleer.local" PG_HOST="${PG_HOST:-postgres}" PG_USER="${PG_USER:-cameleer}" PG_DB_LOGTO="logto" # Vendor credentials (override via env vars) VENDOR_USER="${VENDOR_USER:-vendor}" VENDOR_PASS="${VENDOR_PASS:-vendor}" VENDOR_NAME="${VENDOR_NAME:-SaaS Vendor}" log() { echo "[vendor-seed] $1"; } pgpass() { PGPASSWORD="${PG_PASSWORD:-cameleer_dev}"; export PGPASSWORD; } # Install jq + curl apk add --no-cache jq curl >/dev/null 2>&1 # ============================================================ # Get Management API token # ============================================================ log "Reading M2M credentials from bootstrap file..." BOOTSTRAP_FILE="/data/logto-bootstrap.json" if [ ! -f "$BOOTSTRAP_FILE" ]; then log "ERROR: Bootstrap file not found at $BOOTSTRAP_FILE — run standard bootstrap first" exit 1 fi M2M_ID=$(jq -r '.m2mClientId' "$BOOTSTRAP_FILE") M2M_SECRET=$(jq -r '.m2mClientSecret' "$BOOTSTRAP_FILE") if [ -z "$M2M_ID" ] || [ "$M2M_ID" = "null" ] || [ -z "$M2M_SECRET" ] || [ "$M2M_SECRET" = "null" ]; then log "ERROR: M2M credentials not found in bootstrap file" exit 1 fi log "Getting Management API token..." TOKEN_RESPONSE=$(curl -s -X POST "${LOGTO_ENDPOINT}/oidc/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials&client_id=${M2M_ID}&client_secret=${M2M_SECRET}&resource=${MGMT_API_RESOURCE}&scope=all") TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token' 2>/dev/null) [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ] && { log "ERROR: Failed to get token"; exit 1; } log "Got Management API token." api_get() { curl -s -H "Authorization: Bearer $TOKEN" "${LOGTO_ENDPOINT}${1}" 2>/dev/null || echo "[]"; } api_post() { curl -s -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "$2" "${LOGTO_ENDPOINT}${1}" 2>/dev/null || true; } # ============================================================ # Create saas-vendor global role # ============================================================ log "Checking for saas-vendor role..." EXISTING_ROLES=$(api_get "/api/roles") VENDOR_ROLE_ID=$(echo "$EXISTING_ROLES" | jq -r '.[] | select(.name == "saas-vendor" and .type == "User") | .id') if [ -n "$VENDOR_ROLE_ID" ]; then log "saas-vendor role exists: $VENDOR_ROLE_ID" else # Collect all API resource scope IDs EXISTING_RESOURCES=$(api_get "/api/resources") API_RESOURCE_ID=$(echo "$EXISTING_RESOURCES" | jq -r ".[] | select(.indicator == \"$API_RESOURCE_INDICATOR\") | .id") ALL_SCOPE_IDS=$(api_get "/api/resources/$API_RESOURCE_ID/scopes" | jq '[.[].id]') log "Creating saas-vendor role with all scopes..." VENDOR_ROLE_RESPONSE=$(api_post "/api/roles" "{ \"name\": \"saas-vendor\", \"description\": \"SaaS vendor — full platform control across all tenants\", \"type\": \"User\", \"scopeIds\": $ALL_SCOPE_IDS }") VENDOR_ROLE_ID=$(echo "$VENDOR_ROLE_RESPONSE" | jq -r '.id') log "Created saas-vendor role: $VENDOR_ROLE_ID" fi # ============================================================ # Create vendor user # ============================================================ log "Checking for vendor user '$VENDOR_USER'..." VENDOR_USER_ID=$(api_get "/api/users?search=$VENDOR_USER" | jq -r ".[] | select(.username == \"$VENDOR_USER\") | .id") if [ -n "$VENDOR_USER_ID" ]; then log "Vendor user exists: $VENDOR_USER_ID" else log "Creating vendor user '$VENDOR_USER'..." VENDOR_RESPONSE=$(api_post "/api/users" "{ \"username\": \"$VENDOR_USER\", \"password\": \"$VENDOR_PASS\", \"name\": \"$VENDOR_NAME\" }") VENDOR_USER_ID=$(echo "$VENDOR_RESPONSE" | jq -r '.id') log "Created vendor user: $VENDOR_USER_ID" fi # Assign saas-vendor role if [ -n "$VENDOR_ROLE_ID" ] && [ "$VENDOR_ROLE_ID" != "null" ]; then api_post "/api/users/$VENDOR_USER_ID/roles" "{\"roleIds\": [\"$VENDOR_ROLE_ID\"]}" >/dev/null 2>&1 log "Assigned saas-vendor role." fi # ============================================================ # Add vendor to all existing organizations with owner role # ============================================================ log "Adding vendor to all organizations..." ORG_OWNER_ROLE_ID=$(api_get "/api/organization-roles" | jq -r '.[] | select(.name == "owner") | .id') ORGS=$(api_get "/api/organizations") ORG_COUNT=$(echo "$ORGS" | jq 'length') for i in $(seq 0 $((ORG_COUNT - 1))); do ORG_ID=$(echo "$ORGS" | jq -r ".[$i].id") ORG_NAME=$(echo "$ORGS" | jq -r ".[$i].name") api_post "/api/organizations/$ORG_ID/users" "{\"userIds\": [\"$VENDOR_USER_ID\"]}" >/dev/null 2>&1 if [ -n "$ORG_OWNER_ROLE_ID" ] && [ "$ORG_OWNER_ROLE_ID" != "null" ]; then curl -s -X PUT -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d "{\"organizationRoleIds\": [\"$ORG_OWNER_ROLE_ID\"]}" \ "${LOGTO_ENDPOINT}/api/organizations/$ORG_ID/users/$VENDOR_USER_ID/roles" >/dev/null 2>&1 fi log " Added to org '$ORG_NAME' ($ORG_ID) with owner role." done log "" log "=== Vendor seed complete! ===" log " Vendor user: $VENDOR_USER / $VENDOR_PASS" log " Role: saas-vendor (global) + owner (in all orgs)" log " This user has platform:admin scope and cross-tenant access."