Nye-TeeOff/backend/bootstrap_admin_access.py

139 lines
4.2 KiB
Python
Raw Permalink Normal View History

2026-04-17 09:25:32 +02:00
"""
TEE OFF ADMIN ACCESS BOOTSTRAP
---------------------------------------------------------------------------
FUNKSJON: Oppretter eller oppdaterer én administrator uten å påvirke andre.
Passord leses skjult fra terminalen, og 2FA kan genereres/roteres.
STATUS: Trygg erstatning for create_admin.py når flere admins skal eksistere.
---------------------------------------------------------------------------
"""
import argparse
import asyncio
import getpass
import sys
import asyncpg
import pyotp
from passlib.hash import pbkdf2_sha256
from env_config import get_database_url
DB_URL = get_database_url()
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Opprett eller oppdater en admin-bruker trygt.")
parser.add_argument("--username", required=True, help="Brukernavn for admin-brukeren")
parser.add_argument("--email", required=True, help="E-post for admin-brukeren")
parser.add_argument(
"--rotate-2fa",
action="store_true",
help="Generer en ny 2FA-hemmelighet selv om brukeren allerede har en",
)
return parser.parse_args()
async def bootstrap_admin() -> None:
args = parse_args()
username = args.username.strip()
email = args.email.strip().lower()
if not username:
print("❌ Brukernavn kan ikke være tomt.")
sys.exit(1)
if "@" not in email:
print("❌ E-postadressen ser ugyldig ut.")
sys.exit(1)
while True:
password = getpass.getpass("Skriv inn passord: ")
password_confirm = getpass.getpass("Gjenta passord: ")
if password != password_confirm:
print("❌ Passordene er ikke like. Prøv igjen.\n")
continue
if len(password) < 8:
print("⚠️ Advarsel: Passordet bør være minst 8 tegn.")
break
password_hash = pbkdf2_sha256.hash(password)
conn = None
try:
conn = await asyncpg.connect(DB_URL)
existing = await conn.fetchrow(
"""
SELECT id, username, email, otp_secret
FROM admins
WHERE username = $1 OR email = $2
ORDER BY id ASC
LIMIT 1
""",
username,
email,
)
otp_secret = pyotp.random_base32() if (args.rotate_2fa or not existing or not existing["otp_secret"]) else existing["otp_secret"]
if existing:
await conn.execute(
"""
UPDATE admins
SET username = $1,
email = $2,
password_hash = $3,
otp_secret = $4
WHERE id = $5
""",
username,
email,
password_hash,
otp_secret,
existing["id"],
)
action = "oppdatert"
else:
await conn.execute(
"""
INSERT INTO admins (username, email, password_hash, otp_secret)
VALUES ($1, $2, $3, $4)
""",
username,
email,
password_hash,
otp_secret,
)
action = "opprettet"
except asyncpg.UniqueViolationError:
print("❌ Brukernavn eller e-post er allerede i bruk av en annen admin.")
sys.exit(1)
except Exception as exc:
print(f"❌ Kunne ikke bootstrappe admin-brukeren: {type(exc).__name__}")
sys.exit(1)
finally:
if conn is not None:
await conn.close()
provisioning_uri = pyotp.TOTP(otp_secret).provisioning_uri(
name=email or username,
issuer_name="TeeOff.no",
)
print("\n✅ ADMIN BRUKER KLAR")
print("-" * 50)
print(f"Brukeren '{username}' er {action}.")
print("Passordet ble satt skjult i terminalen.")
print("Legg inn denne 2FA-hemmeligheten i authenticator-appen:")
print(f"2FA-nøkkel: {otp_secret}")
print("Alternativt kan du bruke provisioning URI:")
print(provisioning_uri)
print("-" * 50 + "\n")
if __name__ == "__main__":
try:
asyncio.run(bootstrap_admin())
except KeyboardInterrupt:
print("\nAvbrutt.")
sys.exit(0)