Skip to content
Open

Idp #28

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions auth/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.pat
dev-app-config.json
664 changes: 664 additions & 0 deletions auth/identity-provider-evaluation.md

Large diffs are not rendered by default.

178 changes: 178 additions & 0 deletions auth/setup-zitadel-dev.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#!/usr/bin/env bash
# Setup script for Zitadel development environment
# Creates an OIDC application for modAI-chat and outputs the Client ID
#
# Prerequisites:
# - Zitadel is running (docker compose -f docker-compose-zitadel-develop.yaml up --detach --wait)
# - admin.pat exists in auth/ directory (auto-created by Zitadel init)
#
# Usage:
# ./auth/setup-zitadel-dev.sh

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ZITADEL_URL="http://localhost:8080"
PAT_FILE="$SCRIPT_DIR/admin.pat"
OUTPUT_FILE="$SCRIPT_DIR/dev-app-config.json"

PROJECT_NAME="modAI-chat"
APP_NAME="modAI-chat-frontend"

# Frontend dev server ports
REDIRECT_URIS='["http://localhost:5173/auth/callback","http://localhost:4173/auth/callback"]'
POST_LOGOUT_URIS='["http://localhost:5173/","http://localhost:4173/"]'

echo "=== Zitadel Dev Setup ==="

# Wait for PAT file
if [ ! -f "$PAT_FILE" ]; then
echo "Waiting for admin PAT to be generated..."
for i in $(seq 1 30); do
if [ -f "$PAT_FILE" ]; then
break
fi
sleep 2
done
fi

if [ ! -f "$PAT_FILE" ]; then
echo "ERROR: admin.pat not found at $PAT_FILE"
echo "Make sure Zitadel is running and healthy."
exit 1
fi

PAT=$(cat "$PAT_FILE" | tr -d '[:space:]')
echo "Using admin PAT from $PAT_FILE"

# Helper function for API calls
zitadel_api() {
local method="$1"
local endpoint="$2"
local data="${3:-}"

local args=(
--silent
--show-error
--fail
-X "$method"
-H "Authorization: Bearer $PAT"
-H "Content-Type: application/json"
)

if [ -n "$data" ]; then
args+=(-d "$data")
fi

curl "${args[@]}" "${ZITADEL_URL}${endpoint}" 2>&1
}

# 1. Check if project already exists
echo "Checking for existing project..."
EXISTING_PROJECTS=$(zitadel_api POST "/management/v1/projects/_search" '{"queries":[{"nameQuery":{"name":"'"$PROJECT_NAME"'","method":"TEXT_QUERY_METHOD_EQUALS"}}]}')
EXISTING_PROJECT_COUNT=$(echo "$EXISTING_PROJECTS" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('totalResult','0'))" 2>/dev/null || echo "0")

if [ "$EXISTING_PROJECT_COUNT" != "0" ]; then
PROJECT_ID=$(echo "$EXISTING_PROJECTS" | python3 -c "import sys,json; print(json.load(sys.stdin)['result'][0]['id'])")
echo "Project '$PROJECT_NAME' already exists (ID: $PROJECT_ID)"
else
# Create project
echo "Creating project '$PROJECT_NAME'..."
PROJECT_RESPONSE=$(zitadel_api POST "/management/v1/projects" '{"name":"'"$PROJECT_NAME"'","projectRoleAssertion":true}')
PROJECT_ID=$(echo "$PROJECT_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])")
echo "Created project (ID: $PROJECT_ID)"
fi

# 2. Check if app already exists
echo "Checking for existing app..."
EXISTING_APPS=$(zitadel_api POST "/management/v1/projects/$PROJECT_ID/apps/_search" '{"queries":[{"nameQuery":{"name":"'"$APP_NAME"'","method":"TEXT_QUERY_METHOD_EQUALS"}}]}')
EXISTING_APP_COUNT=$(echo "$EXISTING_APPS" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('totalResult','0'))" 2>/dev/null || echo "0")

if [ "$EXISTING_APP_COUNT" != "0" ]; then
APP_ID=$(echo "$EXISTING_APPS" | python3 -c "import sys,json; print(json.load(sys.stdin)['result'][0]['id'])")
CLIENT_ID=$(echo "$EXISTING_APPS" | python3 -c "import sys,json; r=json.load(sys.stdin)['result'][0]; print(r.get('oidcConfig',{}).get('clientId',''))")
echo "App '$APP_NAME' already exists (ID: $APP_ID, Client ID: $CLIENT_ID)"
else
# Create OIDC User Agent (SPA) application with PKCE
echo "Creating OIDC SPA application '$APP_NAME'..."
APP_RESPONSE=$(zitadel_api POST "/management/v1/projects/$PROJECT_ID/apps/oidc" '{
"name": "'"$APP_NAME"'",
"redirectUris": '"$REDIRECT_URIS"',
"responseTypes": ["OIDC_RESPONSE_TYPE_CODE"],
"grantTypes": ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE"],
"appType": "OIDC_APP_TYPE_USER_AGENT",
"authMethodType": "OIDC_AUTH_METHOD_TYPE_NONE",
"postLogoutRedirectUris": '"$POST_LOGOUT_URIS"',
"devMode": true,
"accessTokenType": "OIDC_TOKEN_TYPE_JWT",
"idTokenRoleAssertion": true,
"idTokenUserinfoAssertion": true
}')

APP_ID=$(echo "$APP_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['appId'])")
CLIENT_ID=$(echo "$APP_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['clientId'])")
echo "Created app (ID: $APP_ID, Client ID: $CLIENT_ID)"
fi

# 3. Create API application for backend introspection (if not exists)
API_APP_NAME="modAI-chat-backend"
echo "Checking for existing API app..."
EXISTING_API_APPS=$(zitadel_api POST "/management/v1/projects/$PROJECT_ID/apps/_search" '{"queries":[{"nameQuery":{"name":"'"$API_APP_NAME"'","method":"TEXT_QUERY_METHOD_EQUALS"}}]}')
EXISTING_API_COUNT=$(echo "$EXISTING_API_APPS" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('totalResult','0'))" 2>/dev/null || echo "0")

if [ "$EXISTING_API_COUNT" != "0" ]; then
API_APP_ID=$(echo "$EXISTING_API_APPS" | python3 -c "import sys,json; print(json.load(sys.stdin)['result'][0]['id'])")
API_CLIENT_ID=$(echo "$EXISTING_API_APPS" | python3 -c "import sys,json; r=json.load(sys.stdin)['result'][0]; print(r.get('apiConfig',{}).get('clientId', r.get('oidcConfig',{}).get('clientId','')))")
echo "API app '$API_APP_NAME' already exists (ID: $API_APP_ID)"
echo "NOTE: Cannot retrieve client secret for existing app. If you need it, delete and recreate."
API_CLIENT_SECRET="<already-created-check-dev-app-config.json>"
else
echo "Creating API application '$API_APP_NAME' for backend introspection..."
API_APP_RESPONSE=$(zitadel_api POST "/management/v1/projects/$PROJECT_ID/apps/api" '{
"name": "'"$API_APP_NAME"'",
"authMethodType": "API_AUTH_METHOD_TYPE_BASIC"
}')

API_APP_ID=$(echo "$API_APP_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['appId'])")
API_CLIENT_ID=$(echo "$API_APP_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['clientId'])")
API_CLIENT_SECRET=$(echo "$API_APP_RESPONSE" | python3 -c "import sys,json; print(json.load(sys.stdin)['clientSecret'])")
echo "Created API app (ID: $API_APP_ID, Client ID: $API_CLIENT_ID)"
fi

# 4. Write config output
cat > "$OUTPUT_FILE" <<EOF
{
"zitadel_url": "$ZITADEL_URL",
"project_id": "$PROJECT_ID",
"frontend": {
"app_id": "$APP_ID",
"client_id": "$CLIENT_ID"
},
"backend": {
"app_id": "$API_APP_ID",
"client_id": "$API_CLIENT_ID",
"client_secret": "$API_CLIENT_SECRET"
}
}
EOF

echo ""
echo "=== Setup Complete ==="
echo ""
echo "Configuration saved to: $OUTPUT_FILE"
echo ""
echo "Frontend OIDC Config:"
echo " VITE_OIDC_AUTHORITY=$ZITADEL_URL"
echo " VITE_OIDC_CLIENT_ID=$CLIENT_ID"
echo " VITE_OIDC_REDIRECT_URI=http://localhost:5173/auth/callback"
echo " VITE_OIDC_POST_LOGOUT_REDIRECT_URI=http://localhost:5173/"
echo ""
echo "Backend Config (add to backend/.env):"
echo " OIDC_ISSUER=$ZITADEL_URL"
echo " OIDC_INTROSPECTION_CLIENT_ID=$API_CLIENT_ID"
echo " OIDC_INTROSPECTION_CLIENT_SECRET=$API_CLIENT_SECRET"
echo ""
echo "Zitadel Console: $ZITADEL_URL/ui/console"
echo "Admin login: zitadel-admin@zitadel.localhost / Password1!"
echo ""
echo "You can register new users via the Zitadel Console or the login page."
2 changes: 1 addition & 1 deletion backend/omni/.env.sample
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
OPENAI_API_KEY=sk-...
JWT_SECRET=your_jwt_secret_key
OIDC_ISSUER=http://localhost:8080
10 changes: 4 additions & 6 deletions backend/omni/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,12 @@ modules:
database_url: "sqlite:///./user_settings.db"
echo: false
session:
class: modai.modules.session.jwt_session_module.JwtSessionModule
class: modai.modules.session.oidc_session_module.OIDCSessionModule
config:
jwt_secret: ${JWT_SECRET}
jwt_algorithm: "HS256"
jwt_expiration_hours: 24
cookie_secure: false
issuer: ${OIDC_ISSUER}
user_id_claim: "sub"
authentication:
class: modai.modules.authentication.password_authentication_module.PasswordAuthenticationModule
class: modai.modules.authentication.oidc_authentication_module.OIDCAuthenticationModule
module_dependencies:
session: "session"
user_store: "user_store"
Expand Down
Loading
Loading