#!/bin/bash set -e # Setup Authentik OAuth provider for Reflector # # IMPORTANT: Run this script from your Reflector repository directory (cd ~/reflector) # The script creates files using relative paths: server/reflector/auth/jwt/keys/ # # Usage: ./setup-authentik-oauth.sh # Example: ./setup-authentik-oauth.sh https://authentik.example.com MyPassword123 https://app.example.com AUTHENTIK_URL="${1:-}" ADMIN_PASSWORD="${2:-}" FRONTEND_URL="${3:-}" if [ -z "$AUTHENTIK_URL" ] || [ -z "$ADMIN_PASSWORD" ] || [ -z "$FRONTEND_URL" ]; then echo "Usage: $0 " echo "Example: $0 https://authentik.example.com MyPassword123 https://app.example.com" exit 1 fi # Remove trailing slash from URLs AUTHENTIK_URL="${AUTHENTIK_URL%/}" FRONTEND_URL="${FRONTEND_URL%/}" echo "===========================================" echo "Authentik OAuth Setup for Reflector" echo "===========================================" echo "" echo "Authentik URL: $AUTHENTIK_URL" echo "Frontend URL: $FRONTEND_URL" echo "" # Step 1: Create API token via Django shell echo "Creating API token..." cd ~/authentik || { echo "Error: ~/authentik directory not found"; exit 1; } API_TOKEN=$(sudo docker compose exec -T server python -m manage shell 2>&1 << 'PYTHON' | grep "^TOKEN:" | cut -d: -f2 from authentik.core.models import User, Token, TokenIntents user = User.objects.get(username='akadmin') token, created = Token.objects.update_or_create( identifier='reflector-setup', defaults={ 'user': user, 'intent': TokenIntents.INTENT_API, 'description': 'Reflector setup token', 'expiring': False } ) print(f"TOKEN:{token.key}") PYTHON ) cd - > /dev/null if [ -z "$API_TOKEN" ] || [ "$API_TOKEN" = "null" ]; then echo "Error: Failed to create API token" echo "Make sure Authentik is fully started and akadmin user exists" exit 1 fi echo " -> Got API token" # Step 2: Get authorization flow UUID echo "Getting authorization flow..." FLOW_RESPONSE=$(curl -s "$AUTHENTIK_URL/api/v3/flows/instances/?slug=default-provider-authorization-implicit-consent" \ -H "Authorization: Bearer $API_TOKEN") FLOW_UUID=$(echo "$FLOW_RESPONSE" | jq -r '.results[0].pk') if [ -z "$FLOW_UUID" ] || [ "$FLOW_UUID" = "null" ]; then echo "Error: Could not find authorization flow" echo "Response: $FLOW_RESPONSE" exit 1 fi echo " -> Flow UUID: $FLOW_UUID" # Step 3: Get invalidation flow UUID echo "Getting invalidation flow..." INVALIDATION_RESPONSE=$(curl -s "$AUTHENTIK_URL/api/v3/flows/instances/?slug=default-provider-invalidation-flow" \ -H "Authorization: Bearer $API_TOKEN") INVALIDATION_UUID=$(echo "$INVALIDATION_RESPONSE" | jq -r '.results[0].pk') if [ -z "$INVALIDATION_UUID" ] || [ "$INVALIDATION_UUID" = "null" ]; then echo "Warning: Could not find invalidation flow, using authorization flow" INVALIDATION_UUID="$FLOW_UUID" fi echo " -> Invalidation UUID: $INVALIDATION_UUID" # Step 4: Get scope mappings (email, openid, profile) echo "Getting scope mappings..." SCOPE_RESPONSE=$(curl -s "$AUTHENTIK_URL/api/v3/propertymappings/all/" \ -H "Authorization: Bearer $API_TOKEN") EMAIL_SCOPE=$(echo "$SCOPE_RESPONSE" | jq -r '.results[] | select(.name == "authentik default OAuth Mapping: OpenID '\''email'\''") | .pk') OPENID_SCOPE=$(echo "$SCOPE_RESPONSE" | jq -r '.results[] | select(.name == "authentik default OAuth Mapping: OpenID '\''openid'\''") | .pk') PROFILE_SCOPE=$(echo "$SCOPE_RESPONSE" | jq -r '.results[] | select(.name == "authentik default OAuth Mapping: OpenID '\''profile'\''") | .pk') echo " -> email: $EMAIL_SCOPE" echo " -> openid: $OPENID_SCOPE" echo " -> profile: $PROFILE_SCOPE" # Step 5: Get signing key echo "Getting signing key..." CERT_RESPONSE=$(curl -s "$AUTHENTIK_URL/api/v3/crypto/certificatekeypairs/" \ -H "Authorization: Bearer $API_TOKEN") SIGNING_KEY=$(echo "$CERT_RESPONSE" | jq -r '.results[0].pk') echo " -> Signing key: $SIGNING_KEY" # Step 6: Generate client credentials CLIENT_ID="reflector" CLIENT_SECRET=$(openssl rand -hex 32) # Step 7: Create OAuth2 provider echo "Creating OAuth2 provider..." PROVIDER_RESPONSE=$(curl -s -X POST "$AUTHENTIK_URL/api/v3/providers/oauth2/" \ -H "Authorization: Bearer $API_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"name\": \"Reflector\", \"authorization_flow\": \"$FLOW_UUID\", \"invalidation_flow\": \"$INVALIDATION_UUID\", \"client_type\": \"confidential\", \"client_id\": \"$CLIENT_ID\", \"client_secret\": \"$CLIENT_SECRET\", \"redirect_uris\": [{ \"matching_mode\": \"strict\", \"url\": \"$FRONTEND_URL/api/auth/callback/authentik\" }], \"property_mappings\": [\"$EMAIL_SCOPE\", \"$OPENID_SCOPE\", \"$PROFILE_SCOPE\"], \"signing_key\": \"$SIGNING_KEY\", \"access_token_validity\": \"hours=1\", \"refresh_token_validity\": \"days=30\" }") PROVIDER_ID=$(echo "$PROVIDER_RESPONSE" | jq -r '.pk') if [ -z "$PROVIDER_ID" ] || [ "$PROVIDER_ID" = "null" ]; then # Check if provider already exists if echo "$PROVIDER_RESPONSE" | grep -q "already exists"; then echo " -> Provider already exists, updating..." EXISTING=$(curl -s "$AUTHENTIK_URL/api/v3/providers/oauth2/?name=Reflector" \ -H "Authorization: Bearer $API_TOKEN") PROVIDER_ID=$(echo "$EXISTING" | jq -r '.results[0].pk') CLIENT_ID=$(echo "$EXISTING" | jq -r '.results[0].client_id') # Update secret and scopes curl -s -X PATCH "$AUTHENTIK_URL/api/v3/providers/oauth2/$PROVIDER_ID/" \ -H "Authorization: Bearer $API_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"client_secret\": \"$CLIENT_SECRET\", \"property_mappings\": [\"$EMAIL_SCOPE\", \"$OPENID_SCOPE\", \"$PROFILE_SCOPE\"], \"signing_key\": \"$SIGNING_KEY\" }" > /dev/null else echo "Error: Failed to create provider" echo "Response: $PROVIDER_RESPONSE" exit 1 fi fi echo " -> Provider ID: $PROVIDER_ID" # Step 8: Create application echo "Creating application..." APP_RESPONSE=$(curl -s -X POST "$AUTHENTIK_URL/api/v3/core/applications/" \ -H "Authorization: Bearer $API_TOKEN" \ -H "Content-Type: application/json" \ -d "{ \"name\": \"Reflector\", \"slug\": \"reflector\", \"provider\": $PROVIDER_ID }") if echo "$APP_RESPONSE" | grep -q "already exists"; then echo " -> Application already exists" else APP_SLUG=$(echo "$APP_RESPONSE" | jq -r '.slug') if [ -z "$APP_SLUG" ] || [ "$APP_SLUG" = "null" ]; then echo "Error: Failed to create application" echo "Response: $APP_RESPONSE" exit 1 fi echo " -> Application created: $APP_SLUG" fi # Step 9: Extract public key for JWT verification echo "Extracting public key for JWT verification..." mkdir -p server/reflector/auth/jwt/keys curl -s "$AUTHENTIK_URL/application/o/reflector/jwks/" | \ jq -r '.keys[0].x5c[0]' | \ base64 -d | \ openssl x509 -pubkey -noout > server/reflector/auth/jwt/keys/authentik_public.pem if [ ! -s server/reflector/auth/jwt/keys/authentik_public.pem ]; then echo "Error: Failed to extract public key" exit 1 fi echo " -> Saved to server/reflector/auth/jwt/keys/authentik_public.pem" # Step 10: Update environment files automatically echo "Updating environment files..." # Update server/.env cat >> server/.env << EOF # --- Authentik OAuth (added by setup script) --- AUTH_BACKEND=jwt AUTH_JWT_AUDIENCE=$CLIENT_ID AUTH_JWT_PUBLIC_KEY=authentik_public.pem # --- End JWT Configuration --- EOF echo " -> Updated server/.env" # Update www/.env cat >> www/.env << EOF # --- Authentik OAuth (added by setup script) --- FEATURE_REQUIRE_LOGIN=true AUTHENTIK_ISSUER=$AUTHENTIK_URL/application/o/reflector AUTHENTIK_REFRESH_TOKEN_URL=$AUTHENTIK_URL/application/o/token/ AUTHENTIK_CLIENT_ID=$CLIENT_ID AUTHENTIK_CLIENT_SECRET=$CLIENT_SECRET # --- End Authentik Configuration --- EOF echo " -> Updated www/.env" # Step 11: Restart Reflector services echo "Restarting Reflector services..." docker compose -f docker-compose.prod.yml up -d server worker web echo "" echo "===========================================" echo "Setup complete!" echo "===========================================" echo "" echo "Authentik admin: $AUTHENTIK_URL" echo " Username: akadmin" echo " Password: (provided as argument)" echo "" echo "Frontend: $FRONTEND_URL" echo " Authentication is now required" echo "" echo "Note: Public key saved to server/reflector/auth/jwt/keys/authentik_public.pem" echo " and mounted via docker-compose volume." echo "" echo "===========================================" echo "Configuration values (for reference):" echo "===========================================" echo "" echo "# server/.env" echo "AUTH_BACKEND=jwt" echo "AUTH_JWT_AUDIENCE=$CLIENT_ID" echo "AUTH_JWT_PUBLIC_KEY=authentik_public.pem" echo "" echo "# www/.env" echo "FEATURE_REQUIRE_LOGIN=true" echo "AUTHENTIK_ISSUER=$AUTHENTIK_URL/application/o/reflector" echo "AUTHENTIK_REFRESH_TOKEN_URL=$AUTHENTIK_URL/application/o/token/" echo "AUTHENTIK_CLIENT_ID=$CLIENT_ID" echo "AUTHENTIK_CLIENT_SECRET=$CLIENT_SECRET" echo ""