alpha-python 0.4.0__py3-none-any.whl → 0.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. alpha/__init__.py +26 -8
  2. alpha/adapters/__init__.py +2 -0
  3. alpha/adapters/rest_api_unit_of_work.py +105 -0
  4. alpha/adapters/sqla_unit_of_work.py +9 -9
  5. alpha/domain/models/group.py +35 -0
  6. alpha/domain/models/user.py +66 -2
  7. alpha/exceptions.py +12 -0
  8. alpha/factories/jwt_factory.py +88 -25
  9. alpha/factories/password_factory.py +1 -0
  10. alpha/handlers/templates/python-flask/controller.mustache +37 -28
  11. alpha/interfaces/__init__.py +5 -1
  12. alpha/interfaces/api_repository.py +610 -0
  13. alpha/interfaces/providers.py +1 -1
  14. alpha/interfaces/sql_repository.py +0 -11
  15. alpha/interfaces/token_factory.py +6 -1
  16. alpha/mixins/jwt_provider.py +7 -7
  17. alpha/providers/database_provider.py +2 -2
  18. alpha/providers/ldap_provider.py +9 -9
  19. alpha/providers/models/identity.py +56 -22
  20. alpha/providers/models/token.py +51 -3
  21. alpha/providers/oidc_provider.py +7 -5
  22. alpha/repositories/__init__.py +2 -0
  23. alpha/repositories/models/repository_model.py +25 -7
  24. alpha/repositories/rest_api_repository.py +1199 -0
  25. alpha/services/__init__.py +2 -0
  26. alpha/services/authentication_service.py +684 -49
  27. alpha/services/models/cookie.py +51 -0
  28. alpha/services/user_lifecycle_management.py +331 -0
  29. alpha/utils/__init__.py +2 -0
  30. alpha/utils/request_headers.py +108 -0
  31. alpha/utils/response_object.py +151 -11
  32. alpha/utils/secret_generator.py +19 -0
  33. {alpha_python-0.4.0.dist-info → alpha_python-0.5.1.dist-info}/METADATA +58 -2
  34. {alpha_python-0.4.0.dist-info → alpha_python-0.5.1.dist-info}/RECORD +39 -31
  35. {alpha_python-0.4.0.dist-info → alpha_python-0.5.1.dist-info}/WHEEL +1 -1
  36. {alpha_python-0.4.0.dist-info → alpha_python-0.5.1.dist-info}/licenses/LICENSE +1 -1
  37. /alpha/{providers/local_provider.py → services/models/__init__.py} +0 -0
  38. {alpha_python-0.4.0.dist-info → alpha_python-0.5.1.dist-info}/entry_points.txt +0 -0
  39. {alpha_python-0.4.0.dist-info → alpha_python-0.5.1.dist-info}/top_level.txt +0 -0
alpha/__init__.py CHANGED
@@ -1,3 +1,4 @@
1
+ from alpha.adapters.rest_api_unit_of_work import RestApiUnitOfWork
1
2
  from alpha.adapters.sqla_unit_of_work import SqlAlchemyUnitOfWork
2
3
  from alpha.factories.jwt_factory import JWTFactory
3
4
  from alpha.factories.logging_handler_factory import LoggingHandlerFactory
@@ -20,6 +21,7 @@ from alpha.interfaces.pydantic_instance import PydanticInstance
20
21
  from alpha.interfaces.openapi_model import OpenAPIModel
21
22
  from alpha.interfaces.updateable import Updateable
22
23
  from alpha.interfaces.patchable import Patchable
24
+ from alpha.interfaces.api_repository import ApiRepository
23
25
  from alpha.interfaces.sql_repository import SqlRepository
24
26
  from alpha.interfaces.sql_mapper import SqlMapper
25
27
  from alpha.interfaces.sql_database import SqlDatabase
@@ -44,8 +46,10 @@ from alpha.providers.models.credentials import PasswordCredentials
44
46
  from alpha.providers.models.token import Token
45
47
  from alpha.providers.oidc_provider import OIDCProvider, KeyCloakProvider
46
48
  from alpha.repositories.models.repository_model import RepositoryModel
49
+ from alpha.repositories.rest_api_repository import RestApiRepository
47
50
  from alpha.repositories.sql_alchemy_repository import SqlAlchemyRepository
48
51
  from alpha.services.authentication_service import AuthenticationService
52
+ from alpha.services.user_lifecycle_management import UserLifecycleManagement
49
53
  from alpha.utils.is_attrs import is_attrs
50
54
  from alpha.utils.is_pydantic import is_pydantic
51
55
  from alpha.utils.logging_configurator import (
@@ -53,6 +57,7 @@ from alpha.utils.logging_configurator import (
53
57
  GunicornLogger,
54
58
  )
55
59
  from alpha.utils.logging_level_checker import logging_level_checker
60
+ from alpha.utils.request_headers import Headers
56
61
  from alpha.utils.response_object import create_response_object
57
62
  from alpha.utils.verify_identity import verify_identity
58
63
  from alpha.utils.version_checker import minor_version_gte
@@ -60,13 +65,20 @@ from alpha.encoder import JSONEncoder
60
65
 
61
66
  # Optional LDAP support - only import if ldap3 is available
62
67
  try:
63
- from alpha.infra.connectors.ldap_connector import LDAPConnector # noqa: F401
64
- from alpha.providers.ldap_provider import LDAPProvider, ADProvider # noqa: F401
68
+ from alpha.infra.connectors.ldap_connector import (
69
+ LDAPConnector, # noqa: F401
70
+ )
71
+ from alpha.providers.ldap_provider import (
72
+ LDAPProvider, # noqa: F401
73
+ ADProvider, # noqa: F401
74
+ )
75
+
65
76
  _LDAP_AVAILABLE = True
66
77
  except ImportError:
67
- _LDAP_AVAILABLE = False # type: ignore
78
+ _LDAP_AVAILABLE = False # type: ignore
68
79
 
69
80
  __all__ = [
81
+ "RestApiUnitOfWork",
70
82
  "SqlAlchemyUnitOfWork",
71
83
  "JWTFactory",
72
84
  "LoggingHandlerFactory",
@@ -91,6 +103,7 @@ __all__ = [
91
103
  "OpenAPIModel",
92
104
  "Updateable",
93
105
  "Patchable",
106
+ "ApiRepository",
94
107
  "SqlRepository",
95
108
  "SqlMapper",
96
109
  "SqlDatabase",
@@ -112,13 +125,16 @@ __all__ = [
112
125
  "OIDCProvider",
113
126
  "KeyCloakProvider",
114
127
  "RepositoryModel",
128
+ "RestApiRepository",
115
129
  "SqlAlchemyRepository",
116
130
  "AuthenticationService",
131
+ "UserLifecycleManagement",
117
132
  "is_attrs",
118
133
  "is_pydantic",
119
134
  "LoggingConfigurator",
120
135
  "GunicornLogger",
121
136
  "logging_level_checker",
137
+ "Headers",
122
138
  "create_response_object",
123
139
  "verify_identity",
124
140
  "minor_version_gte",
@@ -128,8 +144,10 @@ __all__ = [
128
144
 
129
145
  # Conditionally add LDAP-related exports if available
130
146
  if _LDAP_AVAILABLE:
131
- __all__.extend([
132
- "LDAPConnector",
133
- "LDAPProvider",
134
- "ADProvider",
135
- ])
147
+ __all__.extend(
148
+ [
149
+ "LDAPConnector",
150
+ "LDAPProvider",
151
+ "ADProvider",
152
+ ]
153
+ )
@@ -1,5 +1,7 @@
1
+ from alpha.adapters.rest_api_unit_of_work import RestApiUnitOfWork
1
2
  from alpha.adapters.sqla_unit_of_work import SqlAlchemyUnitOfWork
2
3
 
3
4
  __all__ = [
5
+ "RestApiUnitOfWork",
4
6
  "SqlAlchemyUnitOfWork",
5
7
  ]
@@ -0,0 +1,105 @@
1
+ """Contains the REST API Unit of Work implementation."""
2
+
3
+ from typing import Any, TypeVar
4
+
5
+ import requests
6
+
7
+ from alpha.repositories.models.repository_model import RepositoryModel
8
+
9
+
10
+ UOW = TypeVar("UOW", bound="RestApiUnitOfWork")
11
+
12
+
13
+ class RestApiUnitOfWork:
14
+ """Unit of Work implementation for REST API interactions."""
15
+
16
+ def __init__(
17
+ self,
18
+ repos: list[RepositoryModel[Any]],
19
+ session: requests.sessions.Session | None = None,
20
+ ) -> None:
21
+ """Initialize the Unit of Work with repositories.
22
+
23
+ Parameters
24
+ ----------
25
+ repos : list[RepositoryModel]
26
+ The list of repository models to use.
27
+ session : requests.sessions.Session | None
28
+ The requests session (or compatible HTTP client, e.g., httpx) to
29
+ use for context management, by default None
30
+
31
+ Raises
32
+ ------
33
+ TypeError
34
+ If any repository does not implement its specified interface.
35
+ """
36
+ self._repositories = repos
37
+ self._session = session
38
+
39
+ def __enter__(self: UOW) -> UOW:
40
+ """Enter the REST API Unit of Work context.
41
+ Initializes a :class:`requests.sessions.Session` if one was not
42
+ provided and attaches the configured repositories as attributes on the
43
+ unit of work instance. Each repository is constructed using the shared
44
+ session and its associated configuration, and optionally validated
45
+ against a declared interface.
46
+
47
+ Returns
48
+ -------
49
+ UOW
50
+ The configured :class:`RestApiUnitOfWork` instance to be used
51
+ within the context manager.
52
+ """
53
+ self._session = self._session or requests.sessions.Session()
54
+
55
+ for repo in self._repositories:
56
+ name: str = repo.name
57
+ interface: Any = repo.interface
58
+ additional_config: dict[str, Any] = dict(
59
+ repo.additional_config or {}
60
+ )
61
+
62
+ self.__setattr__(
63
+ name,
64
+ repo.repository(
65
+ session=self._session,
66
+ default_model=repo.default_model,
67
+ **additional_config,
68
+ ),
69
+ )
70
+
71
+ if interface:
72
+ if not isinstance(getattr(self, name), interface):
73
+ raise TypeError(f"Repository for {name} has no interface")
74
+
75
+ return self
76
+
77
+ def __exit__(self, *args: Any) -> None:
78
+ """Finalize the Unit of Work context."""
79
+ if self._session:
80
+ self._session.close()
81
+
82
+ def commit(self) -> None:
83
+ raise NotImplementedError("RestApiUnitOfWork does not support commit")
84
+
85
+ def flush(self) -> None:
86
+ raise NotImplementedError("RestApiUnitOfWork does not support flush")
87
+
88
+ def rollback(self) -> None:
89
+ raise NotImplementedError(
90
+ "RestApiUnitOfWork does not support rollback"
91
+ )
92
+
93
+ def refresh(self, obj: object) -> None:
94
+ raise NotImplementedError("RestApiUnitOfWork does not support refresh")
95
+
96
+ @property
97
+ def session(self) -> requests.sessions.Session | None:
98
+ """Get the current session.
99
+
100
+ Returns
101
+ -------
102
+ requests.sessions.Session | None
103
+ The current session used for API interactions.
104
+ """
105
+ return self._session
@@ -14,7 +14,11 @@ UOW = TypeVar("UOW", bound="SqlAlchemyUnitOfWork")
14
14
  class SqlAlchemyUnitOfWork:
15
15
  """Unit of Work implementation for SQLAlchemy databases."""
16
16
 
17
- def __init__(self, db: SqlDatabase, repos: list[RepositoryModel]) -> None:
17
+ def __init__(
18
+ self,
19
+ db: SqlDatabase,
20
+ repos: list[RepositoryModel[Any]],
21
+ ) -> None:
18
22
  """Initialize the Unit of Work with a database and repositories.
19
23
 
20
24
  Parameters
@@ -28,9 +32,6 @@ class SqlAlchemyUnitOfWork:
28
32
  ------
29
33
  TypeError
30
34
  If the provided database is not a valid SqlDatabase instance.
31
- TypeError
32
- If the provided repositories list is empty or contains invalid
33
- models.
34
35
  """
35
36
  if not isinstance(db, SqlDatabase): # type: ignore
36
37
  raise TypeError("No valid database provided")
@@ -55,17 +56,16 @@ class SqlAlchemyUnitOfWork:
55
56
  self._session = self._db.get_session()
56
57
 
57
58
  for repo in self._repositories:
58
- session = self._session
59
- model = repo.default_model
60
-
61
59
  name: str = repo.name
62
- repository = repo.repository
63
60
  interface: Any = repo.interface
64
61
 
65
62
  self.__setattr__(
66
63
  name,
67
- repository(session=session, default_model=model), # type: ignore
64
+ repo.repository(
65
+ session=self._session, default_model=repo.default_model
66
+ ),
68
67
  )
68
+
69
69
  if interface:
70
70
  if not isinstance(getattr(self, name), interface):
71
71
  raise TypeError(f"Repository for {name} has no interface")
@@ -0,0 +1,35 @@
1
+ from dataclasses import dataclass
2
+ from datetime import datetime, timezone
3
+ from typing import Sequence, cast
4
+ from uuid import UUID
5
+
6
+ from alpha.domain.models.base_model import BaseDomainModel, DomainModel
7
+ from alpha.domain.models.life_cycle_base import LifeCycleBase
8
+
9
+
10
+ @dataclass(kw_only=True)
11
+ class Group(LifeCycleBase, BaseDomainModel):
12
+ id: UUID | int | str | None = None
13
+ name: str | None = None
14
+ description: str | None = None
15
+ permissions: Sequence[str] | None = None
16
+ is_active: bool = True
17
+
18
+ def update(self, obj: DomainModel) -> DomainModel:
19
+ """Update the Group instance with data from another Group instance.
20
+
21
+ Parameters
22
+ ----------
23
+ obj
24
+ Group object to update from.
25
+ """
26
+ if not isinstance(obj, Group):
27
+ raise TypeError("Group.update expects a Group instance.")
28
+
29
+ self.name = obj.name
30
+ self.description = obj.description
31
+ self.permissions = obj.permissions
32
+ self.modified_at = datetime.now(tz=timezone.utc)
33
+ self.is_active = obj.is_active
34
+
35
+ return cast(DomainModel, self)
@@ -1,25 +1,89 @@
1
1
  from dataclasses import dataclass
2
2
  from datetime import datetime, timezone
3
+ from enum import Enum, auto
3
4
  from typing import Self, Sequence, cast
4
5
  from uuid import UUID
5
6
 
6
7
  from alpha.domain.models.base_model import BaseDomainModel, DomainModel
8
+ from alpha.domain.models.group import Group
7
9
  from alpha.domain.models.life_cycle_base import LifeCycleBase
8
10
 
9
11
  from alpha.providers.models.identity import Identity
10
12
 
11
13
 
14
+ class Role(Enum):
15
+ """Defines user roles with varying levels of permissions. The roles are
16
+ ordered from highest to lowest permissions. The comparison methods allow
17
+ for easy comparison of roles based on their hierarchy. The roles are
18
+ ordered on a scale from highest to lowest permissions.
19
+
20
+ Typical permissions are as follows:
21
+ - CREATE: Permission to create new content or data, but not modify existing
22
+ content.
23
+ - READ: Permission to read content or data.
24
+ - UPDATE: Permission to modify existing content or data, but not create new
25
+ content.
26
+ - DELETE: Permission to delete content or data.
27
+ - MANAGE_USERS: Permission to manage user accounts and permissions.
28
+ - MANAGE_SETTINGS: Permission to manage system settings and configurations.
29
+ - ALL: Permission to perform all actions, including user management and
30
+ system settings.
31
+
32
+ Roles:
33
+ - ADMIN: Role with permissions to manage users, content, and system
34
+ settings. Typically has the ALL permissions.
35
+ - SUPERUSER: Role with all permissions, including system settings and user
36
+ management. Typically has the ALL permissions, but may be used to denote a
37
+ special type of admin user with additional privileges or responsibilities.
38
+ - OWNER: Role with permissions to manage their own resources and users, but
39
+ not system settings. Typically has permissions similar to ADMIN, but
40
+ limited to their own scope of resources.
41
+ - MODERATOR: Role with permissions to manage content and users, but not
42
+ system settings. Typically has permissions to UPDATE and DELETE content,
43
+ and MANAGE_USERS, but not MANAGE_SETTINGS.
44
+ - EDITOR: Role with permissions to create and edit content, but not manage
45
+ users or settings. Typically has permissions to CREATE, READ, UPDATE, and
46
+ DELETE content, but not MANAGE_USERS or MANAGE_SETTINGS.
47
+ - USER: Default role with standard permissions. Typically has permissions
48
+ to CREATE, READ, and UPDATE their own content, but not DELETE content or
49
+ manage users or settings.
50
+ - VIEWER: Typical read-only role with limited permissions. Typically has
51
+ permission to READ content, but not CREATE, UPDATE, DELETE, or manage users
52
+ or settings.
53
+ """
54
+
55
+ ADMIN = auto()
56
+ SUPERUSER = auto()
57
+ OWNER = auto()
58
+ MODERATOR = auto()
59
+ EDITOR = auto()
60
+ USER = auto()
61
+ VIEWER = auto()
62
+
63
+ def __lt__(self, obj: Self) -> bool:
64
+ return self.value < obj.value
65
+
66
+ def __le__(self, obj: Self) -> bool:
67
+ return self.value <= obj.value
68
+
69
+ def __gt__(self, obj: Self) -> bool:
70
+ return self.value > obj.value
71
+
72
+ def __ge__(self, obj: Self) -> bool:
73
+ return self.value >= obj.value
74
+
75
+
12
76
  @dataclass(kw_only=True)
13
77
  class User(LifeCycleBase, BaseDomainModel):
14
78
  id: UUID | int | str | None = None
15
79
  username: str | None = None
16
80
  password: str | None = None
17
- role: str | None = None
81
+ role: str | Role | None = None
18
82
  email: str | None = None
19
83
  phone: str | None = None
20
84
  display_name: str | None = None
21
85
  permissions: Sequence[str] | None = None
22
- groups: Sequence[str] | None = None
86
+ groups: Sequence[str | Group] | None = None
23
87
  is_active: bool = True
24
88
  admin: bool = False
25
89
 
alpha/exceptions.py CHANGED
@@ -19,6 +19,10 @@ class NotFoundException(ClientErrorException):
19
19
  """Equivalent to HTTP code 404"""
20
20
 
21
21
 
22
+ class MethodNotAllowedException(ClientErrorException):
23
+ """Equivalent to HTTP code 405"""
24
+
25
+
22
26
  class NotAcceptableException(ClientErrorException):
23
27
  """Equivalent to HTTP code 406"""
24
28
 
@@ -55,11 +59,19 @@ class ServiceUnavailableException(ServerErrorException):
55
59
  """Equivalent to HTTP code 503"""
56
60
 
57
61
 
62
+ class GatewayTimeoutException(ServerErrorException):
63
+ """Equivalent to HTTP code 504"""
64
+
65
+
58
66
  # General Exceptions
59
67
  class MissingConfigurationException(Exception):
60
68
  """Raised when a required configuration is missing."""
61
69
 
62
70
 
71
+ class InvalidAttributeError(Exception):
72
+ """Raised when a required attribute is invalid."""
73
+
74
+
63
75
  class MissingDependencyException(Exception):
64
76
  """Raised when a required dependency is missing."""
65
77
 
@@ -18,7 +18,33 @@ class JWTFactory:
18
18
  lifetime_hours: str | None = "12",
19
19
  issuer: str = "http://localhost",
20
20
  jwt_algorithm: str = "HS256",
21
+ options: dict[str, Any] | None = None,
21
22
  ) -> None:
23
+ """Initialize the JWTFactory. This method sets up the necessary
24
+ configuration for creating and validating JWT tokens. It requires a
25
+ secret key for signing the tokens and allows optional configuration
26
+ for token lifetime, issuer, algorithm, and decoding options.
27
+
28
+ Parameters
29
+ ----------
30
+ secret
31
+ The secret key used to sign the JWT.
32
+ lifetime_hours, optional
33
+ The lifetime of the JWT in hours, by default "12"
34
+ issuer, optional
35
+ The issuer of the JWT, by default "http://localhost"
36
+ jwt_algorithm, optional
37
+ The algorithm used to sign the JWT, by default "HS256"
38
+ options, optional
39
+ A dictionary of options to customize the decoding behavior, by
40
+ default None. If not provided, it defaults to requiring standard
41
+ claims and verifying the signature.
42
+
43
+ Raises
44
+ ------
45
+ ValueError
46
+ If the secret value is empty.
47
+ """
22
48
  if not secret:
23
49
  raise ValueError("Secret value cannot be empty")
24
50
  if lifetime_hours is None:
@@ -28,6 +54,10 @@ class JWTFactory:
28
54
  self.JWT_ISSUER = issuer
29
55
  self.JWT_ALGORITHM = jwt_algorithm
30
56
  self.JWT_LIFETIME_SECONDS = 3600 * int(lifetime_hours)
57
+ self.JWT_OPTIONS = options or {
58
+ "require": ["exp", "iat", "nbf", "iss", "sub"],
59
+ "verify_signature": True,
60
+ }
31
61
 
32
62
  def create(
33
63
  self,
@@ -75,13 +105,19 @@ class JWTFactory:
75
105
  )
76
106
  return token
77
107
 
78
- def validate(self, token: str) -> bool:
79
- """Validate a JWT token.
108
+ def validate(
109
+ self, token: str, options: dict[str, Any] | None = None
110
+ ) -> bool:
111
+ """Validate a JWT token. This method checks the token's signature,
112
+ expiration, and issuer. If the token is invalid, it raises an
113
+ appropriate exception. If the token is valid, it returns True.
80
114
 
81
115
  Parameters
82
116
  ----------
83
117
  token
84
118
  The JWT token to be validated.
119
+ options
120
+ A dictionary of options to customize the decoding behavior.
85
121
 
86
122
  Returns
87
123
  -------
@@ -94,15 +130,62 @@ class JWTFactory:
94
130
  InvalidSignatureException
95
131
  If the token signature is invalid.
96
132
  """
133
+ if self._decode(token, options):
134
+ return True
135
+ return False
136
+
137
+ def get_payload(
138
+ self, token: str, options: dict[str, Any] | None = None
139
+ ) -> dict[str, Any]:
140
+ """Retrieve the payload from a JWT token. This method does not perform
141
+ validation of the token by default. It simply decodes the token and
142
+ extracts the payload.
143
+
144
+ If the `options` parameter is provided, it will be passed to the
145
+ `jwt.decode` function. This allows customization of the decoding
146
+ behavior, such as enabling or disabling signature verification.
147
+
148
+ Parameters
149
+ ----------
150
+ token
151
+ The JWT token from which to extract the payload.
152
+ options
153
+ A dictionary of options to customize the decoding behavior.
154
+
155
+ Returns
156
+ -------
157
+ A dictionary containing the payload data extracted from the token.
158
+ """
159
+ decoded = self._decode(token, options)
160
+ return decoded.get("payload", {})
161
+
162
+ def _decode(
163
+ self, token: str, options: dict[str, Any] | None = None
164
+ ) -> dict[str, Any]:
165
+ """Decode a JWT token without performing validation. This method is
166
+ intended for internal use and should not be exposed as part of the
167
+ public API.
168
+
169
+ Parameters
170
+ ----------
171
+ token
172
+ The JWT token to be decoded.
173
+ options
174
+ A dictionary of options to customize the decoding behavior.
175
+
176
+ Returns
177
+ -------
178
+ A dictionary containing the decoded token data.
179
+ """
97
180
  try:
98
- jwt.decode(
181
+ decoded: dict[str, Any] = jwt.decode(
99
182
  jwt=token,
100
183
  key=self.JWT_SECRET,
101
184
  algorithms=[self.JWT_ALGORITHM],
102
185
  issuer=self.JWT_ISSUER,
103
- verify=True,
186
+ options=options or self.JWT_OPTIONS,
104
187
  )
105
- return True
188
+ return decoded
106
189
  except jwt.ExpiredSignatureError as e:
107
190
  raise exceptions.TokenExpiredException(str(e)) from e
108
191
  except jwt.InvalidSignatureError as e:
@@ -111,23 +194,3 @@ class JWTFactory:
111
194
  raise exceptions.InvalidTokenException(
112
195
  f"Token is invalid: {str(e)}"
113
196
  ) from e
114
-
115
- def get_payload(self, token: str) -> dict[str, Any]:
116
- """Retrieve the payload from a JWT token.
117
-
118
- Parameters
119
- ----------
120
- token
121
- The JWT token from which to extract the payload.
122
-
123
- Returns
124
- -------
125
- A dictionary containing the payload data extracted from the token.
126
- """
127
- decoded: dict[str, Any] = jwt.decode(
128
- jwt=token,
129
- key=self.JWT_SECRET,
130
- algorithms=[self.JWT_ALGORITHM],
131
- issuer=self.JWT_ISSUER,
132
- )
133
- return decoded.get("payload", {})
@@ -11,6 +11,7 @@ from alpha import exceptions
11
11
  class PasswordFactory:
12
12
  """This class provides methods for hashing and verifying passwords using
13
13
  the argon2 library. It includes the following methods:
14
+
14
15
  - hash_password: Hashes a given password and returns the hashed value as a
15
16
  hexadecimal string.
16
17
  - verify_password: Verifies a given password against a provided hash and