File size: 4,000 Bytes
f0bfb3b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
"""
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()