oxutils 0.1.7__py3-none-any.whl → 0.1.8__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.
oxutils/__init__.py CHANGED
@@ -10,7 +10,7 @@ This package provides:
10
10
  - Custom exceptions
11
11
  """
12
12
 
13
- __version__ = "0.1.7"
13
+ __version__ = "0.1.8"
14
14
 
15
15
  from oxutils.settings import oxi_settings
16
16
  from oxutils.conf import UTILS_APPS, AUDIT_MIDDLEWARE
oxutils/jwt/models.py CHANGED
@@ -1,4 +1,8 @@
1
- # TokenTenant model
1
+ from uuid import UUID
2
+ from django.utils.functional import cached_property
3
+ from ninja_jwt.models import TokenUser as DefaultTonkenUser
4
+ from ninja_jwt.settings import api_settings
5
+
2
6
  import structlog
3
7
  from .tokens import OrganizationAccessToken
4
8
 
@@ -48,3 +52,17 @@ class TokenTenant:
48
52
  except Exception:
49
53
  logger.exception('Failed to create TokenTenant from token', token=token)
50
54
  return None
55
+
56
+
57
+ class TokenUser(DefaultTonkenUser):
58
+ @cached_property
59
+ def id(self):
60
+ return UUID(self.token[api_settings.USER_ID_CLAIM])
61
+
62
+ @cached_property
63
+ def token_created_at(self):
64
+ return self.token.get('cat', None)
65
+
66
+ @cached_property
67
+ def token_session(self):
68
+ return self.token.get('session', None)
oxutils/jwt/utils.py ADDED
@@ -0,0 +1,45 @@
1
+ from functools import wraps
2
+ import structlog
3
+ from django.contrib.auth import get_user_model
4
+ from django.http import HttpRequest
5
+
6
+ from ninja_jwt.exceptions import InvalidToken
7
+
8
+
9
+
10
+ logger = structlog.getLogger("django")
11
+ User = get_user_model()
12
+
13
+
14
+ def load_user(f):
15
+ """
16
+ Decorator that loads the complete user object from the database for stateless JWT authentication.
17
+ This is necessary because JWT tokens only contain the user ID, and the full user object
18
+ might be needed in the view methods.
19
+
20
+ Usage:
21
+
22
+ .. code-block:: python
23
+
24
+ @load_user
25
+ def my_view_method(self, *args, **kwargs):
26
+ # self.request.user will be the complete user object
27
+ pass
28
+ """
29
+ @wraps(f)
30
+ def wrapper(self, *args, **kwargs):
31
+ populate_user(self.context.request)
32
+ res = f(self, *args, **kwargs)
33
+ return res
34
+ return wrapper
35
+
36
+
37
+ def populate_user(request: HttpRequest):
38
+ if isinstance(request.user, User):
39
+ return
40
+
41
+ try:
42
+ request.user = User.objects.get(oxi_id=request.user.id)
43
+ except User.DoesNotExist as exc:
44
+ logger.exception('user_not_found', oxi_id=request.user.id, message=str(exc))
45
+ raise InvalidToken()
oxutils/utils.py ADDED
@@ -0,0 +1,25 @@
1
+ from django.http import HttpRequest
2
+
3
+
4
+ def get_client_ip(request: HttpRequest) -> str:
5
+ """
6
+ Extract client IP address from request metadata.
7
+
8
+ Priority:
9
+
10
+ 1. X-Forwarded-For header (first entry if multiple)
11
+ 2. REMOTE_ADDR meta value
12
+
13
+ Args:
14
+ request (HttpRequest): Django request object
15
+
16
+ Returns:
17
+ str: Client IP address or None if not found
18
+ """
19
+ x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
20
+ if x_forwarded_for:
21
+ ip = x_forwarded_for.split(',')[0]
22
+ else:
23
+ ip = request.META.get('REMOTE_ADDR')
24
+ return ip
25
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oxutils
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: Production-ready utilities for Django applications in the Oxiliere ecosystem
5
5
  Keywords: django,utilities,jwt,s3,audit,logging,celery,structlog
6
6
  Author: Edimedia Mutoke
@@ -1,4 +1,4 @@
1
- oxutils/__init__.py,sha256=KhbRz26xv7g4QkYDPEBR8UY90RvhrzcYEI8uJZSl94I,536
1
+ oxutils/__init__.py,sha256=cSYDL8bZ0uhcOS2f6NLOnk7-6U19dsz0lLXtSpIG0_M,536
2
2
  oxutils/apps.py,sha256=8pO8eXUZeKYn8fPo0rkoytmHACwDNuTNhdRcpkPTxGM,347
3
3
  oxutils/audit/__init__.py,sha256=uonc00G73Xm7RwRHVWD-wBn8lJYNCq3iBgnRGMWAEWs,583
4
4
  oxutils/audit/apps.py,sha256=xvnmB5Z6nLV7ejzhSeQbesTkwRoFygoPFob8H5QTHgU,304
@@ -34,8 +34,9 @@ oxutils/exceptions.py,sha256=CCjENOD0of6_noif2ajrpfbBLoG16DWa46iB9_uEe3M,3592
34
34
  oxutils/functions.py,sha256=4stHj94VebWX0s1XeWshubMD2v8w8QztTWppbkTE_Gg,3246
35
35
  oxutils/jwt/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
36
36
  oxutils/jwt/auth.py,sha256=rO-xWNfug9Ok6zA7EIPvVkpD8TBUdq05CdrnMrL-t9Q,1597
37
- oxutils/jwt/models.py,sha256=lnBv6buITQ2Pqxkaxdd32MyatRY0V-GYTozaEQVWeAw,1363
37
+ oxutils/jwt/models.py,sha256=xAoIeGw6tqo91g0bupgig6F6OE46CiglREyw7jM4wc0,1854
38
38
  oxutils/jwt/tokens.py,sha256=kWgtPl4XxV0xHkjbhd5QteQy8Wv5MsvyLcLVyO-gzuo,1822
39
+ oxutils/jwt/utils.py,sha256=Wuy-PnCcUw6MpY6z1Isy2vOx-_u1o6LjUfRJgf_cqbY,1202
39
40
  oxutils/locale/fr/LC_MESSAGES/django.po,sha256=APXt_8R99seCWjJyS5ELOawvRLvUqqBT32O252BaG5s,7971
40
41
  oxutils/logger/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
42
  oxutils/logger/receivers.py,sha256=EBpkwMCHYacOJvuOPzUtM_8ttWWetz17kIwoudGiV34,488
@@ -84,6 +85,7 @@ oxutils/users/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
84
85
  oxutils/users/models.py,sha256=YXAHcXuhmHZfcRDOqA5lBiSRcJ24snBs9X6tcJPCcFs,2960
85
86
  oxutils/users/tests.py,sha256=mrbGGRNg5jwbTJtWWa7zSKdDyeB4vmgZCRc2nk6VY-g,60
86
87
  oxutils/users/utils.py,sha256=jY-zL8vLT5U3E2FV3DqCvrPORjKLutbkPZTQ-z96dCw,376
87
- oxutils-0.1.7.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
88
- oxutils-0.1.7.dist-info/METADATA,sha256=kyYdR5sGpECS1QIkIJhnuFLutpG8x4TX26qLaAINxeY,8759
89
- oxutils-0.1.7.dist-info/RECORD,,
88
+ oxutils/utils.py,sha256=6yGX2d1ajU5RqgfqiaS4McYm7ip2KEgADABo3M-yA3U,595
89
+ oxutils-0.1.8.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
90
+ oxutils-0.1.8.dist-info/METADATA,sha256=_CW_yBDOyOTv04ABahWzZbJ0XJHlnNqutefdq089hVA,8759
91
+ oxutils-0.1.8.dist-info/RECORD,,