Skip to content

Commit 4f16175

Browse files
committed
added setup_google_oauth.sh, untested
1 parent 34cae1b commit 4f16175

1 file changed

Lines changed: 315 additions & 0 deletions

File tree

scripts/setup_google_oauth.sh

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
#!/usr/bin/env bash
2+
#
3+
# setup_google_oauth.sh
4+
#
5+
# Automates the creation of Google OAuth 2.0 credentials for py4web Google SSO.
6+
#
7+
# Prerequisites:
8+
# - gcloud CLI installed and authenticated (run: gcloud auth login)
9+
# - A Google Cloud billing account (required to enable APIs)
10+
#
11+
# Usage:
12+
# ./scripts/setup_google_oauth.sh [OPTIONS]
13+
#
14+
# Options:
15+
# --project-id ID GCP project ID (default: auto-generated)
16+
# --project-name NAME GCP project display name (default: "py4web OAuth")
17+
# --app-name NAME py4web app name (default: "_scaffold")
18+
# --host HOST App host for redirect URIs (default: "localhost:8000")
19+
# --support-email EMAIL OAuth consent screen email (default: gcloud account email)
20+
# --external Set consent screen to external (default: internal)
21+
# --scoped Also output config for OAuth2GoogleScoped
22+
# --help Show this help
23+
#
24+
set -euo pipefail
25+
26+
# ---------- defaults ----------
27+
PROJECT_ID=""
28+
PROJECT_NAME="py4web OAuth"
29+
APP_NAME="_scaffold"
30+
HOST="localhost:8000"
31+
SUPPORT_EMAIL=""
32+
USER_TYPE="INTERNAL"
33+
SCOPED=false
34+
35+
# ---------- parse args ----------
36+
while [[ $# -gt 0 ]]; do
37+
case $1 in
38+
--project-id) PROJECT_ID="$2"; shift 2 ;;
39+
--project-name) PROJECT_NAME="$2"; shift 2 ;;
40+
--app-name) APP_NAME="$2"; shift 2 ;;
41+
--host) HOST="$2"; shift 2 ;;
42+
--support-email) SUPPORT_EMAIL="$2"; shift 2 ;;
43+
--external) USER_TYPE="EXTERNAL"; shift ;;
44+
--scoped) SCOPED=true; shift ;;
45+
--help)
46+
sed -n '3,/^set /p' "$0" | head -n -1
47+
exit 0
48+
;;
49+
*) echo "Unknown option: $1"; exit 1 ;;
50+
esac
51+
done
52+
53+
# ---------- helpers ----------
54+
info() { echo -e "\033[1;34m==>\033[0m $*"; }
55+
ok() { echo -e "\033[1;32m==>\033[0m $*"; }
56+
warn() { echo -e "\033[1;33m==>\033[0m $*"; }
57+
fail() { echo -e "\033[1;31mERROR:\033[0m $*" >&2; exit 1; }
58+
59+
require_cmd() {
60+
command -v "$1" >/dev/null 2>&1 || fail "'$1' is required but not installed."
61+
}
62+
63+
gcp_api() {
64+
# $1 = method, $2 = url, $3 = optional JSON body
65+
local method="$1" url="$2" body="${3:-}"
66+
local token
67+
token=$(gcloud auth print-access-token 2>/dev/null) || fail "Not authenticated. Run: gcloud auth login"
68+
if [[ -n "$body" ]]; then
69+
curl -sf -X "$method" "$url" \
70+
-H "Authorization: Bearer $token" \
71+
-H "Content-Type: application/json" \
72+
-d "$body"
73+
else
74+
curl -sf -X "$method" "$url" \
75+
-H "Authorization: Bearer $token"
76+
fi
77+
}
78+
79+
# ---------- preflight ----------
80+
require_cmd gcloud
81+
require_cmd curl
82+
require_cmd jq
83+
84+
# Verify gcloud is authenticated
85+
info "Checking gcloud authentication..."
86+
ACCOUNT=$(gcloud config get-value account 2>/dev/null) || true
87+
if [[ -z "$ACCOUNT" || "$ACCOUNT" == "(unset)" ]]; then
88+
fail "Not authenticated. Run: gcloud auth login"
89+
fi
90+
ok "Authenticated as $ACCOUNT"
91+
92+
if [[ -z "$SUPPORT_EMAIL" ]]; then
93+
SUPPORT_EMAIL="$ACCOUNT"
94+
fi
95+
96+
# ---------- step 1: create or select project ----------
97+
if [[ -z "$PROJECT_ID" ]]; then
98+
PROJECT_ID="py4web-oauth-$(date +%s | tail -c 7)"
99+
fi
100+
101+
info "Checking if project '$PROJECT_ID' exists..."
102+
if gcloud projects describe "$PROJECT_ID" &>/dev/null; then
103+
ok "Project '$PROJECT_ID' already exists, using it."
104+
else
105+
info "Creating project '$PROJECT_ID'..."
106+
gcloud projects create "$PROJECT_ID" --name="$PROJECT_NAME" --quiet
107+
ok "Project created."
108+
fi
109+
110+
gcloud config set project "$PROJECT_ID" --quiet
111+
112+
# Get project number (needed for REST APIs)
113+
PROJECT_NUMBER=$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')
114+
info "Project number: $PROJECT_NUMBER"
115+
116+
# ---------- step 2: enable required APIs ----------
117+
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."
135+
136+
# ---------- step 3: create OAuth consent screen (brand) ----------
137+
info "Configuring OAuth consent screen..."
138+
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
143+
144+
if [[ -n "$EXISTING_BRAND" ]]; then
145+
ok "OAuth consent screen already configured."
146+
BRAND_NAME="$EXISTING_BRAND"
147+
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."
164+
fi
165+
166+
# ---------- step 4: create OAuth client ----------
167+
info "Creating OAuth 2.0 client credentials..."
168+
169+
CLIENT_BODY=$(jq -n --arg name "py4web SSO Client" '{displayName: $name}')
170+
171+
CLIENT_RESP=$(gcp_api POST \
172+
"https://iap.googleapis.com/v1/${BRAND_NAME}/identityAwareProxyClients" \
173+
"$CLIENT_BODY")
174+
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+
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
183+
184+
ok "OAuth client created."
185+
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.
189+
190+
# Determine the redirect URI
191+
if [[ "$HOST" == localhost* || "$HOST" == 127.0.0.1* ]]; then
192+
SCHEME="http"
193+
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"
198+
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."
226+
fi
227+
228+
# ---------- step 6: output results ----------
229+
echo ""
230+
echo "=============================================="
231+
echo " Google OAuth Credentials for py4web"
232+
echo "=============================================="
233+
echo ""
234+
echo " Client ID: $CLIENT_ID"
235+
echo " Client Secret: $CLIENT_SECRET"
236+
echo ""
237+
echo " Redirect URI: $REDIRECT_URI"
238+
if $SCOPED; then
239+
echo " Scoped Redirect: $SCOPED_REDIRECT_URI"
240+
fi
241+
echo ""
242+
243+
if ! $REDIRECT_SET; then
244+
warn "Redirect URIs must be configured manually:"
245+
echo " 1. Go to: https://console.cloud.google.com/apis/credentials?project=$PROJECT_ID"
246+
echo " 2. Click on the OAuth 2.0 Client ID just created"
247+
echo " 3. Under 'Authorized redirect URIs', add:"
248+
echo " $REDIRECT_URI"
249+
if $SCOPED; then
250+
echo " $SCOPED_REDIRECT_URI"
251+
fi
252+
echo " 4. Under 'Authorized JavaScript origins', add:"
253+
echo " ${SCHEME}://${HOST}"
254+
echo " 5. Click Save"
255+
echo ""
256+
fi
257+
258+
if [[ "$USER_TYPE" == "EXTERNAL" ]]; then
259+
warn "You selected EXTERNAL user type."
260+
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."
262+
echo ""
263+
fi
264+
265+
# ---------- step 7: write py4web settings ----------
266+
echo "----------------------------------------------"
267+
echo " py4web Configuration"
268+
echo "----------------------------------------------"
269+
echo ""
270+
echo " Add to apps/${APP_NAME}/settings.py:"
271+
echo ""
272+
echo " OAUTH2GOOGLE_CLIENT_ID = \"$CLIENT_ID\""
273+
echo " OAUTH2GOOGLE_CLIENT_SECRET = \"$CLIENT_SECRET\""
274+
echo ""
275+
276+
if $SCOPED; then
277+
# Write the scoped credentials JSON file
278+
SECRETS_FILE="apps/${APP_NAME}/private/google_client_secrets.json"
279+
SECRETS_DIR="$(dirname "$SECRETS_FILE")"
280+
281+
echo " For OAuth2GoogleScoped, a secrets file will be written to:"
282+
echo " $SECRETS_FILE"
283+
echo ""
284+
285+
mkdir -p "$SECRETS_DIR"
286+
287+
jq -n \
288+
--arg client_id "$CLIENT_ID" \
289+
--arg client_secret "$CLIENT_SECRET" \
290+
--arg redirect "$SCOPED_REDIRECT_URI" \
291+
--arg project_id "$PROJECT_ID" \
292+
'{
293+
web: {
294+
client_id: $client_id,
295+
client_secret: $client_secret,
296+
project_id: $project_id,
297+
auth_uri: "https://accounts.google.com/o/oauth2/auth",
298+
token_uri: "https://oauth2.googleapis.com/token",
299+
auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs",
300+
redirect_uris: [$redirect]
301+
}
302+
}' > "$SECRETS_FILE"
303+
304+
echo " Add to apps/${APP_NAME}/settings.py:"
305+
echo ""
306+
echo " OAUTH2GOOGLE_SCOPED_CREDENTIALS_FILE = \"private/google_client_secrets.json\""
307+
echo ""
308+
fi
309+
310+
echo "----------------------------------------------"
311+
echo ""
312+
ok "Done! Your Google OAuth credentials are ready."
313+
echo ""
314+
echo " Console: https://console.cloud.google.com/apis/credentials?project=$PROJECT_ID"
315+
echo ""

0 commit comments

Comments
 (0)