IrisSupport / bootstrap_admin.py
rairo's picture
Add one-off admin bootstrap script
f0bfb3b
Raw
History Blame Contribute Delete
4 kB
"""
bootstrap_admin.py — one-off: create (or promote) the first Iris admin account.
Because there is no public signup, the very first admin must be provisioned out-of-band.
This script creates the Firebase Auth user, sets the custom claim {role: "admin"} (which
the backend's require_auth reads), and writes the iris_staff profile doc. Run it once.
It is idempotent: if the user already exists, it just (re)sets the password + admin claim.
CREDENTIALS — the Firebase service account is read from either:
* env var FIREBASE = the full service-account JSON string (same secret the app uses), OR
* a path to a serviceAccount.json file passed with --service-account
USAGE (PowerShell example):
$env:FIREBASE = Get-Content serviceAccount.json -Raw
python bootstrap_admin.py --email admin@example.com --password '<strong-password>' --name "Iris Admin"
USAGE (bash example):
export FIREBASE="$(cat serviceAccount.json)"
python bootstrap_admin.py --email admin@example.com --password '<strong-password>' --name "Iris Admin"
The password is taken from --password or the ADMIN_PASSWORD env var; never hardcode it here.
"""
import os
import sys
import json
import argparse
from datetime import datetime, timezone
import firebase_admin
from firebase_admin import credentials, auth, firestore
def load_credentials(service_account_path: str):
"""Build a firebase_admin credential from the FIREBASE env var or a JSON file."""
if os.environ.get("FIREBASE"):
return credentials.Certificate(json.loads(os.environ["FIREBASE"]))
if service_account_path and os.path.exists(service_account_path):
return credentials.Certificate(service_account_path)
sys.exit("ERROR: provide credentials via the FIREBASE env var or --service-account <path>.")
def main() -> None:
parser = argparse.ArgumentParser(description="Create or promote the first Iris admin.")
parser.add_argument("--email", required=True, help="Admin email address.")
parser.add_argument("--password", default=os.environ.get("ADMIN_PASSWORD", ""))
parser.add_argument("--name", default="Iris Admin")
parser.add_argument("--service-account", default="serviceAccount.json",
help="Path to a service account JSON (ignored if FIREBASE env is set).")
args = parser.parse_args()
if not args.password:
sys.exit("ERROR: supply the password via --password or the ADMIN_PASSWORD env var.")
cred = load_credentials(args.service_account)
if not firebase_admin._apps:
firebase_admin.initialize_app(cred)
db = firestore.client()
email = args.email.strip().lower()
# Create the user, or update it if it already exists (idempotent).
try:
user = auth.create_user(email=email, password=args.password, display_name=args.name)
print(f"Created user {email} (uid={user.uid}).")
except auth.EmailAlreadyExistsError:
user = auth.get_user_by_email(email)
auth.update_user(user.uid, password=args.password, display_name=args.name, disabled=False)
print(f"User {email} already existed (uid={user.uid}); password + profile updated.")
# Grant the admin role via custom claim — this is what the backend checks.
auth.set_custom_user_claims(user.uid, {"role": "admin"})
print("Set custom claim {role: 'admin'}.")
# Write the staff profile doc the dashboard lists.
db.collection("iris_staff").document(user.uid).set({
"uid": user.uid,
"email": email,
"name": args.name,
"role": "admin",
"disabled": False,
"created_at": datetime.now(timezone.utc).isoformat(),
"created_by": "bootstrap_admin.py",
}, merge=True)
print("Wrote iris_staff profile.")
print("\nDone. Sign in to the dashboard with this email/password.")
print("Note: if the user was already signed in somewhere, the new admin claim")
print("takes effect after the next token refresh (re-login).")
if __name__ == "__main__":
main()