Fix milestones 422 + acc-mgr user + reset-apikey endpoint
- Fix: /milestones?project_id= now accepts project_code (str) not just int
- Add: built-in acc-mgr user created on wizard init (account-manager role, no login, undeletable)
- Add: POST /users/{id}/reset-apikey with permission-based access control
- Add: GET /auth/me/apikey-permissions for frontend capability check
- Add: user.reset-self-apikey and user.reset-apikey permissions
- Protect admin and acc-mgr accounts from deletion
- Block acc-mgr from login (/auth/token returns 403)
This commit is contained in:
@@ -126,6 +126,9 @@ DEFAULT_PERMISSIONS = [
|
||||
("account.create", "Create HarborForge accounts", "account"),
|
||||
# User management
|
||||
("user.manage", "Manage users", "admin"),
|
||||
# API key management
|
||||
("user.reset-self-apikey", "Reset own API key", "user"),
|
||||
("user.reset-apikey", "Reset any user's API key", "admin"),
|
||||
# Monitor
|
||||
("monitor.read", "View monitor", "monitor"),
|
||||
("monitor.manage", "Manage monitor", "monitor"),
|
||||
@@ -165,6 +168,7 @@ _MGR_PERMISSIONS = {
|
||||
"task.close", "task.reopen_closed", "task.reopen_completed",
|
||||
"propose.accept", "propose.reject", "propose.reopen",
|
||||
"monitor.read",
|
||||
"user.reset-self-apikey",
|
||||
}
|
||||
|
||||
# dev: day-to-day development work — no freeze/start/close milestone, no accept/reject propose
|
||||
@@ -174,6 +178,7 @@ _DEV_PERMISSIONS = {
|
||||
"milestone.read",
|
||||
"task.close", "task.reopen_closed", "task.reopen_completed",
|
||||
"monitor.read",
|
||||
"user.reset-self-apikey",
|
||||
}
|
||||
|
||||
_ACCOUNT_MANAGER_PERMISSIONS = {
|
||||
@@ -246,6 +251,43 @@ def init_admin_role(db: Session, admin_user: models.User) -> None:
|
||||
logger.info("Default roles setup complete (admin, mgr, dev, guest)")
|
||||
|
||||
|
||||
def init_acc_mgr_user(db: Session) -> models.User | None:
|
||||
"""Create the built-in acc-mgr user if not exists.
|
||||
|
||||
This user:
|
||||
- Has role 'account-manager' (can only create accounts)
|
||||
- Cannot log in (no password, hashed_password=None)
|
||||
- Cannot be deleted (enforced in delete endpoint)
|
||||
- Is created automatically after wizard initialization
|
||||
"""
|
||||
username = "acc-mgr"
|
||||
existing = db.query(models.User).filter(models.User.username == username).first()
|
||||
if existing:
|
||||
logger.info("acc-mgr user already exists (id=%d), skipping", existing.id)
|
||||
return existing
|
||||
|
||||
# Find account-manager role
|
||||
acc_mgr_role = db.query(Role).filter(Role.name == "account-manager").first()
|
||||
if not acc_mgr_role:
|
||||
logger.warning("account-manager role not found, skipping acc-mgr user creation")
|
||||
return None
|
||||
|
||||
user = models.User(
|
||||
username=username,
|
||||
email="acc-mgr@harborforge.internal",
|
||||
full_name="Account Manager",
|
||||
hashed_password=None, # Cannot log in — no password
|
||||
is_admin=False,
|
||||
is_active=True,
|
||||
role_id=acc_mgr_role.id,
|
||||
)
|
||||
db.add(user)
|
||||
db.commit()
|
||||
db.refresh(user)
|
||||
logger.info("Created acc-mgr user (id=%d) with account-manager role", user.id)
|
||||
return user
|
||||
|
||||
|
||||
def run_init(db: Session) -> None:
|
||||
"""Main initialization entry point. Reads config from shared volume."""
|
||||
config = load_config()
|
||||
@@ -267,6 +309,9 @@ def run_init(db: Session) -> None:
|
||||
if admin_user:
|
||||
init_admin_role(db, admin_user)
|
||||
|
||||
# Built-in acc-mgr user (after roles are created)
|
||||
init_acc_mgr_user(db)
|
||||
|
||||
# Default project
|
||||
project_cfg = config.get("default_project")
|
||||
if project_cfg and admin_user:
|
||||
|
||||
Reference in New Issue
Block a user