first(); if ($cidadao && empty($cidadao->paciente->senha)) { throw new \InvalidArgumentException('Cidadão não possui senha cadastrada.', Response::HTTP_UNPROCESSABLE_ENTITY); } $telefonePrincipal = $cidadao ? $cidadao->telefone_principal : null; $paciente = $cidadao ? $cidadao->paciente : null; if (is_null($cidadao) || is_null($paciente) || is_null($telefonePrincipal)) { throw new \InvalidArgumentException('Cidadão não encontrado ou sem telefone principal cadastrado.', Response::HTTP_UNPROCESSABLE_ENTITY); } $codigo = (string) random_int(100000, 999999); $agora = Carbon::now(); $registro = ConectaCidadaoSmsCode::updateOrCreate( [ 'cidadao_id' => $cidadao->id, 'telefone' => $telefonePrincipal->numero, 'usado' => false, ], [ 'codigo' => $codigo, 'expires_at' => $agora->copy()->addMinutes(PrimeiroAcesso::SMS_CODE_TTL_MINUTOS), 'last_sent_at' => $agora, 'tentativas_envio' => 1, 'tentativas_validacao' => 0, ] ); $mensagem = "Seu código de recuperação de senha ConectaSus Cidadão é: {$codigo}"; if (app()->environment('production')) { (new SmsService())->send($registro->telefone, $mensagem); } $retorno = [ 'token' => $registro->id, 'telefone' => $this->mascararTelefone($telefonePrincipal->numero), ]; if (!app()->environment('production')) { $retorno['codigo'] = $codigo; } return $retorno; } public function reenviarCodigo(int $token): void { $registro = ConectaCidadaoSmsCode::findOrFail($token); if ($registro->usado || $registro->expires_at->isPast()) { throw new \InvalidArgumentException('Código expirado. Inicie novamente o processo.', Response::HTTP_UNPROCESSABLE_ENTITY); } $agora = Carbon::now(); if (!is_null($registro->last_sent_at) && $agora->diffInSeconds($registro->last_sent_at) < PrimeiroAcesso::SMS_REENVIO_INTERVALO_SEGUNDOS) { throw new \InvalidArgumentException('Aguarde antes de solicitar um novo código.', Response::HTTP_TOO_MANY_REQUESTS); } if ($registro->tentativas_envio >= PrimeiroAcesso::SMS_REENVIO_LIMITE) { throw new \InvalidArgumentException('Limite de reenvios atingido.', Response::HTTP_TOO_MANY_REQUESTS); } $registro->tentativas_envio++; $registro->last_sent_at = $agora; $registro->save(); $mensagem = "Seu código de recuperação de senha ConectaSus Cidadão é: {$registro->codigo}"; if (app()->environment('production')) { (new SmsService())->send($registro->telefone, $mensagem); } } public function validarCodigo(array $dados): void { $registro = ConectaCidadaoSmsCode::findOrFail($dados['token']); if ($registro->usado || $registro->expires_at->isPast()) { throw new \InvalidArgumentException('Código expirado. Inicie novamente o processo.', Response::HTTP_UNPROCESSABLE_ENTITY); } if ($registro->codigo !== $dados['codigo']) { $registro->tentativas_validacao++; $registro->save(); throw new \InvalidArgumentException('Código inválido.', Response::HTTP_UNPROCESSABLE_ENTITY); } $registro->usado = true; $registro->save(); } public function redefinirSenha(array $dados): void { $registro = ConectaCidadaoSmsCode::findOrFail($dados['token']); if (!$registro->usado || $registro->expires_at->isPast()) { throw new \InvalidArgumentException('Fluxo inválido. Valide o código de segurança novamente.', Response::HTTP_UNPROCESSABLE_ENTITY); } $cidadao = Cidadao::findOrFail($registro->cidadao_id); if (is_null($cidadao->paciente)) { throw new \InvalidArgumentException('Cidadão não encontrado.', Response::HTTP_UNPROCESSABLE_ENTITY); } $cidadao->paciente->senha = Hash::make($dados['senha']); $cidadao->paciente->save(); } private function mascararTelefone(string $telefone): string { $apenasNumeros = preg_replace('/[^0-9]/', '', $telefone); $tamanho = strlen($apenasNumeros); if ($tamanho < 8) { return $telefone; } $fimVisivel = substr($apenasNumeros, -4); $ddd = substr($apenasNumeros, 0, 2); return "({$ddd}) *****-{$fimVisivel}"; } }