162 lines
3.9 KiB
Python
162 lines
3.9 KiB
Python
from datetime import datetime
|
|
from uuid import UUID
|
|
|
|
import msgspec
|
|
import uuid7
|
|
|
|
|
|
class Permission(msgspec.Struct, omit_defaults=True):
|
|
uuid: UUID # UUID primary key
|
|
scope: str # Permission scope identifier (e.g. "auth:admin", "myapp:write")
|
|
display_name: str
|
|
domain: str | None = None # If set, scopes permission to this domain
|
|
|
|
|
|
class Role(msgspec.Struct):
|
|
uuid: UUID
|
|
org_uuid: UUID
|
|
display_name: str
|
|
permissions: list[str] = [] # permission UUIDs this role grants
|
|
|
|
|
|
class Org(msgspec.Struct):
|
|
uuid: UUID
|
|
display_name: str
|
|
permissions: list[str] = [] # permission UUIDs this org can grant
|
|
roles: list[Role] = [] # roles belonging to this org
|
|
|
|
|
|
class User(msgspec.Struct, dict=True):
|
|
display_name: str
|
|
role: UUID
|
|
created_at: datetime
|
|
last_seen: datetime | None = None
|
|
visits: int = 0
|
|
|
|
def __post_init__(self):
|
|
self.uuid: UUID | None = None # Convenience field, not serialized
|
|
|
|
@classmethod
|
|
def create(
|
|
cls,
|
|
display_name: str,
|
|
role: UUID,
|
|
created_at: datetime | None = None,
|
|
) -> "User":
|
|
"""Create a new User with auto-generated uuid7."""
|
|
from datetime import timezone
|
|
|
|
user = cls(
|
|
display_name=display_name,
|
|
role=role,
|
|
created_at=created_at or datetime.now(timezone.utc),
|
|
)
|
|
user.uuid = uuid7.create(user.created_at)
|
|
return user
|
|
|
|
|
|
class Credential(msgspec.Struct):
|
|
uuid: UUID
|
|
credential_id: bytes # Long binary ID from the authenticator
|
|
user_uuid: UUID
|
|
aaguid: UUID
|
|
public_key: bytes
|
|
sign_count: int
|
|
created_at: datetime
|
|
last_used: datetime | None = None
|
|
last_verified: datetime | None = None
|
|
|
|
|
|
class Session(msgspec.Struct):
|
|
key: str
|
|
user_uuid: UUID
|
|
credential_uuid: UUID
|
|
host: str | None
|
|
ip: str | None
|
|
user_agent: str | None
|
|
expiry: datetime
|
|
|
|
def metadata(self) -> dict:
|
|
"""Return session metadata for backwards compatibility."""
|
|
return {
|
|
"ip": self.ip,
|
|
"user_agent": self.user_agent,
|
|
"expiry": self.expiry.isoformat(),
|
|
}
|
|
|
|
|
|
class ResetToken(msgspec.Struct):
|
|
key: bytes
|
|
user_uuid: UUID
|
|
expiry: datetime
|
|
token_type: str
|
|
|
|
|
|
class SessionContext(msgspec.Struct):
|
|
session: Session
|
|
user: User
|
|
org: Org
|
|
role: Role
|
|
credential: Credential
|
|
permissions: list[Permission] = []
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
# Internal storage types (different structure for efficient storage)
|
|
# -------------------------------------------------------------------------
|
|
|
|
|
|
class _PermissionData(msgspec.Struct, omit_defaults=True):
|
|
scope: str # Permission scope identifier
|
|
display_name: str
|
|
domain: str | None = None
|
|
orgs: dict[UUID, bool] = {} # org_uuid -> True (which orgs can grant this)
|
|
|
|
|
|
class _OrgData(msgspec.Struct):
|
|
display_name: str
|
|
created_at: datetime | None = None
|
|
|
|
|
|
class _RoleData(msgspec.Struct):
|
|
org: UUID
|
|
display_name: str
|
|
permissions: dict[UUID, bool] = {} # permission_uuid -> True
|
|
|
|
|
|
class _CredentialData(msgspec.Struct):
|
|
credential_id: bytes
|
|
user: UUID
|
|
aaguid: UUID
|
|
public_key: bytes
|
|
sign_count: int
|
|
created_at: datetime
|
|
last_used: datetime | None
|
|
last_verified: datetime | None
|
|
|
|
|
|
class _SessionData(msgspec.Struct):
|
|
user: UUID
|
|
credential: UUID
|
|
host: str | None
|
|
ip: str | None
|
|
user_agent: str | None
|
|
expiry: datetime
|
|
|
|
|
|
class _ResetTokenData(msgspec.Struct):
|
|
user: UUID
|
|
expiry: datetime
|
|
token_type: str
|
|
|
|
|
|
class _DatabaseData(msgspec.Struct, omit_defaults=True):
|
|
permissions: dict[UUID, _PermissionData]
|
|
orgs: dict[UUID, _OrgData]
|
|
roles: dict[UUID, _RoleData]
|
|
users: dict[UUID, User]
|
|
credentials: dict[UUID, _CredentialData]
|
|
sessions: dict[str, _SessionData]
|
|
reset_tokens: dict[bytes, _ResetTokenData]
|
|
v: int = 0
|