From ec9ceec7a7f4737394cf529b118b6a08f8fc4d77 Mon Sep 17 00:00:00 2001 From: Dmitrii Iurco Date: Sun, 26 Apr 2026 03:17:38 -0400 Subject: [PATCH] feat: add show-admin-password and reset-admin-password make targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit make show-admin-password — prints the admin password from the initial setup file if the API hasn't consumed it yet; otherwise prompts to reset make reset-admin-password — generates a strong random password, updates auth_users.json directly, writes it back to the setup file, and prints it prominently so it's easy to copy Also enhances reset_admin_password.py with --show, --generate flags and a clear banner output, and adds both targets to make help. Co-Authored-By: Claude Sonnet 4.6 --- Makefile | 11 ++++ scripts/reset_admin_password.py | 97 +++++++++++++++++++++++++++++---- 2 files changed, 97 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index 8a6a342..ace6c5d 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ test-integration test-integration-readonly \ test-e2e-deps test-e2e-api test-e2e-ui test-e2e-wg test-e2e \ reset-test-admin-pass \ + show-admin-password reset-admin-password \ show-routes add-peer list-peers # Detect docker compose command (v2 plugin preferred, fallback to v1 standalone) @@ -54,6 +55,8 @@ help: @echo " backup - Backup config + data to backups/" @echo " restore - List available backups" @echo " clean - Remove containers and volumes (keeps config/data dirs)" + @echo " show-admin-password - Print the admin password (reads setup file or prompts to reset)" + @echo " reset-admin-password - Generate a new random admin password and print it" @echo "" @echo "Tests:" @echo " test - Run all tests" @@ -271,6 +274,14 @@ ifndef PIC_TEST_ADMIN_PASS endif python3 scripts/reset_admin_password.py "$(PIC_TEST_ADMIN_PASS)" +# ── Admin password management ────────────────────────────────────────────────── + +show-admin-password: + @python3 scripts/reset_admin_password.py --show + +reset-admin-password: + @python3 scripts/reset_admin_password.py --generate + test-phase1: cd api && python3 -m pytest tests/test_network_manager.py tests/test_phase1_endpoints.py -v diff --git a/scripts/reset_admin_password.py b/scripts/reset_admin_password.py index 3961197..bda885f 100644 --- a/scripts/reset_admin_password.py +++ b/scripts/reset_admin_password.py @@ -1,27 +1,102 @@ #!/usr/bin/env python3 -"""Reset admin password directly in auth_users.json — for test environments only.""" +""" +Admin password management utility. + +Usage: + reset_admin_password.py --generate # generate a random password and set it + reset_admin_password.py # set a specific password +""" import sys import os -import json +import secrets +import string sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'api')) +ROOT = os.path.join(os.path.dirname(__file__), '..') +INIT_PW_FILE = os.path.normpath(os.path.join(ROOT, 'data', 'api', '.admin_initial_password')) -def main(): - if len(sys.argv) != 2: - print("Usage: reset_admin_password.py ", file=sys.stderr) - sys.exit(1) - new_password = sys.argv[1] + +def _generate_password(length: int = 20) -> str: + alphabet = string.ascii_letters + string.digits + '!@#$%^&*' + while True: + pw = ''.join(secrets.choice(alphabet) for _ in range(length)) + # Ensure at least one of each character class required by the validator + if (any(c.isupper() for c in pw) + and any(c.islower() for c in pw) + and any(c.isdigit() for c in pw) + and any(c in '!@#$%^&*' for c in pw)): + return pw + + +def _set_password(new_password: str) -> None: from auth_manager import AuthManager - data_dir = os.path.join(os.path.dirname(__file__), '..', 'data', 'api') + data_dir = os.path.normpath(os.path.join(ROOT, 'data', 'api')) os.makedirs(data_dir, exist_ok=True) mgr = AuthManager(data_dir=data_dir, config_dir='/tmp') if mgr.set_password_admin('admin', new_password): - print(f"[OK] Admin password reset successfully.") + print('[OK] Admin password updated in auth_users.json') else: - print("[WARN] Admin user not found — creating admin user.") + print('[WARN] Admin user not found — creating it now') mgr.create_user('admin', new_password, 'admin') - print(f"[OK] Admin user created with provided password.") + print('[OK] Admin user created') + + +def _print_banner(password: str) -> None: + border = '=' * 60 + print(border) + print(' ADMIN PASSWORD') + print(border) + print(f' Username : admin') + print(f' Password : {password}') + print(border) + print(' Save this password — it will NOT be shown again.') + print(border) + + +def main() -> None: + if len(sys.argv) < 2: + print(__doc__, file=sys.stderr) + sys.exit(1) + + arg = sys.argv[1] + + if arg == '--show': + # Show the initial password file if the API hasn't consumed it yet + if os.path.exists(INIT_PW_FILE): + pw = open(INIT_PW_FILE).read().strip() + _print_banner(pw) + print() + print(f' (file: {INIT_PW_FILE})') + print(' The API will delete this file on first start.') + else: + print('Initial password file not found.') + print('The API has already consumed it, or setup has not been run.') + print() + print('To set a new password run:') + print(' make reset-admin-password') + return + + if arg == '--generate': + password = _generate_password() + else: + password = arg + + if len(password) < 10: + print('Error: password must be at least 10 characters', file=sys.stderr) + sys.exit(1) + + _set_password(password) + + # Also update the initial password file so show-admin-password works + os.makedirs(os.path.dirname(INIT_PW_FILE), exist_ok=True) + with open(INIT_PW_FILE, 'w') as f: + f.write(password) + + _print_banner(password) + print(f'\n Also saved to: {INIT_PW_FILE}') + print(' Restart the API container for the change to take effect:') + print(' docker restart cell-api') if __name__ == '__main__':