Some checks failed
Tests / Backend Tests (Python) (3.10) (push) Has been cancelled
Tests / Backend Tests (Python) (3.11) (push) Has been cancelled
Tests / Backend Tests (Python) (3.12) (push) Has been cancelled
Tests / Frontend Tests (JS) (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / All Tests Passed (push) Has been cancelled
89 lines
3.1 KiB
Python
89 lines
3.1 KiB
Python
"""User model for authentication and authorization.
|
|
|
|
Designed for single-user now but prepared for multi-user with roles in the future.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
from typing import List, Optional
|
|
|
|
from sqlalchemy import Boolean, DateTime, String, Text, text
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
from sqlalchemy.sql import func
|
|
|
|
from .database import Base
|
|
|
|
|
|
class UserRole(str, Enum):
|
|
"""User roles for authorization.
|
|
|
|
Current implementation: single admin user.
|
|
Future: can be extended with more granular roles.
|
|
"""
|
|
ADMIN = "admin"
|
|
OPERATOR = "operator" # Future: can execute tasks but not manage users
|
|
VIEWER = "viewer" # Future: read-only access
|
|
|
|
|
|
class User(Base):
|
|
"""User model for authentication.
|
|
|
|
Fields prepared for future multi-user support:
|
|
- role: determines access level
|
|
- is_active: allows disabling users without deletion
|
|
- last_login: track user activity
|
|
- password_changed_at: for password rotation policies
|
|
"""
|
|
__tablename__ = "users"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
|
username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, index=True)
|
|
email: Mapped[Optional[str]] = mapped_column(String(255), unique=True, nullable=True)
|
|
hashed_password: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
|
|
# Role-based access control (prepared for future)
|
|
role: Mapped[str] = mapped_column(
|
|
String(20),
|
|
nullable=False,
|
|
server_default=text("'admin'") # Default to admin for single-user setup
|
|
)
|
|
|
|
# Account status
|
|
is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("1"))
|
|
is_superuser: Mapped[bool] = mapped_column(Boolean, nullable=False, server_default=text("0"))
|
|
|
|
# Display name (optional)
|
|
display_name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
|
|
|
|
# Timestamps
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True),
|
|
nullable=False,
|
|
server_default=func.now()
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True),
|
|
nullable=False,
|
|
server_default=func.now(),
|
|
onupdate=func.now()
|
|
)
|
|
last_login: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
password_changed_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
|
|
# Soft delete support
|
|
deleted_at: Mapped[Optional[datetime]] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
|
|
# Relationships
|
|
terminal_commands: Mapped[List["TerminalCommandLog"]] = relationship(
|
|
"TerminalCommandLog", back_populates="user"
|
|
)
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<User id={self.id} username={self.username} role={self.role}>"
|
|
|
|
@property
|
|
def is_admin(self) -> bool:
|
|
"""Check if user has admin privileges."""
|
|
return self.role == UserRole.ADMIN.value or self.is_superuser
|