Skip to content

Commit d1aeee9

Browse files
committed
imrpoved scripts/setup_google_oauth.sh
1 parent 4f16175 commit d1aeee9

1 file changed

Lines changed: 171 additions & 104 deletions

File tree

scripts/setup_google_oauth.sh

Lines changed: 171 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
# setup_google_oauth.sh
44
#
55
# Automates the creation of Google OAuth 2.0 credentials for py4web Google SSO.
6+
# Works for both organization and personal Google accounts.
67
#
78
# Prerequisites:
89
# - gcloud CLI installed and authenticated (run: gcloud auth login)
9-
# - A Google Cloud billing account (required to enable APIs)
10+
# - A Google Cloud billing account (may be required to enable APIs)
1011
#
1112
# Usage:
1213
# ./scripts/setup_google_oauth.sh [OPTIONS]
@@ -66,16 +67,27 @@ gcp_api() {
6667
local token
6768
token=$(gcloud auth print-access-token 2>/dev/null) || fail "Not authenticated. Run: gcloud auth login"
6869
if [[ -n "$body" ]]; then
69-
curl -sf -X "$method" "$url" \
70+
curl -s --connect-timeout 10 --max-time 30 -X "$method" "$url" \
7071
-H "Authorization: Bearer $token" \
7172
-H "Content-Type: application/json" \
7273
-d "$body"
7374
else
74-
curl -sf -X "$method" "$url" \
75+
curl -s --connect-timeout 10 --max-time 30 -X "$method" "$url" \
7576
-H "Authorization: Bearer $token"
7677
fi
7778
}
7879

80+
open_url() {
81+
local url="$1"
82+
if command -v xdg-open &>/dev/null; then
83+
xdg-open "$url" 2>/dev/null || true
84+
elif command -v open &>/dev/null; then
85+
open "$url" 2>/dev/null || true
86+
else
87+
echo " Open in your browser: $url"
88+
fi
89+
}
90+
7991
# ---------- preflight ----------
8092
require_cmd gcloud
8193
require_cmd curl
@@ -113,125 +125,182 @@ gcloud config set project "$PROJECT_ID" --quiet
113125
PROJECT_NUMBER=$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')
114126
info "Project number: $PROJECT_NUMBER"
115127

128+
# Check if project belongs to an organization
129+
ORG_ID=$(gcloud projects describe "$PROJECT_ID" --format='value(parent.id)' 2>/dev/null) || true
130+
PARENT_TYPE=$(gcloud projects describe "$PROJECT_ID" --format='value(parent.type)' 2>/dev/null) || true
131+
HAS_ORG=false
132+
if [[ "$PARENT_TYPE" == "organization" && -n "$ORG_ID" ]]; then
133+
HAS_ORG=true
134+
info "Project belongs to organization: $ORG_ID"
135+
else
136+
info "Project is a personal project (no organization)."
137+
fi
138+
116139
# ---------- step 2: enable required APIs ----------
117140
info "Enabling required APIs (this may take a moment)..."
118-
gcloud services enable \
119-
iap.googleapis.com \
120-
people.googleapis.com \
121-
oauth2.googleapis.com \
122-
--quiet 2>/dev/null || {
123-
warn "Some APIs may require billing. Checking..."
124-
# Try enabling one at a time for clearer errors
125-
for api in iap.googleapis.com people.googleapis.com; do
126-
if ! gcloud services enable "$api" --quiet 2>/dev/null; then
127-
warn "Could not enable $api — billing may be required."
128-
echo " Enable billing: https://console.cloud.google.com/billing/linkedaccount?project=$PROJECT_ID"
129-
echo " Then re-run this script with: --project-id $PROJECT_ID"
130-
fail "Billing required to enable APIs."
131-
fi
132-
done
133-
}
134-
ok "APIs enabled."
135141

136-
# ---------- step 3: create OAuth consent screen (brand) ----------
137-
info "Configuring OAuth consent screen..."
142+
REQUIRED_APIS=(people.googleapis.com)
143+
if $HAS_ORG; then
144+
REQUIRED_APIS+=(iap.googleapis.com)
145+
fi
138146

139-
# Check if brand already exists
140-
EXISTING_BRAND=$(gcp_api GET \
141-
"https://iap.googleapis.com/v1/projects/$PROJECT_NUMBER/brands" \
142-
| jq -r '.brands[0].name // empty' 2>/dev/null) || true
147+
for api in "${REQUIRED_APIS[@]}"; do
148+
if ! gcloud services enable "$api" --quiet 2>/dev/null; then
149+
warn "Could not enable $api — billing may be required."
150+
echo " Enable billing: https://console.cloud.google.com/billing/linkedaccount?project=$PROJECT_ID"
151+
echo " Then re-run this script with: --project-id $PROJECT_ID"
152+
fail "Billing required to enable APIs."
153+
fi
154+
done
155+
ok "APIs enabled."
143156

144-
if [[ -n "$EXISTING_BRAND" ]]; then
145-
ok "OAuth consent screen already configured."
146-
BRAND_NAME="$EXISTING_BRAND"
157+
# ---------- compute redirect URIs ----------
158+
if [[ "$HOST" == localhost* || "$HOST" == 127.0.0.1* ]]; then
159+
SCHEME="http"
147160
else
148-
BRAND_BODY=$(jq -n \
149-
--arg title "$PROJECT_NAME" \
150-
--arg email "$SUPPORT_EMAIL" \
151-
'{applicationTitle: $title, supportEmail: $email}')
152-
153-
BRAND_RESP=$(gcp_api POST \
154-
"https://iap.googleapis.com/v1/projects/$PROJECT_NUMBER/brands" \
155-
"$BRAND_BODY")
156-
157-
BRAND_NAME=$(echo "$BRAND_RESP" | jq -r '.name // empty')
158-
if [[ -z "$BRAND_NAME" ]]; then
159-
warn "Could not create brand via API. Response:"
160-
echo "$BRAND_RESP"
161-
fail "OAuth consent screen creation failed."
162-
fi
163-
ok "OAuth consent screen created."
161+
SCHEME="https"
164162
fi
163+
REDIRECT_URI="${SCHEME}://${HOST}/${APP_NAME}/auth/plugin/oauth2google/callback"
164+
SCOPED_REDIRECT_URI="${SCHEME}://${HOST}/${APP_NAME}/auth/plugin/oauth2googlescoped/callback"
165165

166-
# ---------- step 4: create OAuth client ----------
167-
info "Creating OAuth 2.0 client credentials..."
166+
# ==========================================================================
167+
# Two paths: organization projects use the IAP API (fully automated),
168+
# personal projects use interactive browser-assisted flow.
169+
# ==========================================================================
168170

169-
CLIENT_BODY=$(jq -n --arg name "py4web SSO Client" '{displayName: $name}')
171+
if $HAS_ORG; then
172+
# ---- ORGANIZATION PATH: fully automated via IAP API ----
170173

171-
CLIENT_RESP=$(gcp_api POST \
172-
"https://iap.googleapis.com/v1/${BRAND_NAME}/identityAwareProxyClients" \
173-
"$CLIENT_BODY")
174+
# step 3: create OAuth consent screen (brand)
175+
info "Configuring OAuth consent screen via IAP API..."
174176

175-
CLIENT_ID=$(echo "$CLIENT_RESP" | jq -r '.name // empty' | awk -F/ '{print $NF}')
176-
CLIENT_SECRET=$(echo "$CLIENT_RESP" | jq -r '.secret // empty')
177+
BRAND_LIST_RESP=$(gcp_api GET \
178+
"https://iap.googleapis.com/v1/projects/$PROJECT_NUMBER/brands") || true
177179

178-
if [[ -z "$CLIENT_ID" || -z "$CLIENT_SECRET" ]]; then
179-
warn "IAP client creation response:"
180-
echo "$CLIENT_RESP"
181-
fail "Could not create OAuth client credentials."
182-
fi
180+
if [[ -z "$BRAND_LIST_RESP" ]]; then
181+
fail "Cannot query OAuth brands. Check that iap.googleapis.com is enabled and you have Owner/Editor role."
182+
fi
183183

184-
ok "OAuth client created."
184+
EXISTING_BRAND=$(echo "$BRAND_LIST_RESP" | jq -r '.brands[0].name // empty' 2>/dev/null) || true
185185

186-
# ---------- step 5: configure redirect URIs ----------
187-
# The IAP API creates clients but redirect URIs must be set via the Cloud Console
188-
# or the OAuth2 config API. We'll try the newer endpoint.
186+
if [[ -n "$EXISTING_BRAND" ]]; then
187+
ok "OAuth consent screen already configured."
188+
BRAND_NAME="$EXISTING_BRAND"
189+
else
190+
BRAND_BODY=$(jq -n \
191+
--arg title "$PROJECT_NAME" \
192+
--arg email "$SUPPORT_EMAIL" \
193+
'{applicationTitle: $title, supportEmail: $email}')
194+
195+
BRAND_RESP=$(gcp_api POST \
196+
"https://iap.googleapis.com/v1/projects/$PROJECT_NUMBER/brands" \
197+
"$BRAND_BODY")
198+
199+
if [[ -z "$BRAND_RESP" ]]; then
200+
fail "OAuth consent screen creation failed (empty response)."
201+
fi
202+
203+
BRAND_NAME=$(echo "$BRAND_RESP" | jq -r '.name // empty')
204+
if [[ -z "$BRAND_NAME" ]]; then
205+
warn "Response: $BRAND_RESP"
206+
fail "OAuth consent screen creation failed."
207+
fi
208+
ok "OAuth consent screen created."
209+
fi
210+
211+
# step 4: create OAuth client
212+
info "Creating OAuth 2.0 client credentials..."
213+
214+
CLIENT_BODY=$(jq -n --arg name "py4web SSO Client" '{displayName: $name}')
215+
216+
CLIENT_RESP=$(gcp_api POST \
217+
"https://iap.googleapis.com/v1/${BRAND_NAME}/identityAwareProxyClients" \
218+
"$CLIENT_BODY")
219+
220+
CLIENT_ID=$(echo "$CLIENT_RESP" | jq -r '.name // empty' | awk -F/ '{print $NF}')
221+
CLIENT_SECRET=$(echo "$CLIENT_RESP" | jq -r '.secret // empty')
222+
223+
if [[ -z "$CLIENT_ID" || -z "$CLIENT_SECRET" ]]; then
224+
warn "Response: $CLIENT_RESP"
225+
fail "Could not create OAuth client credentials."
226+
fi
227+
228+
ok "OAuth client created via IAP API."
229+
230+
# Try to set redirect URIs (may not work via public API)
231+
REDIRECT_SET=false
232+
info "Attempting to configure redirect URIs..."
233+
# The IAP-created clients need redirect URIs set via Console
234+
warn "Redirect URIs must be configured in the Cloud Console (see instructions below)."
189235

190-
# Determine the redirect URI
191-
if [[ "$HOST" == localhost* || "$HOST" == 127.0.0.1* ]]; then
192-
SCHEME="http"
193236
else
194-
SCHEME="https"
195-
fi
196-
REDIRECT_URI="${SCHEME}://${HOST}/${APP_NAME}/auth/plugin/oauth2google/callback"
197-
SCOPED_REDIRECT_URI="${SCHEME}://${HOST}/${APP_NAME}/auth/plugin/oauth2googlescoped/callback"
237+
# ---- PERSONAL ACCOUNT PATH: browser-assisted ----
238+
239+
info "Personal account detected. Using browser-assisted setup."
240+
echo ""
241+
242+
# step 3: consent screen — must be done in browser
243+
CONSENT_URL="https://console.cloud.google.com/apis/credentials/consent?project=$PROJECT_ID"
244+
echo " Step 1: Configure the OAuth consent screen"
245+
echo ""
246+
echo " Opening the OAuth consent screen configuration page..."
247+
echo " If it doesn't open automatically, visit:"
248+
echo " $CONSENT_URL"
249+
echo ""
250+
echo " In the consent screen form:"
251+
echo " - User Type: External (select and click Create)"
252+
echo " - App name: $PROJECT_NAME"
253+
echo " - User support email: $SUPPORT_EMAIL"
254+
echo " - Developer contact email: $SUPPORT_EMAIL"
255+
echo " - Click 'Save and Continue' through Scopes and Test Users"
256+
echo ""
257+
open_url "$CONSENT_URL"
258+
259+
read -rp " Press Enter once you've saved the consent screen... "
260+
echo ""
261+
262+
# step 4: create credentials — must be done in browser
263+
# Pre-fill as much as possible via URL parameters
264+
CRED_URL="https://console.cloud.google.com/apis/credentials/oauthclient?project=$PROJECT_ID"
265+
echo " Step 2: Create OAuth 2.0 Client ID"
266+
echo ""
267+
echo " Opening the credential creation page..."
268+
echo " If it doesn't open automatically, visit:"
269+
echo " $CRED_URL"
270+
echo ""
271+
echo " In the form:"
272+
echo " - Application type: Web application"
273+
echo " - Name: py4web SSO Client"
274+
echo " - Authorized JavaScript origins: ${SCHEME}://${HOST}"
275+
echo " - Authorized redirect URIs:"
276+
echo " $REDIRECT_URI"
277+
if $SCOPED; then
278+
echo " $SCOPED_REDIRECT_URI"
279+
fi
280+
echo " - Click Create"
281+
echo ""
282+
echo " After creation, Google will show the Client ID and Secret."
283+
echo ""
284+
open_url "$CRED_URL"
285+
286+
read -rp " Paste Client ID: " CLIENT_ID
287+
read -rp " Paste Client Secret: " CLIENT_SECRET
288+
289+
if [[ -z "$CLIENT_ID" || -z "$CLIENT_SECRET" ]]; then
290+
fail "Client ID and Secret are required."
291+
fi
198292

199-
info "Configuring redirect URIs..."
200-
201-
# Try to set redirect URIs via the Cloud Console REST API
202-
OAUTH_CLIENT_FULL_ID="projects/$PROJECT_NUMBER/brands/$PROJECT_NUMBER/identityAwareProxyClients/$CLIENT_ID"
203-
204-
# The redirect URI configuration often requires manual steps.
205-
# We'll attempt to use the newer Google Auth Platform API if available.
206-
REDIRECT_SET=false
207-
208-
# Try the googleapis.com credentials API
209-
CRED_BODY=$(jq -n \
210-
--arg redirect "$REDIRECT_URI" \
211-
--arg scoped_redirect "$SCOPED_REDIRECT_URI" \
212-
--arg origin "${SCHEME}://${HOST}" \
213-
'{
214-
web: {
215-
redirect_uris: [$redirect, $scoped_redirect],
216-
javascript_origins: [$origin]
217-
}
218-
}')
219-
220-
# This endpoint may not be publicly available; we try and fall back gracefully
221-
if gcp_api PUT \
222-
"https://oauth2.googleapis.com/v1/projects/$PROJECT_NUMBER/oauthClients/$CLIENT_ID" \
223-
"$CRED_BODY" &>/dev/null; then
224-
REDIRECT_SET=true
225-
ok "Redirect URIs configured automatically."
293+
ok "Credentials received."
294+
REDIRECT_SET=true # User configured redirect URIs manually
226295
fi
227296

228-
# ---------- step 6: output results ----------
297+
# ---------- output results ----------
229298
echo ""
230299
echo "=============================================="
231300
echo " Google OAuth Credentials for py4web"
232301
echo "=============================================="
233302
echo ""
234-
echo " Client ID: $CLIENT_ID"
303+
echo " Client ID: $CLIENT_ID"
235304
echo " Client Secret: $CLIENT_SECRET"
236305
echo ""
237306
echo " Redirect URI: $REDIRECT_URI"
@@ -240,7 +309,7 @@ if $SCOPED; then
240309
fi
241310
echo ""
242311

243-
if ! $REDIRECT_SET; then
312+
if [[ "${REDIRECT_SET:-false}" != "true" ]]; then
244313
warn "Redirect URIs must be configured manually:"
245314
echo " 1. Go to: https://console.cloud.google.com/apis/credentials?project=$PROJECT_ID"
246315
echo " 2. Click on the OAuth 2.0 Client ID just created"
@@ -256,13 +325,12 @@ if ! $REDIRECT_SET; then
256325
fi
257326

258327
if [[ "$USER_TYPE" == "EXTERNAL" ]]; then
259-
warn "You selected EXTERNAL user type."
328+
warn "External consent screen: until the app is verified, only test users can log in."
260329
echo " Add test users at: https://console.cloud.google.com/apis/credentials/consent?project=$PROJECT_ID"
261-
echo " Until the app is verified, only test users can log in."
262330
echo ""
263331
fi
264332

265-
# ---------- step 7: write py4web settings ----------
333+
# ---------- write py4web settings ----------
266334
echo "----------------------------------------------"
267335
echo " py4web Configuration"
268336
echo "----------------------------------------------"
@@ -274,7 +342,6 @@ echo " OAUTH2GOOGLE_CLIENT_SECRET = \"$CLIENT_SECRET\""
274342
echo ""
275343

276344
if $SCOPED; then
277-
# Write the scoped credentials JSON file
278345
SECRETS_FILE="apps/${APP_NAME}/private/google_client_secrets.json"
279346
SECRETS_DIR="$(dirname "$SECRETS_FILE")"
280347

@@ -301,7 +368,7 @@ if $SCOPED; then
301368
}
302369
}' > "$SECRETS_FILE"
303370

304-
echo " Add to apps/${APP_NAME}/settings.py:"
371+
echo " Also add to apps/${APP_NAME}/settings.py:"
305372
echo ""
306373
echo " OAUTH2GOOGLE_SCOPED_CREDENTIALS_FILE = \"private/google_client_secrets.json\""
307374
echo ""

0 commit comments

Comments
 (0)