Referência v3
Voltar à plataforma

Usuário Customizado

Como substituir o módulo Usuario padrão pelo seu próprio, com campos específicos da sua aplicação, mantendo o módulo Auth funcionando.

Como funciona

O módulo Auth não depende da entidade Usuario — ele depende de duas interfaces do kernel:

InterfaceResponsabilidade
AuthenticatableInterfaceContrato que sua entidade de usuário deve implementar
UserRepositoryInterfaceContrato que seu repositório deve implementar

Basta implementar essas duas interfaces e registrar seu repositório no container — o Auth passa a usar sua tabela automaticamente. Login, refresh token, recuperação de senha e verificação de e-mail funcionam sem nenhuma alteração.

O módulo Usuario original continua existindo

Você não precisa removê-lo. O binding de UserRepositoryInterface no container é sobrescrito pelo seu provider — o Auth usa o último binding registrado.

Estrutura do módulo

bash
src/Modules/MeuApp/
├── Entities/
│   └── MeuUsuario.php           ← sua entidade (implementa AuthenticatableInterface)
├── Repositories/
│   └── MeuUsuarioRepository.php ← seu repositório (implementa UserRepositoryInterface)
├── Database/migrations/
│   └── 001_criar_tabela_usuarios.php
├── Providers/
│   └── MeuAppServiceProvider.php ← registra no container
└── Routes/
    └── web.php

1. Entidade

Implemente AuthenticatableInterface e adicione seus campos específicos:

php
namespace Src\Modules\MeuApp\Entities;

use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidInterface;
use Src\Kernel\Contracts\AuthenticatableInterface;

final class MeuUsuario implements AuthenticatableInterface
{
    private function __construct(
        private readonly UuidInterface $uuid,
        private string                 $email,
        private string                 $username,
        private string                 $senhaHash,
        private string                 $role,
        private bool                   $ativo,
        private bool                   $emailVerificado,
        private ?int                   $senhaAlteradaEm,
        private ?string                $tokenRecuperacaoSenha,
        private ?string                $tokenVerificacaoEmail,
        // ── Seus campos específicos ──────────────────
        private string                 $nomeCompleto,
        private string                 $cpf,
        private string                 $plano,
    ) {}

    // ── AuthenticatableInterface (obrigatório) ────────
    public function getAuthId(): string        { return $this->uuid->toString(); }
    public function getAuthEmail(): string     { return $this->email; }
    public function getAuthUsername(): ?string { return $this->username; }
    public function getAuthRole(): string      { return $this->role; }
    public function isAtivo(): bool            { return $this->ativo; }
    public function isEmailVerificado(): bool  { return $this->emailVerificado; }
    public function getSenhaAlteradaEm(): ?int { return $this->senhaAlteradaEm; }

    public function verificarSenha(string $senhaPlana): bool
    {
        return password_verify($senhaPlana, $this->senhaHash);
    }

    // ── Seus getters específicos ──────────────────────
    public function getCpf(): string   { return $this->cpf; }
    public function getPlano(): string { return $this->plano; }
}

2. Repositório

Implemente UserRepositoryInterface apontando para sua tabela:

php
namespace Src\Modules\MeuApp\Repositories;

use PDO;
use Src\Kernel\Contracts\UserRepositoryInterface;
use Src\Modules\MeuApp\Entities\MeuUsuario;

class MeuUsuarioRepository implements UserRepositoryInterface
{
    private const TABELA = 'meu_app_usuarios';

    public function __construct(private readonly PDO $pdo) {}

    public function buscarPorEmail(string $email): ?MeuUsuario
    {
        $stmt = $this->pdo->prepare(
            'SELECT * FROM ' . self::TABELA . ' WHERE email = ? LIMIT 1'
        );
        $stmt->execute([mb_strtolower(trim($email))]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ? $this->hidratar($row) : null;
    }

    public function buscarPorUsername(string $username): ?MeuUsuario
    {
        $stmt = $this->pdo->prepare(
            'SELECT * FROM ' . self::TABELA . ' WHERE username = ? LIMIT 1'
        );
        $stmt->execute([mb_strtolower(trim($username))]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        return $row ? $this->hidratar($row) : null;
    }

    // ... demais métodos da interface (buscarPorUuid, salvarToken, etc.)

    private function hidratar(array $row): MeuUsuario
    {
        return MeuUsuario::reconstituir(/* ... */);
    }
}

3. Service Provider

Registre seu repositório no container. O Auth vai usá-lo automaticamente:

php
namespace Src\Modules\MeuApp\Providers;

use Src\Kernel\Contracts\ContainerInterface;
use Src\Kernel\Contracts\ModuleProviderInterface;
use Src\Kernel\Contracts\RouterInterface;
use Src\Kernel\Contracts\UserRepositoryInterface;
use Src\Modules\MeuApp\Repositories\MeuUsuarioRepository;

class MeuAppServiceProvider implements ModuleProviderInterface
{
    public function getName(): string { return 'MeuApp'; }

    public function boot(ContainerInterface $container): void
    {
        // Registra seu repositório — o Auth usa este binding automaticamente
        $container->bind(
            UserRepositoryInterface::class,
            fn() => new MeuUsuarioRepository($container->make(\PDO::class)),
            true // singleton
        );
    }

    public function registerRoutes(RouterInterface $router): void
    {
        $routesFile = __DIR__ . '/../Routes/web.php';
        if (is_file($routesFile)) {
            require $routesFile;
        }
    }

    public function describe(): array
    {
        return ['description' => 'Módulo principal com usuários customizados.', 'version' => '1.0.0', 'routes' => []];
    }
}

4. Migration

Crie a tabela com os campos do kernel (obrigatórios para o Auth) mais os seus campos específicos:

php
// src/Modules/MeuApp/Database/migrations/001_criar_tabela_usuarios.php
return [
    'up' => function (PDO $pdo): void {
        $driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
        if ($driver === 'pgsql') {
            $pdo->exec("
                CREATE TABLE IF NOT EXISTS meu_app_usuarios (
                    uuid                    UUID PRIMARY KEY DEFAULT gen_random_uuid(),
                    email                   VARCHAR(254) NOT NULL UNIQUE,
                    username                VARCHAR(64) NOT NULL UNIQUE,
                    senha_hash              VARCHAR(255) NOT NULL,
                    role                    VARCHAR(50) NOT NULL DEFAULT 'usuario',
                    ativo                   BOOLEAN NOT NULL DEFAULT TRUE,
                    email_verificado        BOOLEAN NOT NULL DEFAULT FALSE,
                    senha_alterada_em       INTEGER,
                    token_recuperacao_senha VARCHAR(255),
                    token_verificacao_email VARCHAR(255),
                    -- seus campos específicos:
                    nome_completo           VARCHAR(255) NOT NULL,
                    cpf                     VARCHAR(14),
                    plano                   VARCHAR(50) NOT NULL DEFAULT 'free',
                    criado_em               TIMESTAMPTZ NOT NULL DEFAULT NOW()
                )
            ");
        } else {
            $pdo->exec("
                CREATE TABLE IF NOT EXISTS meu_app_usuarios (
                    uuid                    CHAR(36) PRIMARY KEY,
                    email                   VARCHAR(254) NOT NULL UNIQUE,
                    username                VARCHAR(64) NOT NULL UNIQUE,
                    senha_hash              VARCHAR(255) NOT NULL,
                    role                    VARCHAR(50) NOT NULL DEFAULT 'usuario',
                    ativo                   TINYINT(1) NOT NULL DEFAULT 1,
                    email_verificado        TINYINT(1) NOT NULL DEFAULT 0,
                    senha_alterada_em       INT UNSIGNED,
                    token_recuperacao_senha VARCHAR(255),
                    token_verificacao_email VARCHAR(255),
                    -- seus campos específicos:
                    nome_completo           VARCHAR(255) NOT NULL,
                    cpf                     VARCHAR(14),
                    plano                   VARCHAR(50) NOT NULL DEFAULT 'free',
                    criado_em               DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
            ");
        }
    },
    'down' => function (PDO $pdo): void {
        $pdo->exec("DROP TABLE IF EXISTS meu_app_usuarios");
    },
];

5. Rodar a migration

bash
php vupi migrate
Pronto

Após registrar o provider, o endpoint POST /api/login já autentica contra sua tabela. Recuperação de senha, refresh token e verificação de e-mail funcionam automaticamente.

Campos obrigatórios na tabela

O módulo Auth precisa destes campos para funcionar. Os demais são livres:

ColunaTipoUsado pelo Auth para
uuidUUID / CHAR(36)Identificar o usuário no JWT (sub)
emailVARCHAR(254)Login por e-mail
usernameVARCHAR(64)Login por username
senha_hashVARCHAR(255)Verificar senha (password_verify)
roleVARCHAR(50)Controle de acesso (RBAC)
ativoBOOLEANBloquear contas desativadas
email_verificadoBOOLEANPolítica de verificação de e-mail
senha_alterada_emINTEGER (unix)Invalidar tokens antigos após troca de senha
token_recuperacao_senhaVARCHAR(255)Fluxo de recuperação de senha
token_verificacao_emailVARCHAR(255)Fluxo de verificação de e-mail