mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 03:10:46 +08:00
feat(auth): add local login, audit, API keys, and system admin UI
Gate netx Web/API/WebCRT with JWT and per-user API tokens, bootstrap an admin with forced password change, and expose users/audit/API-key management under a System section. MCP can reuse data/auth/mcp_token without extra env for local labs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
14f14d34bd
commit
6d4cd741ef
35 changed files with 2699 additions and 21 deletions
|
|
@ -3,11 +3,16 @@ from __future__ import annotations
|
|||
from datetime import datetime
|
||||
from uuid import uuid4
|
||||
|
||||
from sqlalchemy import DateTime, Float, ForeignKey, Integer, String, Text
|
||||
from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Integer, String, Text
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
from sqlalchemy.types import JSON
|
||||
|
||||
from .db import Base
|
||||
|
||||
# JSONB on Postgres; plain JSON elsewhere (unit tests / sqlite).
|
||||
_JsonType = JSON().with_variant(JSONB(), "postgresql")
|
||||
|
||||
|
||||
class AlarmBatch(Base):
|
||||
__tablename__ = "alarm_batches"
|
||||
|
|
@ -430,3 +435,52 @@ class TopologyEdge(Base):
|
|||
discovered_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class AppUser(Base):
|
||||
"""Local netx application user (login account)."""
|
||||
|
||||
__tablename__ = "app_user"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
|
||||
username: Mapped[str] = mapped_column(String(128), unique=True, index=True)
|
||||
password_hash: Mapped[str] = mapped_column(String(255), default="")
|
||||
role: Mapped[str] = mapped_column(String(32), default="user", index=True) # admin | user
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
|
||||
must_change_password: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
created_by: Mapped[str] = mapped_column(String(64), default="")
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class AuditLog(Base):
|
||||
"""Application audit trail for authenticated (and auth) actions."""
|
||||
|
||||
__tablename__ = "audit_log"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
|
||||
ts: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow, index=True)
|
||||
actor_user_id: Mapped[str] = mapped_column(String(64), default="", index=True)
|
||||
actor_username: Mapped[str] = mapped_column(String(128), default="", index=True)
|
||||
action: Mapped[str] = mapped_column(String(128), default="", index=True)
|
||||
method: Mapped[str] = mapped_column(String(16), default="")
|
||||
path: Mapped[str] = mapped_column(String(512), default="", index=True)
|
||||
status_code: Mapped[int] = mapped_column(Integer, default=0)
|
||||
client_ip: Mapped[str] = mapped_column(String(128), default="")
|
||||
user_agent: Mapped[str] = mapped_column(String(512), default="")
|
||||
detail: Mapped[dict] = mapped_column(_JsonType, default=dict)
|
||||
|
||||
|
||||
class ApiToken(Base):
|
||||
"""Long-lived API token (MCP/scripts); hashed at rest."""
|
||||
|
||||
__tablename__ = "api_token"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
|
||||
name: Mapped[str] = mapped_column(String(128), default="")
|
||||
token_hash: Mapped[str] = mapped_column(String(128), unique=True, index=True)
|
||||
user_id: Mapped[str] = mapped_column(String(64), index=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow)
|
||||
expires_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, index=True)
|
||||
last_used_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
revoked_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue