feat: magic-link auth with access requests and RBAC
This commit is contained in:
parent
c2125f063b
commit
c8280f7078
13 changed files with 1058 additions and 2 deletions
|
|
@ -1,3 +1,13 @@
|
|||
from .flow import ApprovalTask, FlowDefinition, FlowRun, FlowVersion
|
||||
from .user import AccessRequest, MagicLinkToken, User, UserSession
|
||||
|
||||
__all__ = ["ApprovalTask", "FlowDefinition", "FlowRun", "FlowVersion"]
|
||||
__all__ = [
|
||||
"AccessRequest",
|
||||
"ApprovalTask",
|
||||
"FlowDefinition",
|
||||
"FlowRun",
|
||||
"FlowVersion",
|
||||
"MagicLinkToken",
|
||||
"User",
|
||||
"UserSession",
|
||||
]
|
||||
|
|
|
|||
72
backend/app/models/user.py
Normal file
72
backend/app/models/user.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
"""Access-management domain models (SQLModel tables)."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from sqlalchemy import Column, DateTime
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
|
||||
def utcnow() -> datetime:
|
||||
return datetime.now(timezone.utc)
|
||||
|
||||
|
||||
def _ts_column() -> Column:
|
||||
return Column(DateTime(timezone=True))
|
||||
|
||||
|
||||
class User(SQLModel, table=True):
|
||||
"""A person with API access. Role is member or admin."""
|
||||
|
||||
__tablename__ = "users"
|
||||
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||
email: str = Field(index=True, unique=True, max_length=320)
|
||||
name: str = Field(default="", max_length=200)
|
||||
# member | admin
|
||||
role: str = Field(default="member", max_length=20)
|
||||
tenant_id: str = Field(default="default", max_length=200)
|
||||
created_at: datetime = Field(default_factory=utcnow, sa_column=_ts_column())
|
||||
|
||||
|
||||
class AccessRequest(SQLModel, table=True):
|
||||
"""A public request for access; an admin approves or rejects it."""
|
||||
|
||||
__tablename__ = "access_requests"
|
||||
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||
name: str = Field(max_length=200)
|
||||
email: str = Field(index=True, max_length=320)
|
||||
reason: str = Field(default="", max_length=2000)
|
||||
# pending | approved | rejected
|
||||
status: str = Field(default="pending", index=True, max_length=20)
|
||||
created_at: datetime = Field(default_factory=utcnow, sa_column=_ts_column())
|
||||
decided_at: datetime | None = Field(default=None, sa_column=_ts_column())
|
||||
decided_by: str | None = Field(default=None, max_length=320)
|
||||
|
||||
|
||||
class MagicLinkToken(SQLModel, table=True):
|
||||
"""A single-use 15-minute login token. Only the sha256 is stored."""
|
||||
|
||||
__tablename__ = "magic_link_tokens"
|
||||
|
||||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||||
user_id: UUID = Field(foreign_key="users.id", index=True)
|
||||
token_hash: str = Field(index=True, unique=True, max_length=64)
|
||||
expires_at: datetime = Field(sa_column=_ts_column())
|
||||
# Set before the session row is created, so a reused token can never
|
||||
# mint a second session.
|
||||
used_at: datetime | None = Field(default=None, sa_column=_ts_column())
|
||||
created_at: datetime = Field(default_factory=utcnow, sa_column=_ts_column())
|
||||
|
||||
|
||||
class UserSession(SQLModel, table=True):
|
||||
"""An opaque session id in the DB (cookie value). Revocable, expirable."""
|
||||
|
||||
__tablename__ = "user_sessions"
|
||||
|
||||
id: str = Field(primary_key=True, max_length=128)
|
||||
user_id: UUID = Field(foreign_key="users.id", index=True)
|
||||
created_at: datetime = Field(default_factory=utcnow, sa_column=_ts_column())
|
||||
expires_at: datetime = Field(sa_column=_ts_column())
|
||||
revoked_at: datetime | None = Field(default=None, sa_column=_ts_column())
|
||||
Loading…
Add table
Add a link
Reference in a new issue