55
66import json
77import time
8- from typing import Any , Optional
8+ from typing import Any , Optional , Union
99
1010import httpx
1111from pydantic import ValidationError
1616 AnonymousSessionContext ,
1717 AnonymousSessionIntrospection ,
1818 AnonymousTokenResponse ,
19+ CreateAnonymousSessionOptions ,
1920)
2021from auth0_server_python .encryption .encrypt import decrypt , encrypt
2122from auth0_server_python .error import (
2223 AnonymousApiError ,
2324 AnonymousClientNotEnabledError ,
2425 AnonymousClientNotSupportedError ,
26+ AnonymousCreateError ,
2527 AnonymousFeatureNotEnabledError ,
28+ AnonymousIntrospectError ,
2629 AnonymousLogoutError ,
2730 AnonymousResourceServerError ,
2831 AnonymousScopeError ,
29- AnonymousSessionCreateError ,
30- AnonymousSessionIntrospectError ,
3132 AnonymousTokenError ,
3233 ConfigurationError ,
3334 DomainResolverError ,
@@ -211,13 +212,13 @@ def _map_anonymous_error(
211212 return AnonymousScopeError (description , error_data )
212213
213214 if operation == "create" :
214- return AnonymousSessionCreateError (description , cause = error_data )
215+ return AnonymousCreateError (description , cause = error_data )
215216 if operation == "token" :
216217 return AnonymousTokenError (description , error_data )
217218 if operation == "logout" :
218219 return AnonymousLogoutError (description , error_data )
219220 if operation == "introspect" :
220- return AnonymousSessionIntrospectError (description , error_data )
221+ return AnonymousIntrospectError (description , error_data )
221222 return AnonymousApiError (code or "anonymous_error" , description , error_data )
222223
223224 # ============================================================================
@@ -232,25 +233,25 @@ def _validate_metadata(metadata: Optional[dict[str, Any]]) -> None:
232233 metadata: The metadata dict to validate, or None.
233234
234235 Raises:
235- AnonymousSessionCreateError : metadata is not a dict, contains a
236+ AnonymousCreateError : metadata is not a dict, contains a
236237 disallowed key, a non-string value, or exceeds 1KB.
237238 """
238239 if metadata is None :
239240 return
240241 if not isinstance (metadata , dict ):
241- raise AnonymousSessionCreateError ("metadata must be a JSON object" , code = "invalid_metadata" )
242+ raise AnonymousCreateError ("metadata must be a JSON object" , code = "invalid_metadata" )
242243 for key , value in metadata .items ():
243244 if key in _DANGEROUS_METADATA_KEYS :
244- raise AnonymousSessionCreateError (
245+ raise AnonymousCreateError (
245246 f"metadata key '{ key } ' is not allowed" , code = "invalid_metadata"
246247 )
247248 if not isinstance (value , str ):
248- raise AnonymousSessionCreateError (
249+ raise AnonymousCreateError (
249250 f"metadata value for key '{ key } ' must be a string" , code = "invalid_metadata"
250251 )
251252 size = len (json .dumps (metadata ).encode ("utf-8" ))
252253 if size > _METADATA_MAX_BYTES :
253- raise AnonymousSessionCreateError (
254+ raise AnonymousCreateError (
254255 "metadata exceeds the 1KB size limit" , code = "metadata_too_large"
255256 )
256257
@@ -325,7 +326,7 @@ async def _create_session_at(
325326 The newly created AnonymousSession, with is_new=True.
326327
327328 Raises:
328- AnonymousSessionCreateError : The request failed, or the response
329+ AnonymousCreateError : The request failed, or the response
329330 was invalid or missing required fields.
330331 """
331332 base_url = f"https://{ domain } "
@@ -343,7 +344,7 @@ async def _create_session_at(
343344 try :
344345 response = await client .post (f"{ base_url } /anonymous/token" , json = body )
345346 except httpx .HTTPError as e :
346- raise AnonymousSessionCreateError (
347+ raise AnonymousCreateError (
347348 "Failed to reach the anonymous token endpoint"
348349 ) from e
349350
@@ -352,18 +353,18 @@ async def _create_session_at(
352353 mapped = self ._map_anonymous_error (response .status_code , error_data , "create" )
353354 if isinstance (mapped , _AnonymousSessionExpired ):
354355 # Internal-only type must never escape.
355- raise AnonymousSessionCreateError (str (mapped ))
356+ raise AnonymousCreateError (str (mapped ))
356357 raise mapped
357358
358359 try :
359360 token_response = AnonymousTokenResponse .model_validate (response .json ())
360361 except (json .JSONDecodeError , ValueError , ValidationError ) as e :
361- raise AnonymousSessionCreateError (
362+ raise AnonymousCreateError (
362363 "Failed to parse anonymous token response"
363364 ) from e
364365
365366 if not token_response .session_token :
366- raise AnonymousSessionCreateError ("Anonymous token response missing required fields" )
367+ raise AnonymousCreateError ("Anonymous token response missing required fields" )
367368
368369 now = int (time .time ())
369370 context = AnonymousSessionContext (
@@ -527,6 +528,7 @@ async def get_session_token_for_injection(
527528
528529 async def create_session (
529530 self ,
531+ options : Optional [Union [CreateAnonymousSessionOptions , dict [str , Any ]]] = None ,
530532 * ,
531533 audience : Optional [str ] = None ,
532534 scope : Optional [str ] = None ,
@@ -536,22 +538,37 @@ async def create_session(
536538 """Mint a fresh anon@<uuid> identity.
537539
538540 Args:
539- audience: Audience for the session. Falls back to the client's
540- configured default when omitted.
541- scope: Scope for the session. Falls back to the client's
542- configured default when omitted.
541+ options: Optional bundle of audience/scope/metadata, accepted as a
542+ CreateAnonymousSessionOptions or a plain dict. Explicit keyword
543+ arguments below always win over the same field on options.
544+ audience: Audience for the session. Falls back to options.audience,
545+ then to the client's configured default, when omitted.
546+ scope: Scope for the session. Falls back to options.scope, then to
547+ the client's configured default, when omitted.
543548 metadata: Metadata to attach at creation, up to 1KB. Cannot be
544- changed after creation.
549+ changed after creation. Falls back to options.metadata.
545550 store_options: Options passed to the anonymous store.
546551
547552 Returns:
548553 The newly created AnonymousSession.
549554
550555 Raises:
551556 ConfigurationError: No anonymous_store configured.
552- AnonymousSessionCreateError: Local validation or server rejection.
557+ AnonymousCreateError: Invalid options, local validation
558+ failure, or server rejection.
553559 """
554560 self ._require_store ()
561+ if options is not None :
562+ if isinstance (options , dict ):
563+ try :
564+ options = CreateAnonymousSessionOptions (** options )
565+ except ValidationError as e :
566+ raise AnonymousCreateError (
567+ "Invalid create_session options" , code = "invalid_options"
568+ ) from e
569+ audience = audience if audience is not None else options .audience
570+ scope = scope if scope is not None else options .scope
571+ metadata = metadata if metadata is not None else options .metadata
555572 self ._validate_metadata (metadata )
556573 audience = audience or self ._default_audience
557574 scope = scope or self ._default_scope
@@ -645,18 +662,18 @@ async def introspect(
645662
646663 Raises:
647664 ConfigurationError: No anonymous_store configured.
648- AnonymousSessionIntrospectError : No active session, or a request
665+ AnonymousIntrospectError : No active session, or a request
649666 failure.
650667 """
651668 self ._require_store ()
652669 stored = await self ._anonymous_store .get (ANON_IDENTIFIER , options = store_options )
653670 if not stored :
654- raise AnonymousSessionIntrospectError ("No active anonymous session to introspect." )
671+ raise AnonymousIntrospectError ("No active anonymous session to introspect." )
655672
656673 try :
657674 context = self ._decrypt_context (stored )
658675 except _AnonymousSessionExpired as e :
659- raise AnonymousSessionIntrospectError (
676+ raise AnonymousIntrospectError (
660677 "Stored anonymous session is invalid or corrupted."
661678 ) from e
662679
@@ -670,21 +687,21 @@ async def introspect(
670687 auth = BearerAuth (context .access_token ),
671688 )
672689 except httpx .HTTPError as e :
673- raise AnonymousSessionIntrospectError (
690+ raise AnonymousIntrospectError (
674691 "Failed to reach the anonymous userinfo endpoint"
675692 ) from e
676693
677694 if response .status_code != 200 :
678695 error_data = self ._parse_anonymous_error_body (response )
679696 mapped = self ._map_anonymous_error (response .status_code , error_data , "introspect" )
680697 if isinstance (mapped , _AnonymousSessionExpired ):
681- raise AnonymousSessionIntrospectError (str (mapped ))
698+ raise AnonymousIntrospectError (str (mapped ))
682699 raise mapped
683700
684701 try :
685702 return AnonymousSessionIntrospection .model_validate (response .json ())
686703 except (json .JSONDecodeError , ValueError , ValidationError ) as e :
687- raise AnonymousSessionIntrospectError (
704+ raise AnonymousIntrospectError (
688705 "Failed to parse anonymous introspection response"
689706 ) from e
690707
0 commit comments