aboutsummaryrefslogtreecommitdiff
path: root/jb/models/auth.py
diff options
context:
space:
mode:
authorstuppie2026-09-01 11:56:05 -0600
committerstuppie2026-09-01 11:56:05 -0600
commitc991d4f51254b1c81fa6e0e19b0ba94b8c5f61c4 (patch)
tree34598a150db7c85b7e0ee50d1ced31bda74d271c /jb/models/auth.py
parent3f8d178d38686c1a5d71d94ebccdb61701469e95 (diff)
downloadamt-jb-c991d4f51254b1c81fa6e0e19b0ba94b8c5f61c4.tar.gz
amt-jb-c991d4f51254b1c81fa6e0e19b0ba94b8c5f61c4.zip
add GRApiManager. py-utils to generalresearch
Diffstat (limited to 'jb/models/auth.py')
-rw-r--r--jb/models/auth.py47
1 files changed, 40 insertions, 7 deletions
diff --git a/jb/models/auth.py b/jb/models/auth.py
index 5f31bd3..8b48088 100644
--- a/jb/models/auth.py
+++ b/jb/models/auth.py
@@ -1,5 +1,6 @@
import hashlib
import hmac
+from typing import Any
from pydantic import (
BaseModel,
@@ -8,6 +9,8 @@ from pydantic import (
Field,
TypeAdapter,
computed_field,
+ field_validator,
+ model_validator,
)
from jb.config import settings
@@ -30,29 +33,59 @@ def email_to_product_user_id(email: str) -> str:
return hmac.new(
key=salt_bytes,
msg=normalized_email.encode("utf-8"),
- digestmod=hashlib.sha256,
+ digestmod=hashlib.sha1,
).hexdigest()
-class AuthenticatedUser(BaseModel):
+class User(BaseModel):
"""A user that has been authenticated and exists in THL"""
email: EmailStr = Field()
+ display_name: str | None = Field(default=None, max_length=255)
+
+ @field_validator("email", mode="before")
+ @classmethod
+ def normalize_email(cls, value: Any) -> Any:
+ if not isinstance(value, str):
+ return value
+ if not value.isascii():
+ raise ValueError("email must contain ASCII characters only")
+ return value.lower()
+
+ @model_validator(mode="before")
+ @classmethod
+ def validate_product_user_id(cls, data: Any) -> Any:
+ if not isinstance(data, dict) or "product_user_id" not in data:
+ return data
+
+ provided_id = data["product_user_id"]
+ email = data.get("email")
+ if email is None:
+ return data
+
+ expected_id = email_to_product_user_id(str(email))
+ if not isinstance(provided_id, str) or not hmac.compare_digest(
+ provided_id, expected_id
+ ):
+ raise ValueError(f"product_user_id {provided_id} does not match email {email}")
+
+ # The computed field is authoritative; do not retain the input value.
+ validated_data = dict(data)
+ validated_data.pop("product_user_id")
+ return validated_data
+
@computed_field
+ @property
def product_user_id(self) -> str:
return email_to_product_user_id(self.email)
-class AccountCreate(BaseModel):
- email: EmailStr = Field()
-
-
class AccountLogin(BaseModel):
+ # There is no practical difference between an Account "login" and "create"
email: EmailStr = Field()
-
class MagicLinkExchangeRequest(BaseModel):
model_config = ConfigDict(extra="forbid")