RegisterCompanyController.php 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. <?php
  2. namespace Controllers;
  3. use Libs\Logger;
  4. use Libs\Payload;
  5. use Libs\Validator;
  6. use Models\CompanyModel;
  7. use Psr\Http\Message\ServerRequestInterface;
  8. /**
  9. * Provisiona uma nova empresa (tenant) junto com o seu primeiro usuário admin.
  10. *
  11. * Criar uma empresa nova é uma operação de PLATAFORMA: não pode herdar o
  12. * company_id de um usuário autenticado (ele estaria criando OUTRA empresa).
  13. * Por isso o acesso é protegido por um segredo de provisionamento
  14. * (PROVISION_SECRET), enviado no header X-Provision-Secret.
  15. *
  16. * Corpo esperado:
  17. * {
  18. * "company": { "name": "...", "cnpj": "14 dígitos", "logo": "(opcional)" },
  19. * "admin": { "name": "...", "email": "...", "phone": "...", "password": "min 8" }
  20. * }
  21. *
  22. * A resposta inclui o company_hmac_secret gerado — é o segredo a ser entregue
  23. * ao CRM dessa empresa (ver crm.md).
  24. */
  25. class RegisterCompanyController
  26. {
  27. private CompanyModel $companyModel;
  28. public function __construct()
  29. {
  30. $this->companyModel = new CompanyModel();
  31. }
  32. public function __invoke(ServerRequestInterface $request): \React\Http\Message\Response
  33. {
  34. if (!$this->isAuthorized($request)) {
  35. return Payload::fail('Unauthorized', [], 'E_VALIDATE', 401);
  36. }
  37. $body = json_decode((string) $request->getBody(), true);
  38. if (!is_array($body)) {
  39. return Payload::fail('Invalid JSON payload', [], 'E_VALIDATE', 400);
  40. }
  41. $company = is_array($body['company'] ?? null) ? $body['company'] : [];
  42. $admin = is_array($body['admin'] ?? null) ? $body['admin'] : [];
  43. $error = $this->validate($company, $admin);
  44. if ($error !== null) {
  45. return Payload::fail($error, [], 'E_VALIDATE', 400);
  46. }
  47. try {
  48. $result = $this->companyModel->createCompanyWithAdmin($company, $admin);
  49. } catch (\Throwable $e) {
  50. Logger::error('Failed to register company', ['error' => $e->getMessage()]);
  51. return Payload::fail('Failed to register company', [], 'E_GENERIC', 500);
  52. }
  53. switch ($result['status']) {
  54. case 'cnpj_exists':
  55. return Payload::fail('CNPJ already registered', [], 'E_VALIDATE', 400);
  56. case 'email_exists':
  57. return Payload::fail('Email already exists', [], 'E_VALIDATE', 400);
  58. case 'created':
  59. return Payload::ok(
  60. ['company' => $result['company'], 'user' => $result['user']],
  61. 'S_CREATED',
  62. 'Company created.'
  63. );
  64. default:
  65. return Payload::fail('Failed to register company', [], 'E_GENERIC', 500);
  66. }
  67. }
  68. /**
  69. * Valida os campos da empresa e do admin. Retorna a mensagem de erro ou null.
  70. */
  71. private function validate(array $company, array $admin): ?string
  72. {
  73. $cnpj = preg_replace('/\D/', '', (string) ($company['cnpj'] ?? ''));
  74. $validator = (new Validator([
  75. 'company_name' => $company['name'] ?? null,
  76. 'cnpj' => $cnpj,
  77. 'name' => $admin['name'] ?? null,
  78. 'email' => $admin['email'] ?? null,
  79. 'phone' => $admin['phone'] ?? null,
  80. 'password' => $admin['password'] ?? null,
  81. ]))
  82. ->required('company_name')->maxLength('company_name', 100)
  83. ->required('cnpj')
  84. ->required('name')->maxLength('name', 100)
  85. ->required('email')->email('email')->maxLength('email', 100)
  86. ->required('phone')->phone('phone')->maxLength('phone', 20)
  87. ->required('password')->minLength('password', 8)->maxLength('password', 255);
  88. if ($validator->fails()) {
  89. return $validator->firstError();
  90. }
  91. if (strlen($cnpj) !== 14) {
  92. return 'CNPJ must have 14 digits';
  93. }
  94. return null;
  95. }
  96. private function isAuthorized(ServerRequestInterface $request): bool
  97. {
  98. $expected = (string) ($_ENV['PROVISION_SECRET'] ?? '');
  99. if ($expected === '') {
  100. Logger::error('PROVISION_SECRET is not configured; rejecting company provisioning');
  101. return false;
  102. }
  103. return hash_equals($expected, $request->getHeaderLine('X-Provision-Secret'));
  104. }
  105. }