# Auth System - Framework OfficeGest

> **Nível**: Básico a Avançado  
> **Pré-requisitos**: Conhecimento básico de PHP e conceitos de autenticação  
> **Tempo de leitura**: ~12 minutos

---

## 📋 Índice

1. [Introdução](#introdução)
2. [Quick Start](#quick-start)
3. [Arquitetura e Estrutura](#arquitetura-e-estrutura)
4. [AuthManager](#authmanager)
5. [Authorization (Políticas)](#authorization-políticas)
6. [Access Response](#access-response)
7. [Sistema de Impersonation](#sistema-de-impersonation)
8. [Exemplos Práticos](#exemplos-práticos)
9. [Integração com Sistema Legacy](#integração-com-sistema-legacy)
10. [Boas Práticas](#boas-práticas)
11. [Troubleshooting](#troubleshooting)
12. [Referência de API](#referência-de-api)

---

## Introdução

### O que é o Auth System?

O Auth System do OfficeGest é o módulo responsável por:
- **Autenticação**: Verificar a identidade do utilizador
- **Autorização**: Verificar permissões para ações específicas
- **Gestão de Estado**: Manter informações do utilizador durante a sessão

### Conceitos Chave

| Conceito | Descrição |
|----------|-----------|
| **Autenticação** | "Quem é você?" - Verificar identidade |
| **Autorização** | "O que pode fazer?" - Verificar permissões |
| **Permissão** | Direito específico (ex: `invoices:create`) |
| **Policy** | Classe que define regras de autorização |
| **Impersonation** | Admin atuar como outro utilizador |

---

## Quick Start

### 🚀 Para Começar em 2 Minutos

```php
// 1. OBTER o AuthManager
$auth = app('auth');

// 2. VERIFICAR se utilizador está logado
if ($auth->check()) {
    $user = $auth->user();
    echo "Olá, " . $user->getUsername();
}

// 3. VERIFICAR uma permissão
if ($auth->can('invoices:create')) {
    // Utilizador pode criar faturas
}

// 4. VERIFICAR múltiplas permissões
if ($auth->canAny(['sales:view', 'sales:edit'])) {
    // Tem pelo menos uma das permissões
}

// 5. OBTER ID do utilizador
$userId = $auth->id();
```

### Padrão de Permissões

O formato de permissões no OfficeGest segue o padrão `módulo:ação`:

```
entidades:clientes     → Ver clientes
stocks:artigos        → Ver artigos
vendas:facturas       → Ver faturas
crm:edit              → Editar no CRM
invoices:create       → Criar faturas
invoices:admin        → Administração de faturas
```

---

## Arquitetura e Estrutura

### Estrutura de Ficheiros

```
Modules/Common/Auth/
├── AuthManager.php          # Gestor principal de autenticação
├── Authorization.php        # Classe base para políticas
└── Access/
    └── Response.php         # Respostas de controlo de acesso
```

### Diagrama de Componentes

```mermaid
graph TB
    A[Código da Aplicação] -->|app'auth'| B[AuthManager]
    B --> C[User Legacy]
    B --> D[Container DI]
    
    E[Policy Classes] --> F[Authorization]
    F -->|authorize| G{Permitido?}
    G -->|Sim| H[Continua]
    G -->|Não| I[AuthorizationException]
    
    J[Response] -->|allow/deny| K[Decisão de Acesso]
```

### Registo no Container DI

O AuthManager é registado como singleton no `GlobalServiceProvider.php:170`:

```php
$this->app->singleton('auth', function ($app) {
    return new AuthManager($app->get('user'));
});
```

---

## AuthManager

### Localização

`Modules/Common/Auth/AuthManager.php`

### O que faz?

O AuthManager é o **ponto central** para toda a lógica de autenticação. Ele:
- Gere o estado do utilizador autenticado
- Fornece métodos para verificar permissões
- Mantém sincronização com o sistema legacy
- Suporta impersonation de utilizadores

### Métodos Principais

#### Verificação de Estado

```php
$auth = app('auth');

// Verifica se está autenticado (ou em impersonation)
$auth->check(): bool

// Obtém o utilizador atual
$auth->user(): ?User

// Obtém o ID do utilizador
$auth->id(): ?int

// Verifica se está ativo
$auth->isActive(): bool
```

#### Verificação de Permissões

```php
// Verifica UMA permissão
$auth->can('stocks:artigos'): bool

// Verifica se tem ALGUMA das permissões
$auth->canAny(['stocks:view', 'stocks:edit']): bool

// Verifica se tem TODAS as permissões
$auth->canAll(['stocks:view', 'stocks:edit', 'stocks:delete']): bool
```

#### Gestão de Utilizador

```php
// Atualiza o utilizador (e sincroniza com legacy)
$auth->setUser(User $user): void

// Obtém dados do utilizador como array
$auth->getUserData(): array
```

#### Impersonation

```php
// Verifica se está em modo impersonation
$auth->isImpersonating(): bool

// Verifica se é admin developer
$auth->isDeveloperAdmin(): bool

// Obtém ID do impersonator original
$auth->impersonatorId(): ?int
```

---

## Authorization (Políticas)

### Localização

`Modules/Common/Auth/Authorization.php`

### O que são Políticas?

Políticas (Policies) são classes que encapsulam regras de autorização específicas para um recurso. Em vez de espalhar `if` statements pelo código, centraliza a lógica.

### Criando uma Política

```php
use Og\Modules\Common\Auth\Authorization;

class InvoicePolicy extends Authorization
{
    /**
     * Pode visualizar a fatura?
     */
    public function view($invoice): bool
    {
        // Qualquer um com permissão básica pode ver
        return $this->user->checkPriv('invoices:view');
    }
    
    /**
     * Pode editar a fatura?
     */
    public function edit($invoice): bool
    {
        // Só pode editar se for o criador OU for admin
        return $invoice->created_by === $this->user->getId()
            || $this->user->checkPriv('invoices:admin');
    }
    
    /**
     * Pode eliminar a fatura?
     */
    public function delete($invoice): bool
    {
        // Apenas admin pode eliminar
        return $this->user->checkPriv('invoices:admin');
    }
}
```

### Usando uma Política

```php
// Criar instância da política
$policy = new InvoicePolicy($user, $app);

// Verificar manualmente
if ($policy->edit($invoice)) {
    // Pode editar
}

// Ou usar authorize() - lança exceção se falhar
try {
    $policy->authorize('edit', $invoice);
    // Pode editar, continua...
} catch (AuthorizationException $e) {
    // Não tem permissão
    return response()->error($e->getMessage(), 403);
}
```

---

## Access Response

### Localização

`Modules/Common/Auth/Access/Response.php`

### O que é?

A classe `Response` encapsula decisões de acesso de forma estruturada, permitindo respostas com mensagens, códigos e estados HTTP personalizados.

### Criando Respostas

```php
use Og\Modules\Common\Auth\Access\Response;

// Permitir acesso
$response = Response::allow();
$response = Response::allow('Acesso concedido', 'ACCESS_OK');

// Negar acesso
$response = Response::deny('Sem permissão para esta ação');
$response = Response::deny('Acesso negado', 'ACCESS_DENIED');

// Negar com código HTTP específico
$response = Response::denyWithStatus(403, 'Acesso proibido');

// Negar como 404 (esconder que o recurso existe)
$response = Response::denyAsNotFound('Recurso não encontrado');
```

### Usando Respostas

```php
class DocumentPolicy extends Authorization
{
    public function access($document): Response
    {
        if (!$this->user->checkPriv('documents:view')) {
            return Response::deny('Sem permissão para ver documentos');
        }
        
        if ($document->is_confidential && !$this->user->checkPriv('documents:confidential')) {
            return Response::denyWithStatus(403, 'Documento confidencial');
        }
        
        if ($document->status === 'deleted') {
            return Response::denyAsNotFound('Documento não encontrado');
        }
        
        return Response::allow();
    }
}

// Uso:
$response = $policy->access($document);

if ($response->denied()) {
    return response()->error($response->message(), $response->status() ?? 403);
}

// Ou usar authorize() que lança exceção automaticamente
$response->authorize(); // Lança AuthorizationException se negado
```

### Métodos da Response

| Método | Descrição |
|--------|-----------|
| `allowed(): bool` | Verifica se é permitido |
| `denied(): bool` | Verifica se é negado |
| `message(): ?string` | Obtém mensagem |
| `code(): mixed` | Obtém código personalizado |
| `status(): ?int` | Obtém código HTTP |
| `authorize(): static` | Lança exceção se negado |
| `withStatus($status): static` | Define código HTTP |
| `asNotFound(): static` | Define status 404 |

---

## Sistema de Impersonation

### O que é Impersonation?

Impersonation permite que um administrador "atue como" outro utilizador para debugging ou suporte, mantendo registo de quem realmente está a fazer as ações.

### Como Funciona

```php
// AuthManager.php:211-215
public function check(): bool
{
    return (isset($this->user) && $this->user->IsLogged())
        || $this->isImpersonating();
}
```

O método `check()` considera válido se:
1. Utilizador está logado normalmente, **OU**
2. Está em modo impersonation

### Verificando Impersonation

```php
$auth = app('auth');

if ($auth->isImpersonating()) {
    // Em modo impersonation
    $originalAdminId = $auth->impersonatorId();
    
    // Log para auditoria
    $this->log("Ação realizada por user {$auth->id()} impersonado por admin {$originalAdminId}");
}
```

### Developer Admin

```php
// Verifica se é um admin developer (via sessão)
if ($auth->isDeveloperAdmin()) {
    // Tem acesso especial de desenvolvimento
}
```

---

## Exemplos Práticos

### 🎯 Exemplo 1: Proteger uma Action

```php
class CreateInvoiceAction
{
    public function execute(array $data): Invoice
    {
        $auth = app('auth');
        
        // Verificar autenticação
        if (!$auth->check()) {
            throw new AuthorizationException('Utilizador não autenticado');
        }
        
        // Verificar permissão
        if (!$auth->can('invoices:create')) {
            throw new AuthorizationException('Sem permissão para criar faturas');
        }
        
        // Criar fatura com ID do utilizador
        return Invoice::create([
            ...$data,
            'created_by' => $auth->id(),
        ]);
    }
}
```

### 🎯 Exemplo 2: Middleware de Autorização

```php
class RequirePermissionMiddleware
{
    public function handle(Request $request, string $permission): Response
    {
        $auth = app('auth');
        
        if (!$auth->check()) {
            return redirect('/login');
        }
        
        if (!$auth->can($permission)) {
            return response()->error('Acesso negado', 403);
        }
        
        return $next($request);
    }
}

// Uso na rota:
Route::get('/invoices', [InvoiceController::class, 'index'])
    ->middleware('permission:invoices:view');
```

### 🎯 Exemplo 3: Verificação Contextual

```php
class OrderPolicy extends Authorization
{
    public function cancel($order): bool
    {
        // Admin pode cancelar qualquer order
        if ($this->user->checkPriv('orders:admin')) {
            return true;
        }
        
        // Utilizador só pode cancelar suas próprias orders
        if ($order->user_id !== $this->user->getId()) {
            return false;
        }
        
        // E apenas se não estiver processada
        return $order->status === 'pending';
    }
}
```

### 🎯 Exemplo 4: Audit Log com Impersonation

```php
class AuditService
{
    public function log(string $action, $resource): void
    {
        $auth = app('auth');
        
        $data = [
            'action' => $action,
            'resource_type' => get_class($resource),
            'resource_id' => $resource->id,
            'user_id' => $auth->id(),
            'timestamp' => now(),
        ];
        
        // Se em impersonation, registar também o admin original
        if ($auth->isImpersonating()) {
            $data['impersonator_id'] = $auth->impersonatorId();
            $data['is_impersonated'] = true;
        }
        
        AuditLog::create($data);
    }
}
```

---

## Integração com Sistema Legacy

### Sincronização Bidirecional

O AuthManager mantém sincronização entre o sistema moderno e as variáveis globais legacy:

```php
// AuthManager.php:184-206
private function updateApplicationUserState(): void
{
    if (!$this->check()) {
        return;
    }
    global $u;
    if (!isset($this->user)) {
        return;
    }
    if (!is_integer($this->user->getPrivgroup())) {
        return;
    }
    $this->user->loadPermissions($this->user->getPrivgroup());
    $u = $this->user; // Sincroniza com $u global

    // Registo no container moderno
    if (function_exists('app')) {
        app()->instance('user', $this->user);
    }

    if ($this->container && method_exists($this->container, 'instance')) {
        $this->container->instance('user', $this->user);
    }
}
```

### Usando Código Legacy com Auth Moderno

```php
// Código legacy usa $u global
global $u;
if ($u->checkPriv('stocks:artigos')) {
    // ...
}

// Código moderno usa AuthManager
$auth = app('auth');
if ($auth->can('stocks:artigos')) {
    // ...
}

// Ambos funcionam e estão sincronizados!
```

---

## Boas Práticas

### ✅ Verificar Permissões Cedo

```php
// BOM - verificar no início
public function execute(): void
{
    if (!app('auth')->can('action:permission')) {
        throw new AuthorizationException();
    }
    
    // ... resto do código
}

// MAU - verificar tarde demais
public function execute(): void
{
    $this->heavyProcess();
    $this->moreWork();
    
    if (!app('auth')->can('action:permission')) { // Já fez trabalho desnecessário!
        throw new AuthorizationException();
    }
}
```

### ✅ Usar Políticas para Lógica Complexa

```php
// BOM - lógica centralizada na Policy
$policy->authorize('edit', $document);

// MAU - lógica espalhada pelo código
if ($user->id === $document->owner_id || $user->checkPriv('admin') || $document->isPublic()) {
    // ...
}
```

### ✅ Logging de Acessos Negados

```php
if (!$auth->can($permission)) {
    Funcs::log('security', "Acesso negado: user={$auth->id()}, permission={$permission}");
    throw new AuthorizationException();
}
```

### ✅ Nunca Confiar em Dados do Cliente

```php
// MAU - confiar no user_id do request
$invoice->user_id = $request->input('user_id');

// BOM - usar o ID autenticado
$invoice->user_id = app('auth')->id();
```

---

## Troubleshooting

### Problema: `app('auth')` retorna null

**Causa:** Bootstrap não completou ou service provider não registado.

```php
// Verificar se está registado
dd(app()->has('auth')); // Deve ser true

// Se false, verificar se GlobalServiceProvider está a carregar
```

### Problema: Permissões não funcionam

**Causa:** Permissões não carregadas para o grupo do utilizador.

```php
// Verificar grupo de privilégios
$user = app('auth')->user();
dd($user->getPrivgroup()); // Deve ser um ID numérico

// Verificar permissões carregadas
dd($user->getPermissions());
```

### Problema: check() retorna false mesmo logado

**Causa:** Sessão não sincronizada ou user não carregado.

```php
$auth = app('auth');

// Debug completo
dd([
    'user_set' => isset($auth->user()),
    'is_logged' => $auth->user()?->IsLogged(),
    'is_impersonating' => $auth->isImpersonating(),
    'session_user' => $_SESSION['user'] ?? null,
]);
```

---

## Referência de API

### AuthManager

**Localização:** `Modules/Common/Auth/AuthManager.php`

```php
class AuthManager
{
    // Estado do utilizador
    public function user(): ?User;
    public function id(): ?int;
    public function check(): bool;                    // Linhas 211-215
    public function isActive(): bool;
    public function setUser(User $user): void;
    public function getUserData(): array;
    
    // Verificação de permissões
    public function can(string $permission): bool;
    public function canAny(array $permissions): bool;
    public function canAll(array $permissions): bool;
    
    // Impersonation (linhas 217-239)
    public function isImpersonating(): bool;
    public function isDeveloperAdmin(): bool;
    public function impersonatorId(): ?int;
}
```

### Authorization

**Localização:** `Modules/Common/Auth/Authorization.php`

```php
abstract class Authorization
{
    public function __construct(
        protected User $user,
        protected Aplication $application
    );
    
    public function authorize(string $method, mixed $params = null): void;
}
```

### Access\Response

**Localização:** `Modules/Common/Auth/Access/Response.php`

```php
class Response implements Arrayable, Stringable
{
    // Factory methods
    public static function allow($message = null, $code = null): static;
    public static function deny($message = null, $code = null): static;
    public static function denyWithStatus($status, $message = null, $code = null): Response;
    public static function denyAsNotFound($message = null, $code = null): Response;
    
    // Verificação
    public function allowed(): bool;
    public function denied(): bool;
    public function authorize(): static;  // Lança AuthorizationException
    
    // Informação
    public function message(): ?string;
    public function code(): mixed;
    public function status(): ?int;
    
    // Modificadores
    public function withStatus($status): static;
    public function asNotFound(): static;
    
    // Conversão
    public function toArray(): array;
    public function __toString(): string;
}
```

---

## Próximos Passos

- 📖 [Guard System](./guard-system.md) - Middleware de autenticação
- 📖 [Container DI](./container-system.md) - Como os serviços são registados
- 📖 [Facades](./facades-system.md) - Padrão Facade no framework

---

*Última atualização: Dezembro 2024*
