Name | maquinaweb-shared-auth JSON |
Version |
0.2.38
JSON |
| download |
home_page | None |
Summary | Models read-only para autenticação compartilhada entre projetos Django. |
upload_time | 2025-10-23 13:01:06 |
maintainer | None |
docs_url | None |
author | None |
requires_python | >=3.8 |
license | MIT |
keywords |
django
auth
models
shared
|
VCS |
 |
bugtrack_url |
|
requirements |
No requirements were recorded.
|
Travis-CI |
No Travis.
|
coveralls test coverage |
No coveralls.
|
# 🔐 Maquinaweb Shared Auth
> Biblioteca Django para autenticação compartilhada entre múltiplos sistemas usando um único banco de dados centralizado.
[](https://www.python.org/downloads/)
[](https://www.djangoproject.com/)
[](LICENSE)
## 📋 Índice
- [Visão Geral](#-visão-geral)
- [Características](#-características)
- [Arquitetura](#️-arquitetura)
- [Instalação](#-instalação)
- [Configuração](#️-configuração)
- [Uso Básico](#-uso-básico)
- [Guias Avançados](#-guias-avançados)
- [API Reference](#-api-reference)
---
## 🎯 Visão Geral
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.
### Problema Resolvido
Ao invés de:
- ❌ Duplicar dados de usuários em cada sistema
- ❌ Fazer requisições HTTP entre sistemas
- ❌ Manter múltiplos bancos de autenticação sincronizados
Você pode:
- ✅ Acessar dados de autenticação diretamente do banco central
- ✅ Usar a interface familiar do Django ORM
- ✅ Garantir consistência de dados entre sistemas
- ✅ Trabalhar com models read-only seguros
---
## ✨ Características
### Core Features
- **🔐 Autenticação Centralizada**: Token-based authentication compartilhado
- **🏢 Multi-Tenancy**: Suporte completo a organizações e filiais
- **👥 Gestão de Membros**: Relacionamento usuários ↔ organizações
- **🔒 Read-Only Safety**: Proteção contra modificações acidentais
- **⚡ Performance**: Managers otimizados com prefetch automático
- **🎨 DRF Ready**: Mixins para serializers com dados aninhados
### Componentes Principais
| Componente | Descrição |
|------------|-----------|
| **Models** | SharedOrganization, User, SharedMember, SharedToken |
| **Mixins** | OrganizationMixin, UserMixin, OrganizationUserMixin |
| **Serializers** | OrganizationSerializerMixin, UserSerializerMixin |
| **Authentication** | SharedTokenAuthentication |
| **Middleware** | SharedAuthMiddleware, OrganizationMiddleware |
| **Permissions** | IsAuthenticated, HasActiveOrganization, IsSameOrganization |
| **Managers** | Métodos otimizados com prefetch e validações |
---
## 🏗️ Arquitetura
```
┌─────────────────────────────────────┐
│ Sistema de Autenticação Central │
│ │
│ ┌──────────────┐ ┌────────────┐ │
│ │Organization │ │ User │ │
│ └──────┬───────┘ └─────┬──────┘ │
│ │ │ │
│ └────────┬───────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ Member │ │
│ │ Token │ │
│ └─────────────┘ │
└──────────────────┬──────────────────┘
│
┌────────────┴────────────┐
│ PostgreSQL/MySQL │
│ (auth_db) │
└────────────┬────────────┘
│
┌────────────┴────────────┐
│ │
┌─────▼─────┐ ┌─────▼─────┐
│ Sistema A │ │ Sistema B │
│ │ │ │
│ Pedidos │ │ Estoque │
│ ├─ org │ │ ├─ org │
│ └─ user │ │ └─ user │
└───────────┘ └───────────┘
```
**Fluxo de Autenticação:**
1. Cliente envia request com token no header
2. Middleware valida token no banco `auth_db`
3. Dados do usuário e organização são anexados ao `request`
4. Sistema cliente acessa dados via ORM (read-only)
---
## 📦 Instalação
### 1. Instalar a Biblioteca
```bash
# Via pip (quando publicado)
pip install maquinaweb-shared-auth
# Ou modo desenvolvimento
pip install -e /path/to/maquinaweb-shared-auth
```
### 2. Adicionar ao requirements.txt
```txt
Django>=4.2
djangorestframework>=3.14
maquinaweb-shared-auth>=0.2.25
```
---
## ⚙️ Configuração
### 1. Settings do Django
```python
# settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'rest_framework',
# Adicionar shared_auth
'shared_auth',
# Suas apps
'myapp',
]
```
### 2. Configurar Banco de Dados
```python
# settings.py
DATABASES = {
'default': {
# Banco do sistema atual
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'meu_sistema_db',
'USER': 'meu_user',
'PASSWORD': 'senha',
'HOST': 'localhost',
'PORT': '5432',
},
'auth_db': {
# Banco centralizado de autenticação (READ-ONLY)
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'sistema_auth_db',
'USER': 'readonly_user',
'PASSWORD': 'senha_readonly',
'HOST': 'auth-server.example.com',
'PORT': '5432',
}
}
# Router para direcionar queries
DATABASE_ROUTERS = ['shared_auth.router.SharedAuthRouter']
```
### 3. Configurar Autenticação (DRF)
```python
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'shared_auth.authentication.SharedTokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'shared_auth.permissions.IsAuthenticated',
],
}
```
### 4. Configurar Middleware (Opcional)
```python
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# Middlewares da shared_auth
'shared_auth.middleware.SharedAuthMiddleware',
'shared_auth.middleware.OrganizationMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
]
```
### 5. Configurar Tabelas (Opcional)
```python
# settings.py
# Customizar nomes das tabelas (se necessário)
SHARED_AUTH_ORGANIZATION_TABLE = 'organization_organization'
SHARED_AUTH_USER_TABLE = 'auth_user'
SHARED_AUTH_MEMBER_TABLE = 'organization_member'
SHARED_AUTH_TOKEN_TABLE = 'authtoken_token'
```
### 6. Criar Usuário Read-Only no PostgreSQL
```sql
-- No servidor de autenticação
CREATE USER readonly_user WITH PASSWORD 'senha_segura_aqui';
-- Conceder permissões
GRANT CONNECT ON DATABASE sistema_auth_db TO readonly_user;
GRANT USAGE ON SCHEMA public TO readonly_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_user;
-- Para tabelas futuras
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO readonly_user;
-- Garantir read-only
ALTER USER readonly_user SET default_transaction_read_only = on;
```
---
## 🚀 Uso Básico
### 1. Models com Mixins
```python
# myapp/models.py
from django.db import models
from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin
from shared_auth.managers import BaseAuthManager
class Pedido(OrganizationUserMixin, TimestampedMixin):
"""Model que pertence a organização e usuário"""
numero = models.CharField(max_length=20, unique=True)
valor_total = models.DecimalField(max_digits=10, decimal_places=2)
status = models.CharField(max_length=20, default='pending')
objects = BaseAuthManager()
def __str__(self):
return f"Pedido {self.numero}"
```
**O que você ganha automaticamente:**
- ✅ Campos: `organization_id`, `user_id`, `created_at`, `updated_at`
- ✅ Properties: `organization`, `user`, `organization_name`, `user_email`
- ✅ Métodos: `validate_user_belongs_to_organization()`, `user_can_access()`
### 2. Serializers com Dados Aninhados
```python
# myapp/serializers.py
from rest_framework import serializers
from shared_auth.serializers import OrganizationUserSerializerMixin
from .models import Pedido
class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
class Meta:
model = Pedido
fields = [
'id', 'numero', 'valor_total', 'status',
'organization', # Objeto completo
'user', # Objeto completo
'created_at',
]
read_only_fields = ['organization', 'user', 'created_at']
```
**Response JSON:**
```json
{
"id": 1,
"numero": "PED-001",
"valor_total": "1500.00",
"status": "pending",
"organization": {
"id": 123,
"name": "Empresa XYZ Ltda",
"fantasy_name": "XYZ",
"cnpj": "12.345.678/0001-90",
"email": "contato@xyz.com",
"is_active": true
},
"user": {
"id": 456,
"username": "joao.silva",
"email": "joao@xyz.com",
"full_name": "João Silva",
"is_active": true
},
"created_at": "2025-10-01T10:00:00Z"
}
```
### 3. ViewSets com Organização
```python
# myapp/views.py
from rest_framework import viewsets
from shared_auth.mixins import LoggedOrganizationMixin
from shared_auth.permissions import HasActiveOrganization, IsSameOrganization
from .models import Pedido
from .serializers import PedidoSerializer
class PedidoViewSet(LoggedOrganizationMixin, viewsets.ModelViewSet):
"""
ViewSet que filtra automaticamente por organização logada
"""
serializer_class = PedidoSerializer
permission_classes = [HasActiveOrganization, IsSameOrganization]
# get_queryset() já filtra por organization_id automaticamente
# perform_create() já adiciona organization_id automaticamente
```
### 4. Acessar Dados Compartilhados
```python
# Em qualquer lugar do código
from shared_auth.models import SharedOrganization, User, SharedMember
# Buscar organização
org = SharedOrganization.objects.get_or_fail(123)
print(org.name) # "Empresa XYZ"
print(org.members) # QuerySet de membros
# Buscar usuário
user = User.objects.get_or_fail(456)
print(user.email) # "joao@xyz.com"
print(user.organizations) # Organizações do usuário
# Verificar membership
member = SharedMember.objects.filter(
user_id=456,
organization_id=123
).first()
if member:
print(f"{member.user.email} é membro de {member.organization.name}")
```
---
## 📚 Guias Avançados
### Mixins para Models
#### 1. OrganizationMixin
Para models que pertencem apenas a uma organização.
```python
from shared_auth.mixins import OrganizationMixin
class EmpresaConfig(OrganizationMixin):
tema_cor = models.CharField(max_length=7, default='#3490dc')
logo = models.ImageField(upload_to='logos/')
# Uso
config = EmpresaConfig.objects.create(organization_id=123, tema_cor='#ff0000')
print(config.organization.name) # Acesso automático
print(config.organization_members) # Membros da organização
```
#### 2. UserMixin
Para models que pertencem apenas a um usuário.
```python
from shared_auth.mixins import UserMixin
class UserPreferences(UserMixin):
theme = models.CharField(max_length=20, default='light')
notifications_enabled = models.BooleanField(default=True)
# Uso
prefs = UserPreferences.objects.create(user_id=456, theme='dark')
print(prefs.user.email)
print(prefs.user_full_name)
```
#### 3. OrganizationUserMixin
Para models que pertencem a organização E usuário (mais comum).
```python
from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin
class Tarefa(OrganizationUserMixin, TimestampedMixin):
titulo = models.CharField(max_length=200)
descricao = models.TextField()
status = models.CharField(max_length=20, default='pending')
# Uso
tarefa = Tarefa.objects.create(
organization_id=123,
user_id=456,
titulo='Implementar feature X'
)
# Validações
if tarefa.validate_user_belongs_to_organization():
print("✓ Usuário pertence à organização")
if tarefa.user_can_access(outro_user_id):
print("✓ Outro usuário pode acessar")
```
### Managers Otimizados
```python
from shared_auth.managers import BaseAuthManager
class Pedido(OrganizationUserMixin):
# ...
objects = BaseAuthManager()
# Filtrar por organização
pedidos = Pedido.objects.for_organization(123)
# Filtrar por usuário
meus_pedidos = Pedido.objects.for_user(456)
# Prefetch automático (evita N+1)
pedidos = Pedido.objects.with_auth_data()
for pedido in pedidos:
print(pedido.organization.name) # Sem query adicional
print(pedido.user.email) # Sem query adicional
```
### Serializers - Variações
#### Versão Completa (Detail)
```python
from shared_auth.serializers import OrganizationUserSerializerMixin
class PedidoDetailSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
class Meta:
model = Pedido
fields = ['id', 'numero', 'organization', 'user', 'created_at']
```
#### Versão Simplificada (List)
```python
from shared_auth.serializers import (
OrganizationSimpleSerializerMixin,
UserSimpleSerializerMixin
)
class PedidoListSerializer(
OrganizationSimpleSerializerMixin,
UserSimpleSerializerMixin,
serializers.ModelSerializer
):
class Meta:
model = Pedido
fields = ['id', 'numero', 'organization', 'user']
# Response com dados reduzidos
{
"id": 1,
"numero": "PED-001",
"organization": {
"id": 123,
"name": "Empresa XYZ",
"cnpj": "12.345.678/0001-90"
},
"user": {
"id": 456,
"email": "joao@xyz.com",
"full_name": "João Silva"
}
}
```
#### Customização Avançada
```python
class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
def get_organization(self, obj):
"""Override para adicionar campos customizados"""
org_data = super().get_organization(obj)
if org_data:
# Adicionar dados extras
org_data['logo_url'] = f"/logos/{obj.organization_id}.png"
org_data['member_count'] = obj.organization.members.count()
return org_data
```
### Middleware
#### SharedAuthMiddleware
Autentica usuário baseado no token.
```python
# settings.py
MIDDLEWARE = [
# ...
'shared_auth.middleware.SharedAuthMiddleware',
]
```
**Busca token em:**
- Header: `Authorization: Token <token>`
- Header: `X-Auth-Token: <token>`
- Cookie: `auth_token`
**Adiciona ao request:**
- `request.user` - Objeto User autenticado
- `request.auth` - Token object
#### OrganizationMiddleware
Adiciona organização logada ao request.
```python
# settings.py
MIDDLEWARE = [
'shared_auth.middleware.SharedAuthMiddleware',
'shared_auth.middleware.OrganizationMiddleware', # Depois do Auth
]
```
**Busca organização:**
1. Header `X-Organization: <org_id>`
2. Primeira organização do usuário autenticado
**Adiciona ao request:**
- `request.organization_id` - ID da organização
- `request.organization` - Objeto SharedOrganization
**Uso em views:**
```python
def my_view(request):
org_id = request.organization_id
org = request.organization
if org:
print(f"Organização logada: {org.name}")
```
### Permissions
```python
from shared_auth.permissions import (
IsAuthenticated,
HasActiveOrganization,
IsSameOrganization,
IsOwnerOrSameOrganization,
)
class PedidoViewSet(viewsets.ModelViewSet):
permission_classes = [
IsAuthenticated, # Requer autenticação
HasActiveOrganization, # Requer organização ativa
IsSameOrganization, # Objeto da mesma org
]
# Ou combinações
class TarefaViewSet(viewsets.ModelViewSet):
permission_classes = [IsOwnerOrSameOrganization]
# Permite se for dono OU da mesma organização
```
### Authentication
```python
# Em qualquer view/viewset DRF
from shared_auth.authentication import SharedTokenAuthentication
class MyAPIView(APIView):
authentication_classes = [SharedTokenAuthentication]
def get(self, request):
user = request.user # User autenticado
token = request.auth # SharedToken object
return Response({
'user': user.email,
'token_created': token.created
})
```
---
## 🔍 API Reference
### Models
#### SharedOrganization
```python
from shared_auth.models import SharedOrganization
# Campos
org.id
org.name
org.fantasy_name
org.cnpj
org.email
org.telephone
org.cellphone
org.image_organization
org.is_branch
org.main_organization_id
org.created_at
org.updated_at
org.deleted_at
# Properties
org.main_organization # SharedOrganization | None
org.branches # QuerySet[SharedOrganization]
org.members # QuerySet[SharedMember]
org.users # QuerySet[User]
# Métodos
org.is_active() # bool
```
#### User
```python
from shared_auth.models import User
# Campos (AbstractUser + custom)
user.id
user.username
user.email
user.first_name
user.last_name
user.is_active
user.is_staff
user.is_superuser
user.date_joined
user.last_login
user.avatar
user.createdat
user.updatedat
user.deleted_at
# Properties
user.organizations # QuerySet[SharedOrganization]
# Métodos
user.get_full_name() # str
user.get_org(organization_id) # SharedOrganization | raise
```
#### SharedMember
```python
from shared_auth.models import SharedMember
# Campos
member.id
member.user_id
member.organization_id
member.metadata # JSONField
# Properties
member.user # User
member.organization # SharedOrganization
```
#### SharedToken
```python
from shared_auth.models import SharedToken
# Campos
token.key # Primary Key
token.user_id
token.created
# Properties
token.user # User
# Métodos
token.is_valid() # bool
```
### Managers
#### SharedOrganizationManager
```python
from shared_auth.models import SharedOrganization
SharedOrganization.objects.get_or_fail(123) # Org | raise OrganizationNotFoundError
SharedOrganization.objects.active() # QuerySet (deleted_at is null)
SharedOrganization.objects.branches() # QuerySet (is_branch=True)
SharedOrganization.objects.main_organizations() # QuerySet (is_branch=False)
SharedOrganization.objects.by_cnpj('12.345.678/0001-90') # Org | None
```
#### UserManager
```python
from shared_auth.models import User
User.objects.get_or_fail(456) # User | raise UserNotFoundError
User.objects.active() # QuerySet (is_active=True, deleted_at is null)
User.objects.by_email('user@example.com') # User | None
```
#### SharedMemberManager
```python
from shared_auth.models import SharedMember
SharedMember.objects.for_user(456) # QuerySet
SharedMember.objects.for_organization(123) # QuerySet
```
#### BaseAuthManager (para seus models)
```python
# Quando usa OrganizationMixin
Model.objects.for_organization(123) # QuerySet
Model.objects.for_organizations([123, 456]) # QuerySet
Model.objects.with_organization_data() # List com prefetch
# Quando usa UserMixin
Model.objects.for_user(456) # QuerySet
Model.objects.for_users([456, 789]) # QuerySet
Model.objects.with_user_data() # List com prefetch
# Quando usa OrganizationUserMixin
Model.objects.with_auth_data() # List com prefetch de org e user
Model.objects.create_with_validation(
organization_id=123,
user_id=456,
**kwargs
) # Valida membership antes de criar
```
### Exceptions
```python
from shared_auth.exceptions import (
SharedAuthError,
OrganizationNotFoundError,
UserNotFoundError,
DatabaseConnectionError,
)
try:
org = SharedOrganization.objects.get_or_fail(999)
except OrganizationNotFoundError as e:
print(e) # "Organização com ID 999 não encontrada"
```
---
## 🎯 Casos de Uso Reais
### Sistema de Pedidos Multi-Tenant
```python
# models.py
from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin
class Pedido(OrganizationUserMixin, TimestampedMixin):
numero = models.CharField(max_length=20, unique=True)
valor_total = models.DecimalField(max_digits=10, decimal_places=2)
status = models.CharField(max_length=20)
objects = BaseAuthManager()
class ItemPedido(models.Model):
pedido = models.ForeignKey(Pedido, related_name='itens')
produto = models.CharField(max_length=200)
quantidade = models.IntegerField()
valor_unitario = models.DecimalField(max_digits=10, decimal_places=2)
# serializers.py
from shared_auth.serializers import OrganizationUserSerializerMixin
class ItemPedidoSerializer(serializers.ModelSerializer):
class Meta:
model = ItemPedido
fields = ['id', 'produto', 'quantidade', 'valor_unitario']
class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
itens = ItemPedidoSerializer(many=True, read_only=True)
class Meta:
model = Pedido
fields = [
'id', 'numero', 'valor_total', 'status',
'organization', 'user', 'itens', 'created_at'
]
# views.py
from shared_auth.mixins import LoggedOrganizationMixin
from shared_auth.permissions import HasActiveOrganization
class PedidoViewSet(LoggedOrganizationMixin, viewsets.ModelViewSet):
serializer_class = PedidoSerializer
permission_classes = [HasActiveOrganization]
def get_queryset(self):
# Já filtra por organization_id automaticamente
return super().get_queryset().with_auth_data()
```
### Sistema de Tarefas com Responsáveis
```python
# models.py
class Tarefa(OrganizationUserMixin, TimestampedMixin):
"""
user_id = criador
responsavel_id = quem vai executar
"""
titulo = models.CharField(max_length=200)
descricao = models.TextField()
responsavel_id = models.IntegerField()
status = models.CharField(max_length=20, default='pending')
objects = BaseAuthManager()
@property
def responsavel(self):
"""Acessa usuário responsável"""
if not hasattr(self, '_cached_responsavel'):
from shared_auth.models import User
self._cached_responsavel = User.objects.get_or_fail(self.responsavel_id)
return self._cached_responsavel
# serializers.py
class TarefaSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
responsavel = serializers.SerializerMethodField()
def get_responsavel(self, obj):
try:
resp = obj.responsavel
return {
'id': resp.pk,
'email': resp.email,
'full_name': resp.get_full_name(),
}
except:
return None
class Meta:
model = Tarefa
fields = [
'id', 'titulo', 'descricao', 'status',
'organization', # Organização dona
'user', # Criador
'responsavel', # Executor
'created_at'
]
```
---
## 🔧 Troubleshooting
### Problema: Queries lentas (N+1)
**Solução:** Use os managers com prefetch
```python
# ❌ Ruim - Causa N+1
pedidos = Pedido.objects.all()
for pedido in pedidos:
print(pedido.organization.name) # Query por item!
# ✅ Bom - 3 queries total
pedidos = Pedido.objects.with_auth_data()
for pedido in pedidos:
print(pedido.organization.name) # Sem query adicional
```
### Problema: OrganizationNotFoundError
**Causa:** ID de organização inválido ou deletada
**Solução:**
```python
# Usar try/except
try:
org = SharedOrganization.objects.get_or_fail(org_id)
except OrganizationNotFoundError:
# Tratar erro
return Response({'error': 'Organização não encontrada'}, status=404)
# Ou usar filter
org = SharedOrganization.objects.filter(pk=org_id).first()
if not org:
# Tratar
```
### Problema: Erro de conexão com auth_db
**Solução:** Verificar configuração do database router e permissões
```python
# Testar conexão
from django.db import connections
connection = connections['auth_db']
with connection.cursor() as cursor:
cursor.execute("SELECT 1")
print("✓ Conexão OK")
```
---
## 📝 Changelog
### v0.2.25
- ✨ Adicionado suporte a imagens (avatar, logo)
- ✨ StorageBackend para arquivos compartilhados
- 🐛 Correções nos serializers
- 📚 Documentação melhorada
### v0.2.0
- ✨ Middlewares: SharedAuthMiddleware, OrganizationMiddleware
- ✨ Permissions customizadas
- ✨ Managers otimizados com prefetch
- ✨ Serializer mixins com dados aninhados
### v0.1.0
- 🎉 Versão inicial
- ✨ Models compartilhados
- ✨ Mixins básicos
- ✨ Autenticação via token
---
## 📄 Licença
MIT License - veja [LICENSE](LICENSE) para detalhes.
---
## 🤝 Contribuindo
Contribuições são bem-vindas! Por favor, abra uma issue ou pull request.
---
## 📧 Suporte
Para suporte, abra uma issue no GitHub ou entre em contato com a equipe Maquinaweb.
---
**Desenvolvido com ❤️ por Maquinaweb**
Raw data
{
"_id": null,
"home_page": null,
"name": "maquinaweb-shared-auth",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "django, auth, models, shared",
"author": null,
"author_email": "Seu Nome <seuemail@dominio.com>",
"download_url": "https://files.pythonhosted.org/packages/d1/f2/d14e3265a190e87ecb8fad183100c5de8f477a88054cec1c27ef2c573db1/maquinaweb_shared_auth-0.2.38.tar.gz",
"platform": null,
"description": "# \ud83d\udd10 Maquinaweb Shared Auth\n\n> Biblioteca Django para autentica\u00e7\u00e3o compartilhada entre m\u00faltiplos sistemas usando um \u00fanico banco de dados centralizado.\n\n[](https://www.python.org/downloads/)\n[](https://www.djangoproject.com/)\n[](LICENSE)\n\n## \ud83d\udccb \u00cdndice\n\n- [Vis\u00e3o Geral](#-vis\u00e3o-geral)\n- [Caracter\u00edsticas](#-caracter\u00edsticas)\n- [Arquitetura](#\ufe0f-arquitetura)\n- [Instala\u00e7\u00e3o](#-instala\u00e7\u00e3o)\n- [Configura\u00e7\u00e3o](#\ufe0f-configura\u00e7\u00e3o)\n- [Uso B\u00e1sico](#-uso-b\u00e1sico)\n- [Guias Avan\u00e7ados](#-guias-avan\u00e7ados)\n- [API Reference](#-api-reference)\n\n---\n\n## \ud83c\udfaf Vis\u00e3o Geral\n\nA **Maquinaweb Shared Auth** permite que m\u00faltiplos sistemas Django compartilhem dados de autentica\u00e7\u00e3o, usu\u00e1rios e organiza\u00e7\u00f5es atrav\u00e9s de um banco de dados centralizado, sem necessidade de requisi\u00e7\u00f5es HTTP.\n\n### Problema Resolvido\n\nAo inv\u00e9s de:\n- \u274c Duplicar dados de usu\u00e1rios em cada sistema\n- \u274c Fazer requisi\u00e7\u00f5es HTTP entre sistemas\n- \u274c Manter m\u00faltiplos bancos de autentica\u00e7\u00e3o sincronizados\n\nVoc\u00ea pode:\n- \u2705 Acessar dados de autentica\u00e7\u00e3o diretamente do banco central\n- \u2705 Usar a interface familiar do Django ORM\n- \u2705 Garantir consist\u00eancia de dados entre sistemas\n- \u2705 Trabalhar com models read-only seguros\n\n---\n\n## \u2728 Caracter\u00edsticas\n\n### Core Features\n\n- **\ud83d\udd10 Autentica\u00e7\u00e3o Centralizada**: Token-based authentication compartilhado\n- **\ud83c\udfe2 Multi-Tenancy**: Suporte completo a organiza\u00e7\u00f5es e filiais\n- **\ud83d\udc65 Gest\u00e3o de Membros**: Relacionamento usu\u00e1rios \u2194 organiza\u00e7\u00f5es\n- **\ud83d\udd12 Read-Only Safety**: Prote\u00e7\u00e3o contra modifica\u00e7\u00f5es acidentais\n- **\u26a1 Performance**: Managers otimizados com prefetch autom\u00e1tico\n- **\ud83c\udfa8 DRF Ready**: Mixins para serializers com dados aninhados\n\n### Componentes Principais\n\n| Componente | Descri\u00e7\u00e3o |\n|------------|-----------|\n| **Models** | SharedOrganization, User, SharedMember, SharedToken |\n| **Mixins** | OrganizationMixin, UserMixin, OrganizationUserMixin |\n| **Serializers** | OrganizationSerializerMixin, UserSerializerMixin |\n| **Authentication** | SharedTokenAuthentication |\n| **Middleware** | SharedAuthMiddleware, OrganizationMiddleware |\n| **Permissions** | IsAuthenticated, HasActiveOrganization, IsSameOrganization |\n| **Managers** | M\u00e9todos otimizados com prefetch e valida\u00e7\u00f5es |\n\n---\n\n## \ud83c\udfd7\ufe0f Arquitetura\n\n```\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Sistema de Autentica\u00e7\u00e3o Central \u2502\n\u2502 \u2502\n\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n\u2502 \u2502Organization \u2502 \u2502 User \u2502 \u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n\u2502 \u2502 \u2502 \u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n\u2502 \u2502 \u2502\n\u2502 \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u25bc\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n\u2502 \u2502 Member \u2502 \u2502\n\u2502 \u2502 Token \u2502 \u2502\n\u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 PostgreSQL/MySQL \u2502\n \u2502 (auth_db) \u2502\n \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n \u2502\n \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n \u2502 \u2502\n\u250c\u2500\u2500\u2500\u2500\u2500\u25bc\u2500\u2500\u2500\u2500\u2500\u2510 \u250c\u2500\u2500\u2500\u2500\u2500\u25bc\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Sistema A \u2502 \u2502 Sistema B \u2502\n\u2502 \u2502 \u2502 \u2502\n\u2502 Pedidos \u2502 \u2502 Estoque \u2502\n\u2502 \u251c\u2500 org \u2502 \u2502 \u251c\u2500 org \u2502\n\u2502 \u2514\u2500 user \u2502 \u2502 \u2514\u2500 user \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n```\n\n**Fluxo de Autentica\u00e7\u00e3o:**\n\n1. Cliente envia request com token no header\n2. Middleware valida token no banco `auth_db`\n3. Dados do usu\u00e1rio e organiza\u00e7\u00e3o s\u00e3o anexados ao `request`\n4. Sistema cliente acessa dados via ORM (read-only)\n\n---\n\n## \ud83d\udce6 Instala\u00e7\u00e3o\n\n### 1. Instalar a Biblioteca\n\n```bash\n# Via pip (quando publicado)\npip install maquinaweb-shared-auth\n\n# Ou modo desenvolvimento\npip install -e /path/to/maquinaweb-shared-auth\n```\n\n### 2. Adicionar ao requirements.txt\n\n```txt\nDjango>=4.2\ndjangorestframework>=3.14\nmaquinaweb-shared-auth>=0.2.25\n```\n\n---\n\n## \u2699\ufe0f Configura\u00e7\u00e3o\n\n### 1. Settings do Django\n\n```python\n# settings.py\n\nINSTALLED_APPS = [\n 'django.contrib.admin',\n 'django.contrib.auth',\n 'django.contrib.contenttypes',\n 'django.contrib.sessions',\n 'rest_framework',\n \n # Adicionar shared_auth\n 'shared_auth',\n \n # Suas apps\n 'myapp',\n]\n```\n\n### 2. Configurar Banco de Dados\n\n```python\n# settings.py\n\nDATABASES = {\n 'default': {\n # Banco do sistema atual\n 'ENGINE': 'django.db.backends.postgresql',\n 'NAME': 'meu_sistema_db',\n 'USER': 'meu_user',\n 'PASSWORD': 'senha',\n 'HOST': 'localhost',\n 'PORT': '5432',\n },\n 'auth_db': {\n # Banco centralizado de autentica\u00e7\u00e3o (READ-ONLY)\n 'ENGINE': 'django.db.backends.postgresql',\n 'NAME': 'sistema_auth_db',\n 'USER': 'readonly_user',\n 'PASSWORD': 'senha_readonly',\n 'HOST': 'auth-server.example.com',\n 'PORT': '5432',\n }\n}\n\n# Router para direcionar queries\nDATABASE_ROUTERS = ['shared_auth.router.SharedAuthRouter']\n```\n\n### 3. Configurar Autentica\u00e7\u00e3o (DRF)\n\n```python\n# settings.py\n\nREST_FRAMEWORK = {\n 'DEFAULT_AUTHENTICATION_CLASSES': [\n 'shared_auth.authentication.SharedTokenAuthentication',\n ],\n 'DEFAULT_PERMISSION_CLASSES': [\n 'shared_auth.permissions.IsAuthenticated',\n ],\n}\n```\n\n### 4. Configurar Middleware (Opcional)\n\n```python\n# settings.py\n\nMIDDLEWARE = [\n 'django.middleware.security.SecurityMiddleware',\n 'django.contrib.sessions.middleware.SessionMiddleware',\n 'django.middleware.common.CommonMiddleware',\n \n # Middlewares da shared_auth\n 'shared_auth.middleware.SharedAuthMiddleware',\n 'shared_auth.middleware.OrganizationMiddleware',\n \n 'django.middleware.csrf.CsrfViewMiddleware',\n 'django.contrib.auth.middleware.AuthenticationMiddleware',\n 'django.contrib.messages.middleware.MessageMiddleware',\n]\n```\n\n### 5. Configurar Tabelas (Opcional)\n\n```python\n# settings.py\n\n# Customizar nomes das tabelas (se necess\u00e1rio)\nSHARED_AUTH_ORGANIZATION_TABLE = 'organization_organization'\nSHARED_AUTH_USER_TABLE = 'auth_user'\nSHARED_AUTH_MEMBER_TABLE = 'organization_member'\nSHARED_AUTH_TOKEN_TABLE = 'authtoken_token'\n```\n\n### 6. Criar Usu\u00e1rio Read-Only no PostgreSQL\n\n```sql\n-- No servidor de autentica\u00e7\u00e3o\nCREATE USER readonly_user WITH PASSWORD 'senha_segura_aqui';\n\n-- Conceder permiss\u00f5es\nGRANT CONNECT ON DATABASE sistema_auth_db TO readonly_user;\nGRANT USAGE ON SCHEMA public TO readonly_user;\nGRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_user;\n\n-- Para tabelas futuras\nALTER DEFAULT PRIVILEGES IN SCHEMA public \nGRANT SELECT ON TABLES TO readonly_user;\n\n-- Garantir read-only\nALTER USER readonly_user SET default_transaction_read_only = on;\n```\n\n---\n\n## \ud83d\ude80 Uso B\u00e1sico\n\n### 1. Models com Mixins\n\n```python\n# myapp/models.py\nfrom django.db import models\nfrom shared_auth.mixins import OrganizationUserMixin, TimestampedMixin\nfrom shared_auth.managers import BaseAuthManager\n\nclass Pedido(OrganizationUserMixin, TimestampedMixin):\n \"\"\"Model que pertence a organiza\u00e7\u00e3o e usu\u00e1rio\"\"\"\n \n numero = models.CharField(max_length=20, unique=True)\n valor_total = models.DecimalField(max_digits=10, decimal_places=2)\n status = models.CharField(max_length=20, default='pending')\n \n objects = BaseAuthManager()\n \n def __str__(self):\n return f\"Pedido {self.numero}\"\n```\n\n**O que voc\u00ea ganha automaticamente:**\n- \u2705 Campos: `organization_id`, `user_id`, `created_at`, `updated_at`\n- \u2705 Properties: `organization`, `user`, `organization_name`, `user_email`\n- \u2705 M\u00e9todos: `validate_user_belongs_to_organization()`, `user_can_access()`\n\n### 2. Serializers com Dados Aninhados\n\n```python\n# myapp/serializers.py\nfrom rest_framework import serializers\nfrom shared_auth.serializers import OrganizationUserSerializerMixin\nfrom .models import Pedido\n\nclass PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):\n class Meta:\n model = Pedido\n fields = [\n 'id', 'numero', 'valor_total', 'status',\n 'organization', # Objeto completo\n 'user', # Objeto completo\n 'created_at',\n ]\n read_only_fields = ['organization', 'user', 'created_at']\n```\n\n**Response JSON:**\n```json\n{\n \"id\": 1,\n \"numero\": \"PED-001\",\n \"valor_total\": \"1500.00\",\n \"status\": \"pending\",\n \"organization\": {\n \"id\": 123,\n \"name\": \"Empresa XYZ Ltda\",\n \"fantasy_name\": \"XYZ\",\n \"cnpj\": \"12.345.678/0001-90\",\n \"email\": \"contato@xyz.com\",\n \"is_active\": true\n },\n \"user\": {\n \"id\": 456,\n \"username\": \"joao.silva\",\n \"email\": \"joao@xyz.com\",\n \"full_name\": \"Jo\u00e3o Silva\",\n \"is_active\": true\n },\n \"created_at\": \"2025-10-01T10:00:00Z\"\n}\n```\n\n### 3. ViewSets com Organiza\u00e7\u00e3o\n\n```python\n# myapp/views.py\nfrom rest_framework import viewsets\nfrom shared_auth.mixins import LoggedOrganizationMixin\nfrom shared_auth.permissions import HasActiveOrganization, IsSameOrganization\nfrom .models import Pedido\nfrom .serializers import PedidoSerializer\n\nclass PedidoViewSet(LoggedOrganizationMixin, viewsets.ModelViewSet):\n \"\"\"\n ViewSet que filtra automaticamente por organiza\u00e7\u00e3o logada\n \"\"\"\n serializer_class = PedidoSerializer\n permission_classes = [HasActiveOrganization, IsSameOrganization]\n \n # get_queryset() j\u00e1 filtra por organization_id automaticamente\n # perform_create() j\u00e1 adiciona organization_id automaticamente\n```\n\n### 4. Acessar Dados Compartilhados\n\n```python\n# Em qualquer lugar do c\u00f3digo\nfrom shared_auth.models import SharedOrganization, User, SharedMember\n\n# Buscar organiza\u00e7\u00e3o\norg = SharedOrganization.objects.get_or_fail(123)\nprint(org.name) # \"Empresa XYZ\"\nprint(org.members) # QuerySet de membros\n\n# Buscar usu\u00e1rio\nuser = User.objects.get_or_fail(456)\nprint(user.email) # \"joao@xyz.com\"\nprint(user.organizations) # Organiza\u00e7\u00f5es do usu\u00e1rio\n\n# Verificar membership\nmember = SharedMember.objects.filter(\n user_id=456,\n organization_id=123\n).first()\n\nif member:\n print(f\"{member.user.email} \u00e9 membro de {member.organization.name}\")\n```\n\n---\n\n## \ud83d\udcda Guias Avan\u00e7ados\n\n### Mixins para Models\n\n#### 1. OrganizationMixin\nPara models que pertencem apenas a uma organiza\u00e7\u00e3o.\n\n```python\nfrom shared_auth.mixins import OrganizationMixin\n\nclass EmpresaConfig(OrganizationMixin):\n tema_cor = models.CharField(max_length=7, default='#3490dc')\n logo = models.ImageField(upload_to='logos/')\n \n# Uso\nconfig = EmpresaConfig.objects.create(organization_id=123, tema_cor='#ff0000')\nprint(config.organization.name) # Acesso autom\u00e1tico\nprint(config.organization_members) # Membros da organiza\u00e7\u00e3o\n```\n\n#### 2. UserMixin\nPara models que pertencem apenas a um usu\u00e1rio.\n\n```python\nfrom shared_auth.mixins import UserMixin\n\nclass UserPreferences(UserMixin):\n theme = models.CharField(max_length=20, default='light')\n notifications_enabled = models.BooleanField(default=True)\n\n# Uso\nprefs = UserPreferences.objects.create(user_id=456, theme='dark')\nprint(prefs.user.email)\nprint(prefs.user_full_name)\n```\n\n#### 3. OrganizationUserMixin\nPara models que pertencem a organiza\u00e7\u00e3o E usu\u00e1rio (mais comum).\n\n```python\nfrom shared_auth.mixins import OrganizationUserMixin, TimestampedMixin\n\nclass Tarefa(OrganizationUserMixin, TimestampedMixin):\n titulo = models.CharField(max_length=200)\n descricao = models.TextField()\n status = models.CharField(max_length=20, default='pending')\n\n# Uso\ntarefa = Tarefa.objects.create(\n organization_id=123,\n user_id=456,\n titulo='Implementar feature X'\n)\n\n# Valida\u00e7\u00f5es\nif tarefa.validate_user_belongs_to_organization():\n print(\"\u2713 Usu\u00e1rio pertence \u00e0 organiza\u00e7\u00e3o\")\n\nif tarefa.user_can_access(outro_user_id):\n print(\"\u2713 Outro usu\u00e1rio pode acessar\")\n```\n\n### Managers Otimizados\n\n```python\nfrom shared_auth.managers import BaseAuthManager\n\nclass Pedido(OrganizationUserMixin):\n # ...\n objects = BaseAuthManager()\n\n# Filtrar por organiza\u00e7\u00e3o\npedidos = Pedido.objects.for_organization(123)\n\n# Filtrar por usu\u00e1rio\nmeus_pedidos = Pedido.objects.for_user(456)\n\n# Prefetch autom\u00e1tico (evita N+1)\npedidos = Pedido.objects.with_auth_data()\nfor pedido in pedidos:\n print(pedido.organization.name) # Sem query adicional\n print(pedido.user.email) # Sem query adicional\n```\n\n### Serializers - Varia\u00e7\u00f5es\n\n#### Vers\u00e3o Completa (Detail)\n```python\nfrom shared_auth.serializers import OrganizationUserSerializerMixin\n\nclass PedidoDetailSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):\n class Meta:\n model = Pedido\n fields = ['id', 'numero', 'organization', 'user', 'created_at']\n```\n\n#### Vers\u00e3o Simplificada (List)\n```python\nfrom shared_auth.serializers import (\n OrganizationSimpleSerializerMixin,\n UserSimpleSerializerMixin\n)\n\nclass PedidoListSerializer(\n OrganizationSimpleSerializerMixin,\n UserSimpleSerializerMixin,\n serializers.ModelSerializer\n):\n class Meta:\n model = Pedido\n fields = ['id', 'numero', 'organization', 'user']\n \n# Response com dados reduzidos\n{\n \"id\": 1,\n \"numero\": \"PED-001\",\n \"organization\": {\n \"id\": 123,\n \"name\": \"Empresa XYZ\",\n \"cnpj\": \"12.345.678/0001-90\"\n },\n \"user\": {\n \"id\": 456,\n \"email\": \"joao@xyz.com\",\n \"full_name\": \"Jo\u00e3o Silva\"\n }\n}\n```\n\n#### Customiza\u00e7\u00e3o Avan\u00e7ada\n```python\nclass PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):\n \n def get_organization(self, obj):\n \"\"\"Override para adicionar campos customizados\"\"\"\n org_data = super().get_organization(obj)\n \n if org_data:\n # Adicionar dados extras\n org_data['logo_url'] = f\"/logos/{obj.organization_id}.png\"\n org_data['member_count'] = obj.organization.members.count()\n \n return org_data\n```\n\n### Middleware\n\n#### SharedAuthMiddleware\nAutentica usu\u00e1rio baseado no token.\n\n```python\n# settings.py\nMIDDLEWARE = [\n # ...\n 'shared_auth.middleware.SharedAuthMiddleware',\n]\n```\n\n**Busca token em:**\n- Header: `Authorization: Token <token>`\n- Header: `X-Auth-Token: <token>`\n- Cookie: `auth_token`\n\n**Adiciona ao request:**\n- `request.user` - Objeto User autenticado\n- `request.auth` - Token object\n\n#### OrganizationMiddleware\nAdiciona organiza\u00e7\u00e3o logada ao request.\n\n```python\n# settings.py\nMIDDLEWARE = [\n 'shared_auth.middleware.SharedAuthMiddleware',\n 'shared_auth.middleware.OrganizationMiddleware', # Depois do Auth\n]\n```\n\n**Busca organiza\u00e7\u00e3o:**\n1. Header `X-Organization: <org_id>`\n2. Primeira organiza\u00e7\u00e3o do usu\u00e1rio autenticado\n\n**Adiciona ao request:**\n- `request.organization_id` - ID da organiza\u00e7\u00e3o\n- `request.organization` - Objeto SharedOrganization\n\n**Uso em views:**\n```python\ndef my_view(request):\n org_id = request.organization_id\n org = request.organization\n \n if org:\n print(f\"Organiza\u00e7\u00e3o logada: {org.name}\")\n```\n\n### Permissions\n\n```python\nfrom shared_auth.permissions import (\n IsAuthenticated,\n HasActiveOrganization,\n IsSameOrganization,\n IsOwnerOrSameOrganization,\n)\n\nclass PedidoViewSet(viewsets.ModelViewSet):\n permission_classes = [\n IsAuthenticated, # Requer autentica\u00e7\u00e3o\n HasActiveOrganization, # Requer organiza\u00e7\u00e3o ativa\n IsSameOrganization, # Objeto da mesma org\n ]\n \n# Ou combina\u00e7\u00f5es\nclass TarefaViewSet(viewsets.ModelViewSet):\n permission_classes = [IsOwnerOrSameOrganization]\n # Permite se for dono OU da mesma organiza\u00e7\u00e3o\n```\n\n### Authentication\n\n```python\n# Em qualquer view/viewset DRF\nfrom shared_auth.authentication import SharedTokenAuthentication\n\nclass MyAPIView(APIView):\n authentication_classes = [SharedTokenAuthentication]\n \n def get(self, request):\n user = request.user # User autenticado\n token = request.auth # SharedToken object\n \n return Response({\n 'user': user.email,\n 'token_created': token.created\n })\n```\n\n---\n\n## \ud83d\udd0d API Reference\n\n### Models\n\n#### SharedOrganization\n\n```python\nfrom shared_auth.models import SharedOrganization\n\n# Campos\norg.id\norg.name\norg.fantasy_name\norg.cnpj\norg.email\norg.telephone\norg.cellphone\norg.image_organization\norg.is_branch\norg.main_organization_id\norg.created_at\norg.updated_at\norg.deleted_at\n\n# Properties\norg.main_organization # SharedOrganization | None\norg.branches # QuerySet[SharedOrganization]\norg.members # QuerySet[SharedMember]\norg.users # QuerySet[User]\n\n# M\u00e9todos\norg.is_active() # bool\n```\n\n#### User\n\n```python\nfrom shared_auth.models import User\n\n# Campos (AbstractUser + custom)\nuser.id\nuser.username\nuser.email\nuser.first_name\nuser.last_name\nuser.is_active\nuser.is_staff\nuser.is_superuser\nuser.date_joined\nuser.last_login\nuser.avatar\nuser.createdat\nuser.updatedat\nuser.deleted_at\n\n# Properties\nuser.organizations # QuerySet[SharedOrganization]\n\n# M\u00e9todos\nuser.get_full_name() # str\nuser.get_org(organization_id) # SharedOrganization | raise\n```\n\n#### SharedMember\n\n```python\nfrom shared_auth.models import SharedMember\n\n# Campos\nmember.id\nmember.user_id\nmember.organization_id\nmember.metadata # JSONField\n\n# Properties\nmember.user # User\nmember.organization # SharedOrganization\n```\n\n#### SharedToken\n\n```python\nfrom shared_auth.models import SharedToken\n\n# Campos\ntoken.key # Primary Key\ntoken.user_id\ntoken.created\n\n# Properties\ntoken.user # User\n\n# M\u00e9todos\ntoken.is_valid() # bool\n```\n\n### Managers\n\n#### SharedOrganizationManager\n\n```python\nfrom shared_auth.models import SharedOrganization\n\nSharedOrganization.objects.get_or_fail(123) # Org | raise OrganizationNotFoundError\nSharedOrganization.objects.active() # QuerySet (deleted_at is null)\nSharedOrganization.objects.branches() # QuerySet (is_branch=True)\nSharedOrganization.objects.main_organizations() # QuerySet (is_branch=False)\nSharedOrganization.objects.by_cnpj('12.345.678/0001-90') # Org | None\n```\n\n#### UserManager\n\n```python\nfrom shared_auth.models import User\n\nUser.objects.get_or_fail(456) # User | raise UserNotFoundError\nUser.objects.active() # QuerySet (is_active=True, deleted_at is null)\nUser.objects.by_email('user@example.com') # User | None\n```\n\n#### SharedMemberManager\n\n```python\nfrom shared_auth.models import SharedMember\n\nSharedMember.objects.for_user(456) # QuerySet\nSharedMember.objects.for_organization(123) # QuerySet\n```\n\n#### BaseAuthManager (para seus models)\n\n```python\n# Quando usa OrganizationMixin\nModel.objects.for_organization(123) # QuerySet\nModel.objects.for_organizations([123, 456]) # QuerySet\nModel.objects.with_organization_data() # List com prefetch\n\n# Quando usa UserMixin\nModel.objects.for_user(456) # QuerySet\nModel.objects.for_users([456, 789]) # QuerySet\nModel.objects.with_user_data() # List com prefetch\n\n# Quando usa OrganizationUserMixin\nModel.objects.with_auth_data() # List com prefetch de org e user\nModel.objects.create_with_validation(\n organization_id=123,\n user_id=456,\n **kwargs\n) # Valida membership antes de criar\n```\n\n### Exceptions\n\n```python\nfrom shared_auth.exceptions import (\n SharedAuthError,\n OrganizationNotFoundError,\n UserNotFoundError,\n DatabaseConnectionError,\n)\n\ntry:\n org = SharedOrganization.objects.get_or_fail(999)\nexcept OrganizationNotFoundError as e:\n print(e) # \"Organiza\u00e7\u00e3o com ID 999 n\u00e3o encontrada\"\n```\n\n---\n\n## \ud83c\udfaf Casos de Uso Reais\n\n### Sistema de Pedidos Multi-Tenant\n\n```python\n# models.py\nfrom shared_auth.mixins import OrganizationUserMixin, TimestampedMixin\n\nclass Pedido(OrganizationUserMixin, TimestampedMixin):\n numero = models.CharField(max_length=20, unique=True)\n valor_total = models.DecimalField(max_digits=10, decimal_places=2)\n status = models.CharField(max_length=20)\n \n objects = BaseAuthManager()\n\nclass ItemPedido(models.Model):\n pedido = models.ForeignKey(Pedido, related_name='itens')\n produto = models.CharField(max_length=200)\n quantidade = models.IntegerField()\n valor_unitario = models.DecimalField(max_digits=10, decimal_places=2)\n\n# serializers.py\nfrom shared_auth.serializers import OrganizationUserSerializerMixin\n\nclass ItemPedidoSerializer(serializers.ModelSerializer):\n class Meta:\n model = ItemPedido\n fields = ['id', 'produto', 'quantidade', 'valor_unitario']\n\nclass PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):\n itens = ItemPedidoSerializer(many=True, read_only=True)\n \n class Meta:\n model = Pedido\n fields = [\n 'id', 'numero', 'valor_total', 'status',\n 'organization', 'user', 'itens', 'created_at'\n ]\n\n# views.py\nfrom shared_auth.mixins import LoggedOrganizationMixin\nfrom shared_auth.permissions import HasActiveOrganization\n\nclass PedidoViewSet(LoggedOrganizationMixin, viewsets.ModelViewSet):\n serializer_class = PedidoSerializer\n permission_classes = [HasActiveOrganization]\n \n def get_queryset(self):\n # J\u00e1 filtra por organization_id automaticamente\n return super().get_queryset().with_auth_data()\n```\n\n### Sistema de Tarefas com Respons\u00e1veis\n\n```python\n# models.py\nclass Tarefa(OrganizationUserMixin, TimestampedMixin):\n \"\"\"\n user_id = criador\n responsavel_id = quem vai executar\n \"\"\"\n titulo = models.CharField(max_length=200)\n descricao = models.TextField()\n responsavel_id = models.IntegerField()\n status = models.CharField(max_length=20, default='pending')\n \n objects = BaseAuthManager()\n \n @property\n def responsavel(self):\n \"\"\"Acessa usu\u00e1rio respons\u00e1vel\"\"\"\n if not hasattr(self, '_cached_responsavel'):\n from shared_auth.models import User\n self._cached_responsavel = User.objects.get_or_fail(self.responsavel_id)\n return self._cached_responsavel\n\n# serializers.py\nclass TarefaSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):\n responsavel = serializers.SerializerMethodField()\n \n def get_responsavel(self, obj):\n try:\n resp = obj.responsavel\n return {\n 'id': resp.pk,\n 'email': resp.email,\n 'full_name': resp.get_full_name(),\n }\n except:\n return None\n \n class Meta:\n model = Tarefa\n fields = [\n 'id', 'titulo', 'descricao', 'status',\n 'organization', # Organiza\u00e7\u00e3o dona\n 'user', # Criador\n 'responsavel', # Executor\n 'created_at'\n ]\n```\n\n---\n\n## \ud83d\udd27 Troubleshooting\n\n### Problema: Queries lentas (N+1)\n\n**Solu\u00e7\u00e3o:** Use os managers com prefetch\n\n```python\n# \u274c Ruim - Causa N+1\npedidos = Pedido.objects.all()\nfor pedido in pedidos:\n print(pedido.organization.name) # Query por item!\n\n# \u2705 Bom - 3 queries total\npedidos = Pedido.objects.with_auth_data()\nfor pedido in pedidos:\n print(pedido.organization.name) # Sem query adicional\n```\n\n### Problema: OrganizationNotFoundError\n\n**Causa:** ID de organiza\u00e7\u00e3o inv\u00e1lido ou deletada\n\n**Solu\u00e7\u00e3o:**\n```python\n# Usar try/except\ntry:\n org = SharedOrganization.objects.get_or_fail(org_id)\nexcept OrganizationNotFoundError:\n # Tratar erro\n return Response({'error': 'Organiza\u00e7\u00e3o n\u00e3o encontrada'}, status=404)\n\n# Ou usar filter\norg = SharedOrganization.objects.filter(pk=org_id).first()\nif not org:\n # Tratar\n```\n\n### Problema: Erro de conex\u00e3o com auth_db\n\n**Solu\u00e7\u00e3o:** Verificar configura\u00e7\u00e3o do database router e permiss\u00f5es\n\n```python\n# Testar conex\u00e3o\nfrom django.db import connections\n\nconnection = connections['auth_db']\nwith connection.cursor() as cursor:\n cursor.execute(\"SELECT 1\")\n print(\"\u2713 Conex\u00e3o OK\")\n```\n\n---\n\n## \ud83d\udcdd Changelog\n\n### v0.2.25\n- \u2728 Adicionado suporte a imagens (avatar, logo)\n- \u2728 StorageBackend para arquivos compartilhados\n- \ud83d\udc1b Corre\u00e7\u00f5es nos serializers\n- \ud83d\udcda Documenta\u00e7\u00e3o melhorada\n\n### v0.2.0\n- \u2728 Middlewares: SharedAuthMiddleware, OrganizationMiddleware\n- \u2728 Permissions customizadas\n- \u2728 Managers otimizados com prefetch\n- \u2728 Serializer mixins com dados aninhados\n\n### v0.1.0\n- \ud83c\udf89 Vers\u00e3o inicial\n- \u2728 Models compartilhados\n- \u2728 Mixins b\u00e1sicos\n- \u2728 Autentica\u00e7\u00e3o via token\n\n---\n\n## \ud83d\udcc4 Licen\u00e7a\n\nMIT License - veja [LICENSE](LICENSE) para detalhes.\n\n---\n\n## \ud83e\udd1d Contribuindo\n\nContribui\u00e7\u00f5es s\u00e3o bem-vindas! Por favor, abra uma issue ou pull request.\n\n---\n\n## \ud83d\udce7 Suporte\n\nPara suporte, abra uma issue no GitHub ou entre em contato com a equipe Maquinaweb.\n\n---\n\n**Desenvolvido com \u2764\ufe0f por Maquinaweb**\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Models read-only para autentica\u00e7\u00e3o compartilhada entre projetos Django.",
"version": "0.2.38",
"project_urls": {
"Homepage": "https://github.com/maquinaweb/maquinaweb-shared-auth",
"Issues": "https://github.com/maquinaweb/maquinaweb-shared-auth/issues",
"Repository": "https://github.com/maquinaweb/maquinaweb-shared-auth"
},
"split_keywords": [
"django",
" auth",
" models",
" shared"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "f41f16aa87542e20c5d42ead718e99e4d42a94820dffcb2fa3492804566eabe7",
"md5": "b9a67293080ca2e6b7f394676535a283",
"sha256": "c094803c46525af457f7965c0179bc6f59ef07d93d416e50aae23cb41036391f"
},
"downloads": -1,
"filename": "maquinaweb_shared_auth-0.2.38-py3-none-any.whl",
"has_sig": false,
"md5_digest": "b9a67293080ca2e6b7f394676535a283",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 26677,
"upload_time": "2025-10-23T13:01:05",
"upload_time_iso_8601": "2025-10-23T13:01:05.138495Z",
"url": "https://files.pythonhosted.org/packages/f4/1f/16aa87542e20c5d42ead718e99e4d42a94820dffcb2fa3492804566eabe7/maquinaweb_shared_auth-0.2.38-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "d1f2d14e3265a190e87ecb8fad183100c5de8f477a88054cec1c27ef2c573db1",
"md5": "4cbd2817327115e274027d71bd0cea57",
"sha256": "af3117994f25b28dec0a6d923717dd570ed0f456d6b4fd72e91f259d3342c8f7"
},
"downloads": -1,
"filename": "maquinaweb_shared_auth-0.2.38.tar.gz",
"has_sig": false,
"md5_digest": "4cbd2817327115e274027d71bd0cea57",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 21859,
"upload_time": "2025-10-23T13:01:06",
"upload_time_iso_8601": "2025-10-23T13:01:06.339311Z",
"url": "https://files.pythonhosted.org/packages/d1/f2/d14e3265a190e87ecb8fad183100c5de8f477a88054cec1c27ef2c573db1/maquinaweb_shared_auth-0.2.38.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-23 13:01:06",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "maquinaweb",
"github_project": "maquinaweb-shared-auth",
"github_not_found": true,
"lcname": "maquinaweb-shared-auth"
}