maquinaweb-shared-auth 0.2.25__tar.gz → 0.2.27__tar.gz

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.

Potentially problematic release.


This version of maquinaweb-shared-auth might be problematic. Click here for more details.

Files changed (30) hide show
  1. maquinaweb_shared_auth-0.2.27/PKG-INFO +1002 -0
  2. maquinaweb_shared_auth-0.2.27/README.md +980 -0
  3. maquinaweb_shared_auth-0.2.27/maquinaweb_shared_auth.egg-info/PKG-INFO +1002 -0
  4. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/pyproject.toml +1 -1
  5. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/models.py +11 -9
  6. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/serializers.py +3 -1
  7. maquinaweb_shared_auth-0.2.25/PKG-INFO +0 -1017
  8. maquinaweb_shared_auth-0.2.25/README.md +0 -995
  9. maquinaweb_shared_auth-0.2.25/maquinaweb_shared_auth.egg-info/PKG-INFO +0 -1017
  10. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/maquinaweb_shared_auth.egg-info/SOURCES.txt +0 -0
  11. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/maquinaweb_shared_auth.egg-info/dependency_links.txt +0 -0
  12. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/maquinaweb_shared_auth.egg-info/requires.txt +0 -0
  13. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/maquinaweb_shared_auth.egg-info/top_level.txt +0 -0
  14. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/setup.cfg +0 -0
  15. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/setup.py +0 -0
  16. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/__init__.py +0 -0
  17. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/app.py +0 -0
  18. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/authentication.py +0 -0
  19. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/conf.py +0 -0
  20. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/decorators.py +0 -0
  21. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/exceptions.py +0 -0
  22. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/fields.py +0 -0
  23. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/managers.py +0 -0
  24. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/middleware.py +0 -0
  25. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/mixins.py +0 -0
  26. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/permissions.py +0 -0
  27. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/router.py +0 -0
  28. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/storage_backend.py +0 -0
  29. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/urls.py +0 -0
  30. {maquinaweb_shared_auth-0.2.25 → maquinaweb_shared_auth-0.2.27}/shared_auth/views.py +0 -0
@@ -0,0 +1,1002 @@
1
+ Metadata-Version: 2.4
2
+ Name: maquinaweb-shared-auth
3
+ Version: 0.2.27
4
+ Summary: Models read-only para autenticação compartilhada entre projetos Django.
5
+ Author-email: Seu Nome <seuemail@dominio.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/maquinaweb/maquinaweb-shared-auth
8
+ Project-URL: Repository, https://github.com/maquinaweb/maquinaweb-shared-auth
9
+ Project-URL: Issues, https://github.com/maquinaweb/maquinaweb-shared-auth/issues
10
+ Keywords: django,auth,models,shared
11
+ Classifier: Framework :: Django
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: django>=5
18
+ Requires-Dist: django-storages>=1.14.6
19
+ Requires-Dist: djangorestframework>=3
20
+ Requires-Dist: setuptools>=70
21
+ Dynamic: requires-python
22
+
23
+ # 🔐 Maquinaweb Shared Auth
24
+
25
+ > Biblioteca Django para autenticação compartilhada entre múltiplos sistemas usando um único banco de dados centralizado.
26
+
27
+ [![Python](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
28
+ [![Django](https://img.shields.io/badge/django-4.2+-green.svg)](https://www.djangoproject.com/)
29
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
30
+
31
+ ## 📋 Índice
32
+
33
+ - [Visão Geral](#-visão-geral)
34
+ - [Características](#-características)
35
+ - [Arquitetura](#️-arquitetura)
36
+ - [Instalação](#-instalação)
37
+ - [Configuração](#️-configuração)
38
+ - [Uso Básico](#-uso-básico)
39
+ - [Guias Avançados](#-guias-avançados)
40
+ - [API Reference](#-api-reference)
41
+
42
+ ---
43
+
44
+ ## 🎯 Visão Geral
45
+
46
+ A **Maquinaweb Shared Auth** permite que múltiplos sistemas Django compartilhem dados de autenticação, usuários e organizações através de um banco de dados centralizado, sem necessidade de requisições HTTP.
47
+
48
+ ### Problema Resolvido
49
+
50
+ Ao invés de:
51
+ - ❌ Duplicar dados de usuários em cada sistema
52
+ - ❌ Fazer requisições HTTP entre sistemas
53
+ - ❌ Manter múltiplos bancos de autenticação sincronizados
54
+
55
+ Você pode:
56
+ - ✅ Acessar dados de autenticação diretamente do banco central
57
+ - ✅ Usar a interface familiar do Django ORM
58
+ - ✅ Garantir consistência de dados entre sistemas
59
+ - ✅ Trabalhar com models read-only seguros
60
+
61
+ ---
62
+
63
+ ## ✨ Características
64
+
65
+ ### Core Features
66
+
67
+ - **🔐 Autenticação Centralizada**: Token-based authentication compartilhado
68
+ - **🏢 Multi-Tenancy**: Suporte completo a organizações e filiais
69
+ - **👥 Gestão de Membros**: Relacionamento usuários ↔ organizações
70
+ - **🔒 Read-Only Safety**: Proteção contra modificações acidentais
71
+ - **⚡ Performance**: Managers otimizados com prefetch automático
72
+ - **🎨 DRF Ready**: Mixins para serializers com dados aninhados
73
+
74
+ ### Componentes Principais
75
+
76
+ | Componente | Descrição |
77
+ |------------|-----------|
78
+ | **Models** | SharedOrganization, User, SharedMember, SharedToken |
79
+ | **Mixins** | OrganizationMixin, UserMixin, OrganizationUserMixin |
80
+ | **Serializers** | OrganizationSerializerMixin, UserSerializerMixin |
81
+ | **Authentication** | SharedTokenAuthentication |
82
+ | **Middleware** | SharedAuthMiddleware, OrganizationMiddleware |
83
+ | **Permissions** | IsAuthenticated, HasActiveOrganization, IsSameOrganization |
84
+ | **Managers** | Métodos otimizados com prefetch e validações |
85
+
86
+ ---
87
+
88
+ ## 🏗️ Arquitetura
89
+
90
+ ```
91
+ ┌─────────────────────────────────────┐
92
+ │ Sistema de Autenticação Central │
93
+ │ │
94
+ │ ┌──────────────┐ ┌────────────┐ │
95
+ │ │Organization │ │ User │ │
96
+ │ └──────┬───────┘ └─────┬──────┘ │
97
+ │ │ │ │
98
+ │ └────────┬───────┘ │
99
+ │ │ │
100
+ │ ┌──────▼──────┐ │
101
+ │ │ Member │ │
102
+ │ │ Token │ │
103
+ │ └─────────────┘ │
104
+ └──────────────────┬──────────────────┘
105
+
106
+ ┌────────────┴────────────┐
107
+ │ PostgreSQL/MySQL │
108
+ │ (auth_db) │
109
+ └────────────┬────────────┘
110
+
111
+ ┌────────────┴────────────┐
112
+ │ │
113
+ ┌─────▼─────┐ ┌─────▼─────┐
114
+ │ Sistema A │ │ Sistema B │
115
+ │ │ │ │
116
+ │ Pedidos │ │ Estoque │
117
+ │ ├─ org │ │ ├─ org │
118
+ │ └─ user │ │ └─ user │
119
+ └───────────┘ └───────────┘
120
+ ```
121
+
122
+ **Fluxo de Autenticação:**
123
+
124
+ 1. Cliente envia request com token no header
125
+ 2. Middleware valida token no banco `auth_db`
126
+ 3. Dados do usuário e organização são anexados ao `request`
127
+ 4. Sistema cliente acessa dados via ORM (read-only)
128
+
129
+ ---
130
+
131
+ ## 📦 Instalação
132
+
133
+ ### 1. Instalar a Biblioteca
134
+
135
+ ```bash
136
+ # Via pip (quando publicado)
137
+ pip install maquinaweb-shared-auth
138
+
139
+ # Ou modo desenvolvimento
140
+ pip install -e /path/to/maquinaweb-shared-auth
141
+ ```
142
+
143
+ ### 2. Adicionar ao requirements.txt
144
+
145
+ ```txt
146
+ Django>=4.2
147
+ djangorestframework>=3.14
148
+ maquinaweb-shared-auth>=0.2.25
149
+ ```
150
+
151
+ ---
152
+
153
+ ## ⚙️ Configuração
154
+
155
+ ### 1. Settings do Django
156
+
157
+ ```python
158
+ # settings.py
159
+
160
+ INSTALLED_APPS = [
161
+ 'django.contrib.admin',
162
+ 'django.contrib.auth',
163
+ 'django.contrib.contenttypes',
164
+ 'django.contrib.sessions',
165
+ 'rest_framework',
166
+
167
+ # Adicionar shared_auth
168
+ 'shared_auth',
169
+
170
+ # Suas apps
171
+ 'myapp',
172
+ ]
173
+ ```
174
+
175
+ ### 2. Configurar Banco de Dados
176
+
177
+ ```python
178
+ # settings.py
179
+
180
+ DATABASES = {
181
+ 'default': {
182
+ # Banco do sistema atual
183
+ 'ENGINE': 'django.db.backends.postgresql',
184
+ 'NAME': 'meu_sistema_db',
185
+ 'USER': 'meu_user',
186
+ 'PASSWORD': 'senha',
187
+ 'HOST': 'localhost',
188
+ 'PORT': '5432',
189
+ },
190
+ 'auth_db': {
191
+ # Banco centralizado de autenticação (READ-ONLY)
192
+ 'ENGINE': 'django.db.backends.postgresql',
193
+ 'NAME': 'sistema_auth_db',
194
+ 'USER': 'readonly_user',
195
+ 'PASSWORD': 'senha_readonly',
196
+ 'HOST': 'auth-server.example.com',
197
+ 'PORT': '5432',
198
+ }
199
+ }
200
+
201
+ # Router para direcionar queries
202
+ DATABASE_ROUTERS = ['shared_auth.router.SharedAuthRouter']
203
+ ```
204
+
205
+ ### 3. Configurar Autenticação (DRF)
206
+
207
+ ```python
208
+ # settings.py
209
+
210
+ REST_FRAMEWORK = {
211
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
212
+ 'shared_auth.authentication.SharedTokenAuthentication',
213
+ ],
214
+ 'DEFAULT_PERMISSION_CLASSES': [
215
+ 'shared_auth.permissions.IsAuthenticated',
216
+ ],
217
+ }
218
+ ```
219
+
220
+ ### 4. Configurar Middleware (Opcional)
221
+
222
+ ```python
223
+ # settings.py
224
+
225
+ MIDDLEWARE = [
226
+ 'django.middleware.security.SecurityMiddleware',
227
+ 'django.contrib.sessions.middleware.SessionMiddleware',
228
+ 'django.middleware.common.CommonMiddleware',
229
+
230
+ # Middlewares da shared_auth
231
+ 'shared_auth.middleware.SharedAuthMiddleware',
232
+ 'shared_auth.middleware.OrganizationMiddleware',
233
+
234
+ 'django.middleware.csrf.CsrfViewMiddleware',
235
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
236
+ 'django.contrib.messages.middleware.MessageMiddleware',
237
+ ]
238
+ ```
239
+
240
+ ### 5. Configurar Tabelas (Opcional)
241
+
242
+ ```python
243
+ # settings.py
244
+
245
+ # Customizar nomes das tabelas (se necessário)
246
+ SHARED_AUTH_ORGANIZATION_TABLE = 'organization_organization'
247
+ SHARED_AUTH_USER_TABLE = 'auth_user'
248
+ SHARED_AUTH_MEMBER_TABLE = 'organization_member'
249
+ SHARED_AUTH_TOKEN_TABLE = 'authtoken_token'
250
+ ```
251
+
252
+ ### 6. Criar Usuário Read-Only no PostgreSQL
253
+
254
+ ```sql
255
+ -- No servidor de autenticação
256
+ CREATE USER readonly_user WITH PASSWORD 'senha_segura_aqui';
257
+
258
+ -- Conceder permissões
259
+ GRANT CONNECT ON DATABASE sistema_auth_db TO readonly_user;
260
+ GRANT USAGE ON SCHEMA public TO readonly_user;
261
+ GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_user;
262
+
263
+ -- Para tabelas futuras
264
+ ALTER DEFAULT PRIVILEGES IN SCHEMA public
265
+ GRANT SELECT ON TABLES TO readonly_user;
266
+
267
+ -- Garantir read-only
268
+ ALTER USER readonly_user SET default_transaction_read_only = on;
269
+ ```
270
+
271
+ ---
272
+
273
+ ## 🚀 Uso Básico
274
+
275
+ ### 1. Models com Mixins
276
+
277
+ ```python
278
+ # myapp/models.py
279
+ from django.db import models
280
+ from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin
281
+ from shared_auth.managers import BaseAuthManager
282
+
283
+ class Pedido(OrganizationUserMixin, TimestampedMixin):
284
+ """Model que pertence a organização e usuário"""
285
+
286
+ numero = models.CharField(max_length=20, unique=True)
287
+ valor_total = models.DecimalField(max_digits=10, decimal_places=2)
288
+ status = models.CharField(max_length=20, default='pending')
289
+
290
+ objects = BaseAuthManager()
291
+
292
+ def __str__(self):
293
+ return f"Pedido {self.numero}"
294
+ ```
295
+
296
+ **O que você ganha automaticamente:**
297
+ - ✅ Campos: `organization_id`, `user_id`, `created_at`, `updated_at`
298
+ - ✅ Properties: `organization`, `user`, `organization_name`, `user_email`
299
+ - ✅ Métodos: `validate_user_belongs_to_organization()`, `user_can_access()`
300
+
301
+ ### 2. Serializers com Dados Aninhados
302
+
303
+ ```python
304
+ # myapp/serializers.py
305
+ from rest_framework import serializers
306
+ from shared_auth.serializers import OrganizationUserSerializerMixin
307
+ from .models import Pedido
308
+
309
+ class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
310
+ class Meta:
311
+ model = Pedido
312
+ fields = [
313
+ 'id', 'numero', 'valor_total', 'status',
314
+ 'organization', # Objeto completo
315
+ 'user', # Objeto completo
316
+ 'created_at',
317
+ ]
318
+ read_only_fields = ['organization', 'user', 'created_at']
319
+ ```
320
+
321
+ **Response JSON:**
322
+ ```json
323
+ {
324
+ "id": 1,
325
+ "numero": "PED-001",
326
+ "valor_total": "1500.00",
327
+ "status": "pending",
328
+ "organization": {
329
+ "id": 123,
330
+ "name": "Empresa XYZ Ltda",
331
+ "fantasy_name": "XYZ",
332
+ "cnpj": "12.345.678/0001-90",
333
+ "email": "contato@xyz.com",
334
+ "is_active": true
335
+ },
336
+ "user": {
337
+ "id": 456,
338
+ "username": "joao.silva",
339
+ "email": "joao@xyz.com",
340
+ "full_name": "João Silva",
341
+ "is_active": true
342
+ },
343
+ "created_at": "2025-10-01T10:00:00Z"
344
+ }
345
+ ```
346
+
347
+ ### 3. ViewSets com Organização
348
+
349
+ ```python
350
+ # myapp/views.py
351
+ from rest_framework import viewsets
352
+ from shared_auth.mixins import LoggedOrganizationMixin
353
+ from shared_auth.permissions import HasActiveOrganization, IsSameOrganization
354
+ from .models import Pedido
355
+ from .serializers import PedidoSerializer
356
+
357
+ class PedidoViewSet(LoggedOrganizationMixin, viewsets.ModelViewSet):
358
+ """
359
+ ViewSet que filtra automaticamente por organização logada
360
+ """
361
+ serializer_class = PedidoSerializer
362
+ permission_classes = [HasActiveOrganization, IsSameOrganization]
363
+
364
+ # get_queryset() já filtra por organization_id automaticamente
365
+ # perform_create() já adiciona organization_id automaticamente
366
+ ```
367
+
368
+ ### 4. Acessar Dados Compartilhados
369
+
370
+ ```python
371
+ # Em qualquer lugar do código
372
+ from shared_auth.models import SharedOrganization, User, SharedMember
373
+
374
+ # Buscar organização
375
+ org = SharedOrganization.objects.get_or_fail(123)
376
+ print(org.name) # "Empresa XYZ"
377
+ print(org.members) # QuerySet de membros
378
+
379
+ # Buscar usuário
380
+ user = User.objects.get_or_fail(456)
381
+ print(user.email) # "joao@xyz.com"
382
+ print(user.organizations) # Organizações do usuário
383
+
384
+ # Verificar membership
385
+ member = SharedMember.objects.filter(
386
+ user_id=456,
387
+ organization_id=123
388
+ ).first()
389
+
390
+ if member:
391
+ print(f"{member.user.email} é membro de {member.organization.name}")
392
+ ```
393
+
394
+ ---
395
+
396
+ ## 📚 Guias Avançados
397
+
398
+ ### Mixins para Models
399
+
400
+ #### 1. OrganizationMixin
401
+ Para models que pertencem apenas a uma organização.
402
+
403
+ ```python
404
+ from shared_auth.mixins import OrganizationMixin
405
+
406
+ class EmpresaConfig(OrganizationMixin):
407
+ tema_cor = models.CharField(max_length=7, default='#3490dc')
408
+ logo = models.ImageField(upload_to='logos/')
409
+
410
+ # Uso
411
+ config = EmpresaConfig.objects.create(organization_id=123, tema_cor='#ff0000')
412
+ print(config.organization.name) # Acesso automático
413
+ print(config.organization_members) # Membros da organização
414
+ ```
415
+
416
+ #### 2. UserMixin
417
+ Para models que pertencem apenas a um usuário.
418
+
419
+ ```python
420
+ from shared_auth.mixins import UserMixin
421
+
422
+ class UserPreferences(UserMixin):
423
+ theme = models.CharField(max_length=20, default='light')
424
+ notifications_enabled = models.BooleanField(default=True)
425
+
426
+ # Uso
427
+ prefs = UserPreferences.objects.create(user_id=456, theme='dark')
428
+ print(prefs.user.email)
429
+ print(prefs.user_full_name)
430
+ ```
431
+
432
+ #### 3. OrganizationUserMixin
433
+ Para models que pertencem a organização E usuário (mais comum).
434
+
435
+ ```python
436
+ from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin
437
+
438
+ class Tarefa(OrganizationUserMixin, TimestampedMixin):
439
+ titulo = models.CharField(max_length=200)
440
+ descricao = models.TextField()
441
+ status = models.CharField(max_length=20, default='pending')
442
+
443
+ # Uso
444
+ tarefa = Tarefa.objects.create(
445
+ organization_id=123,
446
+ user_id=456,
447
+ titulo='Implementar feature X'
448
+ )
449
+
450
+ # Validações
451
+ if tarefa.validate_user_belongs_to_organization():
452
+ print("✓ Usuário pertence à organização")
453
+
454
+ if tarefa.user_can_access(outro_user_id):
455
+ print("✓ Outro usuário pode acessar")
456
+ ```
457
+
458
+ ### Managers Otimizados
459
+
460
+ ```python
461
+ from shared_auth.managers import BaseAuthManager
462
+
463
+ class Pedido(OrganizationUserMixin):
464
+ # ...
465
+ objects = BaseAuthManager()
466
+
467
+ # Filtrar por organização
468
+ pedidos = Pedido.objects.for_organization(123)
469
+
470
+ # Filtrar por usuário
471
+ meus_pedidos = Pedido.objects.for_user(456)
472
+
473
+ # Prefetch automático (evita N+1)
474
+ pedidos = Pedido.objects.with_auth_data()
475
+ for pedido in pedidos:
476
+ print(pedido.organization.name) # Sem query adicional
477
+ print(pedido.user.email) # Sem query adicional
478
+ ```
479
+
480
+ ### Serializers - Variações
481
+
482
+ #### Versão Completa (Detail)
483
+ ```python
484
+ from shared_auth.serializers import OrganizationUserSerializerMixin
485
+
486
+ class PedidoDetailSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
487
+ class Meta:
488
+ model = Pedido
489
+ fields = ['id', 'numero', 'organization', 'user', 'created_at']
490
+ ```
491
+
492
+ #### Versão Simplificada (List)
493
+ ```python
494
+ from shared_auth.serializers import (
495
+ OrganizationSimpleSerializerMixin,
496
+ UserSimpleSerializerMixin
497
+ )
498
+
499
+ class PedidoListSerializer(
500
+ OrganizationSimpleSerializerMixin,
501
+ UserSimpleSerializerMixin,
502
+ serializers.ModelSerializer
503
+ ):
504
+ class Meta:
505
+ model = Pedido
506
+ fields = ['id', 'numero', 'organization', 'user']
507
+
508
+ # Response com dados reduzidos
509
+ {
510
+ "id": 1,
511
+ "numero": "PED-001",
512
+ "organization": {
513
+ "id": 123,
514
+ "name": "Empresa XYZ",
515
+ "cnpj": "12.345.678/0001-90"
516
+ },
517
+ "user": {
518
+ "id": 456,
519
+ "email": "joao@xyz.com",
520
+ "full_name": "João Silva"
521
+ }
522
+ }
523
+ ```
524
+
525
+ #### Customização Avançada
526
+ ```python
527
+ class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
528
+
529
+ def get_organization(self, obj):
530
+ """Override para adicionar campos customizados"""
531
+ org_data = super().get_organization(obj)
532
+
533
+ if org_data:
534
+ # Adicionar dados extras
535
+ org_data['logo_url'] = f"/logos/{obj.organization_id}.png"
536
+ org_data['member_count'] = obj.organization.members.count()
537
+
538
+ return org_data
539
+ ```
540
+
541
+ ### Middleware
542
+
543
+ #### SharedAuthMiddleware
544
+ Autentica usuário baseado no token.
545
+
546
+ ```python
547
+ # settings.py
548
+ MIDDLEWARE = [
549
+ # ...
550
+ 'shared_auth.middleware.SharedAuthMiddleware',
551
+ ]
552
+ ```
553
+
554
+ **Busca token em:**
555
+ - Header: `Authorization: Token <token>`
556
+ - Header: `X-Auth-Token: <token>`
557
+ - Cookie: `auth_token`
558
+
559
+ **Adiciona ao request:**
560
+ - `request.user` - Objeto User autenticado
561
+ - `request.auth` - Token object
562
+
563
+ #### OrganizationMiddleware
564
+ Adiciona organização logada ao request.
565
+
566
+ ```python
567
+ # settings.py
568
+ MIDDLEWARE = [
569
+ 'shared_auth.middleware.SharedAuthMiddleware',
570
+ 'shared_auth.middleware.OrganizationMiddleware', # Depois do Auth
571
+ ]
572
+ ```
573
+
574
+ **Busca organização:**
575
+ 1. Header `X-Organization: <org_id>`
576
+ 2. Primeira organização do usuário autenticado
577
+
578
+ **Adiciona ao request:**
579
+ - `request.organization_id` - ID da organização
580
+ - `request.organization` - Objeto SharedOrganization
581
+
582
+ **Uso em views:**
583
+ ```python
584
+ def my_view(request):
585
+ org_id = request.organization_id
586
+ org = request.organization
587
+
588
+ if org:
589
+ print(f"Organização logada: {org.name}")
590
+ ```
591
+
592
+ ### Permissions
593
+
594
+ ```python
595
+ from shared_auth.permissions import (
596
+ IsAuthenticated,
597
+ HasActiveOrganization,
598
+ IsSameOrganization,
599
+ IsOwnerOrSameOrganization,
600
+ )
601
+
602
+ class PedidoViewSet(viewsets.ModelViewSet):
603
+ permission_classes = [
604
+ IsAuthenticated, # Requer autenticação
605
+ HasActiveOrganization, # Requer organização ativa
606
+ IsSameOrganization, # Objeto da mesma org
607
+ ]
608
+
609
+ # Ou combinações
610
+ class TarefaViewSet(viewsets.ModelViewSet):
611
+ permission_classes = [IsOwnerOrSameOrganization]
612
+ # Permite se for dono OU da mesma organização
613
+ ```
614
+
615
+ ### Authentication
616
+
617
+ ```python
618
+ # Em qualquer view/viewset DRF
619
+ from shared_auth.authentication import SharedTokenAuthentication
620
+
621
+ class MyAPIView(APIView):
622
+ authentication_classes = [SharedTokenAuthentication]
623
+
624
+ def get(self, request):
625
+ user = request.user # User autenticado
626
+ token = request.auth # SharedToken object
627
+
628
+ return Response({
629
+ 'user': user.email,
630
+ 'token_created': token.created
631
+ })
632
+ ```
633
+
634
+ ---
635
+
636
+ ## 🔍 API Reference
637
+
638
+ ### Models
639
+
640
+ #### SharedOrganization
641
+
642
+ ```python
643
+ from shared_auth.models import SharedOrganization
644
+
645
+ # Campos
646
+ org.id
647
+ org.name
648
+ org.fantasy_name
649
+ org.cnpj
650
+ org.email
651
+ org.telephone
652
+ org.cellphone
653
+ org.image_organization
654
+ org.is_branch
655
+ org.main_organization_id
656
+ org.created_at
657
+ org.updated_at
658
+ org.deleted_at
659
+
660
+ # Properties
661
+ org.main_organization # SharedOrganization | None
662
+ org.branches # QuerySet[SharedOrganization]
663
+ org.members # QuerySet[SharedMember]
664
+ org.users # QuerySet[User]
665
+
666
+ # Métodos
667
+ org.is_active() # bool
668
+ ```
669
+
670
+ #### User
671
+
672
+ ```python
673
+ from shared_auth.models import User
674
+
675
+ # Campos (AbstractUser + custom)
676
+ user.id
677
+ user.username
678
+ user.email
679
+ user.first_name
680
+ user.last_name
681
+ user.is_active
682
+ user.is_staff
683
+ user.is_superuser
684
+ user.date_joined
685
+ user.last_login
686
+ user.avatar
687
+ user.createdat
688
+ user.updatedat
689
+ user.deleted_at
690
+
691
+ # Properties
692
+ user.organizations # QuerySet[SharedOrganization]
693
+
694
+ # Métodos
695
+ user.get_full_name() # str
696
+ user.get_org(organization_id) # SharedOrganization | raise
697
+ ```
698
+
699
+ #### SharedMember
700
+
701
+ ```python
702
+ from shared_auth.models import SharedMember
703
+
704
+ # Campos
705
+ member.id
706
+ member.user_id
707
+ member.organization_id
708
+ member.metadata # JSONField
709
+
710
+ # Properties
711
+ member.user # User
712
+ member.organization # SharedOrganization
713
+ ```
714
+
715
+ #### SharedToken
716
+
717
+ ```python
718
+ from shared_auth.models import SharedToken
719
+
720
+ # Campos
721
+ token.key # Primary Key
722
+ token.user_id
723
+ token.created
724
+
725
+ # Properties
726
+ token.user # User
727
+
728
+ # Métodos
729
+ token.is_valid() # bool
730
+ ```
731
+
732
+ ### Managers
733
+
734
+ #### SharedOrganizationManager
735
+
736
+ ```python
737
+ from shared_auth.models import SharedOrganization
738
+
739
+ SharedOrganization.objects.get_or_fail(123) # Org | raise OrganizationNotFoundError
740
+ SharedOrganization.objects.active() # QuerySet (deleted_at is null)
741
+ SharedOrganization.objects.branches() # QuerySet (is_branch=True)
742
+ SharedOrganization.objects.main_organizations() # QuerySet (is_branch=False)
743
+ SharedOrganization.objects.by_cnpj('12.345.678/0001-90') # Org | None
744
+ ```
745
+
746
+ #### SharedUserManager
747
+
748
+ ```python
749
+ from shared_auth.models import User
750
+
751
+ User.objects.get_or_fail(456) # User | raise UserNotFoundError
752
+ User.objects.active() # QuerySet (is_active=True, deleted_at is null)
753
+ User.objects.by_email('user@example.com') # User | None
754
+ ```
755
+
756
+ #### SharedMemberManager
757
+
758
+ ```python
759
+ from shared_auth.models import SharedMember
760
+
761
+ SharedMember.objects.for_user(456) # QuerySet
762
+ SharedMember.objects.for_organization(123) # QuerySet
763
+ ```
764
+
765
+ #### BaseAuthManager (para seus models)
766
+
767
+ ```python
768
+ # Quando usa OrganizationMixin
769
+ Model.objects.for_organization(123) # QuerySet
770
+ Model.objects.for_organizations([123, 456]) # QuerySet
771
+ Model.objects.with_organization_data() # List com prefetch
772
+
773
+ # Quando usa UserMixin
774
+ Model.objects.for_user(456) # QuerySet
775
+ Model.objects.for_users([456, 789]) # QuerySet
776
+ Model.objects.with_user_data() # List com prefetch
777
+
778
+ # Quando usa OrganizationUserMixin
779
+ Model.objects.with_auth_data() # List com prefetch de org e user
780
+ Model.objects.create_with_validation(
781
+ organization_id=123,
782
+ user_id=456,
783
+ **kwargs
784
+ ) # Valida membership antes de criar
785
+ ```
786
+
787
+ ### Exceptions
788
+
789
+ ```python
790
+ from shared_auth.exceptions import (
791
+ SharedAuthError,
792
+ OrganizationNotFoundError,
793
+ UserNotFoundError,
794
+ DatabaseConnectionError,
795
+ )
796
+
797
+ try:
798
+ org = SharedOrganization.objects.get_or_fail(999)
799
+ except OrganizationNotFoundError as e:
800
+ print(e) # "Organização com ID 999 não encontrada"
801
+ ```
802
+
803
+ ---
804
+
805
+ ## 🎯 Casos de Uso Reais
806
+
807
+ ### Sistema de Pedidos Multi-Tenant
808
+
809
+ ```python
810
+ # models.py
811
+ from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin
812
+
813
+ class Pedido(OrganizationUserMixin, TimestampedMixin):
814
+ numero = models.CharField(max_length=20, unique=True)
815
+ valor_total = models.DecimalField(max_digits=10, decimal_places=2)
816
+ status = models.CharField(max_length=20)
817
+
818
+ objects = BaseAuthManager()
819
+
820
+ class ItemPedido(models.Model):
821
+ pedido = models.ForeignKey(Pedido, related_name='itens')
822
+ produto = models.CharField(max_length=200)
823
+ quantidade = models.IntegerField()
824
+ valor_unitario = models.DecimalField(max_digits=10, decimal_places=2)
825
+
826
+ # serializers.py
827
+ from shared_auth.serializers import OrganizationUserSerializerMixin
828
+
829
+ class ItemPedidoSerializer(serializers.ModelSerializer):
830
+ class Meta:
831
+ model = ItemPedido
832
+ fields = ['id', 'produto', 'quantidade', 'valor_unitario']
833
+
834
+ class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
835
+ itens = ItemPedidoSerializer(many=True, read_only=True)
836
+
837
+ class Meta:
838
+ model = Pedido
839
+ fields = [
840
+ 'id', 'numero', 'valor_total', 'status',
841
+ 'organization', 'user', 'itens', 'created_at'
842
+ ]
843
+
844
+ # views.py
845
+ from shared_auth.mixins import LoggedOrganizationMixin
846
+ from shared_auth.permissions import HasActiveOrganization
847
+
848
+ class PedidoViewSet(LoggedOrganizationMixin, viewsets.ModelViewSet):
849
+ serializer_class = PedidoSerializer
850
+ permission_classes = [HasActiveOrganization]
851
+
852
+ def get_queryset(self):
853
+ # Já filtra por organization_id automaticamente
854
+ return super().get_queryset().with_auth_data()
855
+ ```
856
+
857
+ ### Sistema de Tarefas com Responsáveis
858
+
859
+ ```python
860
+ # models.py
861
+ class Tarefa(OrganizationUserMixin, TimestampedMixin):
862
+ """
863
+ user_id = criador
864
+ responsavel_id = quem vai executar
865
+ """
866
+ titulo = models.CharField(max_length=200)
867
+ descricao = models.TextField()
868
+ responsavel_id = models.IntegerField()
869
+ status = models.CharField(max_length=20, default='pending')
870
+
871
+ objects = BaseAuthManager()
872
+
873
+ @property
874
+ def responsavel(self):
875
+ """Acessa usuário responsável"""
876
+ if not hasattr(self, '_cached_responsavel'):
877
+ from shared_auth.models import User
878
+ self._cached_responsavel = User.objects.get_or_fail(self.responsavel_id)
879
+ return self._cached_responsavel
880
+
881
+ # serializers.py
882
+ class TarefaSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
883
+ responsavel = serializers.SerializerMethodField()
884
+
885
+ def get_responsavel(self, obj):
886
+ try:
887
+ resp = obj.responsavel
888
+ return {
889
+ 'id': resp.pk,
890
+ 'email': resp.email,
891
+ 'full_name': resp.get_full_name(),
892
+ }
893
+ except:
894
+ return None
895
+
896
+ class Meta:
897
+ model = Tarefa
898
+ fields = [
899
+ 'id', 'titulo', 'descricao', 'status',
900
+ 'organization', # Organização dona
901
+ 'user', # Criador
902
+ 'responsavel', # Executor
903
+ 'created_at'
904
+ ]
905
+ ```
906
+
907
+ ---
908
+
909
+ ## 🔧 Troubleshooting
910
+
911
+ ### Problema: Queries lentas (N+1)
912
+
913
+ **Solução:** Use os managers com prefetch
914
+
915
+ ```python
916
+ # ❌ Ruim - Causa N+1
917
+ pedidos = Pedido.objects.all()
918
+ for pedido in pedidos:
919
+ print(pedido.organization.name) # Query por item!
920
+
921
+ # ✅ Bom - 3 queries total
922
+ pedidos = Pedido.objects.with_auth_data()
923
+ for pedido in pedidos:
924
+ print(pedido.organization.name) # Sem query adicional
925
+ ```
926
+
927
+ ### Problema: OrganizationNotFoundError
928
+
929
+ **Causa:** ID de organização inválido ou deletada
930
+
931
+ **Solução:**
932
+ ```python
933
+ # Usar try/except
934
+ try:
935
+ org = SharedOrganization.objects.get_or_fail(org_id)
936
+ except OrganizationNotFoundError:
937
+ # Tratar erro
938
+ return Response({'error': 'Organização não encontrada'}, status=404)
939
+
940
+ # Ou usar filter
941
+ org = SharedOrganization.objects.filter(pk=org_id).first()
942
+ if not org:
943
+ # Tratar
944
+ ```
945
+
946
+ ### Problema: Erro de conexão com auth_db
947
+
948
+ **Solução:** Verificar configuração do database router e permissões
949
+
950
+ ```python
951
+ # Testar conexão
952
+ from django.db import connections
953
+
954
+ connection = connections['auth_db']
955
+ with connection.cursor() as cursor:
956
+ cursor.execute("SELECT 1")
957
+ print("✓ Conexão OK")
958
+ ```
959
+
960
+ ---
961
+
962
+ ## 📝 Changelog
963
+
964
+ ### v0.2.25
965
+ - ✨ Adicionado suporte a imagens (avatar, logo)
966
+ - ✨ StorageBackend para arquivos compartilhados
967
+ - 🐛 Correções nos serializers
968
+ - 📚 Documentação melhorada
969
+
970
+ ### v0.2.0
971
+ - ✨ Middlewares: SharedAuthMiddleware, OrganizationMiddleware
972
+ - ✨ Permissions customizadas
973
+ - ✨ Managers otimizados com prefetch
974
+ - ✨ Serializer mixins com dados aninhados
975
+
976
+ ### v0.1.0
977
+ - 🎉 Versão inicial
978
+ - ✨ Models compartilhados
979
+ - ✨ Mixins básicos
980
+ - ✨ Autenticação via token
981
+
982
+ ---
983
+
984
+ ## 📄 Licença
985
+
986
+ MIT License - veja [LICENSE](LICENSE) para detalhes.
987
+
988
+ ---
989
+
990
+ ## 🤝 Contribuindo
991
+
992
+ Contribuições são bem-vindas! Por favor, abra uma issue ou pull request.
993
+
994
+ ---
995
+
996
+ ## 📧 Suporte
997
+
998
+ Para suporte, abra uma issue no GitHub ou entre em contato com a equipe Maquinaweb.
999
+
1000
+ ---
1001
+
1002
+ **Desenvolvido com ❤️ por Maquinaweb**