Finish the database key-in-object refactoring.

This commit is contained in:
2026-01-27 02:11:09 +00:00
parent 0f29544bdb
commit 9bdca1f43a
16 changed files with 415 additions and 412 deletions
+126 -54
View File
@@ -5,26 +5,84 @@ import msgspec
import uuid7
class Permission(msgspec.Struct, omit_defaults=True):
uuid: UUID # UUID primary key
class Permission(msgspec.Struct, dict=True, omit_defaults=True):
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
orgs: dict[UUID, bool] = {} # org_uuid -> True (which orgs can grant this)
def __post_init__(self):
self.uuid: UUID | None = None # Convenience field, not serialized
@classmethod
def create(
cls,
scope: str,
display_name: str,
domain: str | None = None,
) -> "Permission":
"""Create a new Permission with auto-generated uuid7."""
perm = cls(
scope=scope,
display_name=display_name,
domain=domain,
)
perm.uuid = uuid7.create()
return perm
class Role(msgspec.Struct):
uuid: UUID
org_uuid: UUID
class Role(msgspec.Struct, dict=True):
org: UUID
display_name: str
permissions: list[str] = [] # permission UUIDs this role grants
def __post_init__(self):
self.uuid: UUID | None = None # Convenience field, not serialized
class Org(msgspec.Struct):
uuid: UUID
@classmethod
def create(
cls,
org: UUID,
display_name: str,
permissions: list[str] | None = None,
) -> "Role":
"""Create a new Role with auto-generated uuid7."""
role = cls(
org=org,
display_name=display_name,
permissions=permissions or [],
)
role.uuid = uuid7.create()
return role
# Legacy alias for org field
@property
def org_uuid(self) -> UUID:
return self.org
class Org(msgspec.Struct, dict=True):
display_name: str
permissions: list[str] = [] # permission UUIDs this org can grant
roles: list[Role] = [] # roles belonging to this org
def __post_init__(self):
self.uuid: UUID | None = None # Convenience field, not serialized
@classmethod
def create(
cls,
display_name: str,
permissions: list[str] | None = None,
) -> "Org":
"""Create a new Org with auto-generated uuid7."""
org = cls(
display_name=display_name,
permissions=permissions or [],
)
org.uuid = uuid7.create()
return org
class User(msgspec.Struct, dict=True):
display_name: str
@@ -55,10 +113,9 @@ class User(msgspec.Struct, dict=True):
return user
class Credential(msgspec.Struct):
uuid: UUID
class Credential(msgspec.Struct, dict=True):
credential_id: bytes # Long binary ID from the authenticator
user_uuid: UUID
user: UUID
aaguid: UUID
public_key: bytes
sign_count: int
@@ -66,16 +123,57 @@ class Credential(msgspec.Struct):
last_used: datetime | None = None
last_verified: datetime | None = None
def __post_init__(self):
self.uuid: UUID | None = None # Convenience field, not serialized
class Session(msgspec.Struct):
key: str
user_uuid: UUID
credential_uuid: UUID
@classmethod
def create(
cls,
credential_id: bytes,
user: UUID,
aaguid: UUID,
public_key: bytes,
sign_count: int,
created_at: datetime | None = None,
) -> "Credential":
"""Create a new Credential with auto-generated uuid7."""
from datetime import timezone
now = created_at or datetime.now(timezone.utc)
cred = cls(
credential_id=credential_id,
user=user,
aaguid=aaguid,
public_key=public_key,
sign_count=sign_count,
created_at=now,
last_used=now,
last_verified=now,
)
cred.uuid = uuid7.create(now)
return cred
class Session(msgspec.Struct, dict=True):
user: UUID
credential: UUID
host: str | None
ip: str | None
user_agent: str | None
expiry: datetime
def __post_init__(self):
self.key: str | None = None # Convenience field, not serialized
# Legacy aliases
@property
def user_uuid(self) -> UUID:
return self.user
@property
def credential_uuid(self) -> UUID:
return self.credential
def metadata(self) -> dict:
"""Return session metadata for backwards compatibility."""
return {
@@ -85,12 +183,19 @@ class Session(msgspec.Struct):
}
class ResetToken(msgspec.Struct):
key: bytes
user_uuid: UUID
class ResetToken(msgspec.Struct, dict=True):
user: UUID
expiry: datetime
token_type: str
def __post_init__(self):
self.key: bytes | None = None # Convenience field, not serialized
# Legacy alias
@property
def user_uuid(self) -> UUID:
return self.user
class SessionContext(msgspec.Struct):
session: Session
@@ -106,13 +211,6 @@ class SessionContext(msgspec.Struct):
# -------------------------------------------------------------------------
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
@@ -124,38 +222,12 @@ class _RoleData(msgspec.Struct):
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]
permissions: dict[UUID, Permission]
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]
credentials: dict[UUID, Credential]
sessions: dict[str, Session]
reset_tokens: dict[bytes, ResetToken]
v: int = 0