4.3 KiB
Data Model: 005-authentication
Fase 1 — Modelo de Dados Data: 2026-04-13
Entidades
ClientUser
Tabela: client_users
Módulo: backend/app/models/user.py
Classe: ClientUser
| Campo | Tipo SQLAlchemy | Tipo Python | Nullable | Constraints | Notas |
|---|---|---|---|---|---|
id |
UUID(as_uuid=True) |
uuid.UUID |
NOT NULL | PK, default=uuid.uuid4 |
Gerado no Python antes do flush |
name |
String(150) |
str |
NOT NULL | — | Nome completo |
email |
String(254) |
str |
NOT NULL | UNIQUE, INDEX | Normalizado para lowercase via schema Pydantic |
password_hash |
String(100) |
str |
NOT NULL | — | Hash bcrypt (60 chars); String(100) com margem de segurança |
role |
String(20) |
str |
NOT NULL | default='client' |
Único papel ativo nesta versão |
created_at |
DateTime |
datetime |
NOT NULL | server_default=func.now() |
Timezone-naive UTC |
Indexes: email (único + índice explícito — frequente em queries de login)
Relacionamentos: nenhum nesta versão
DDL esperado (gerenciado via Alembic, não escrever manualmente):
CREATE TABLE client_users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(150) NOT NULL,
email VARCHAR(254) NOT NULL UNIQUE,
password_hash VARCHAR(100) NOT NULL,
role VARCHAR(20) NOT NULL DEFAULT 'client',
created_at TIMESTAMP NOT NULL DEFAULT now()
);
CREATE INDEX ix_client_users_email ON client_users (email);
Schemas Pydantic
Módulo: backend/app/schemas/auth.py
RegisterIn
Valida o corpo da requisição POST /api/v1/auth/register.
class RegisterIn(BaseModel):
name: str = Field(min_length=1, max_length=150)
email: EmailStr
password: str = Field(min_length=8)
@field_validator("email")
@classmethod
def normalize_email(cls, v: str) -> str:
return v.lower().strip()
LoginIn
Valida o corpo da requisição POST /api/v1/auth/login.
class LoginIn(BaseModel):
email: EmailStr
password: str
@field_validator("email")
@classmethod
def normalize_email(cls, v: str) -> str:
return v.lower().strip()
UserOut
Resposta segura com dados do usuário (sem password_hash).
class UserOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: UUID
name: str
email: str
role: str
created_at: datetime
AuthTokenOut
Resposta de register e login bem-sucedidos.
class AuthTokenOut(BaseModel):
access_token: str
user: UserOut
Regras de Validação
| Regra | Campo | Resposta em falha |
|---|---|---|
min_length=8 |
password em RegisterIn |
HTTP 422 |
min_length=1, max_length=150 |
name em RegisterIn |
HTTP 422 |
EmailStr |
email em RegisterIn / LoginIn |
HTTP 422 |
| Email único | email em ClientUser |
HTTP 409 (capturado na rota via IntegrityError) |
| Normalize lowercase | email (ambos os schemas) |
Aplicado silenciosamente via field_validator |
Transições de Estado
O ClientUser não possui máquina de estados nesta versão:
- Created: via
POST /api/v1/auth/register - Read: via
POST /api/v1/auth/login+GET /api/v1/auth/me - Update / Delete: fora do escopo desta feature
Tipos Frontend
Módulo: frontend/src/types/auth.ts
export interface User {
id: string;
name: string;
email: string;
role: string;
}
export interface AuthState {
user: User | null;
token: string | null;
isAuthenticated: boolean;
}
Contexto de Autenticação Frontend
Módulo: frontend/src/contexts/AuthContext.tsx
interface AuthContextType {
user: User | null;
token: string | null;
isAuthenticated: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
register: (name: string, email: string, password: string) => Promise<void>;
}
Inicialização: ao montar AuthProvider, carregar imob_token do localStorage e chamar GET /api/v1/auth/me para hidratar user. Se o token estiver expirado/inválido, limpar o localStorage e definir estado unauthenticated.