fix: login con username
This commit is contained in:
parent
ee9b265582
commit
927c46aa2e
@ -25,16 +25,10 @@ class LoginController extends Controller
|
|||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Iniciar sesión
|
* Iniciar sesión
|
||||||
* Permite login con username O email
|
|
||||||
*/
|
*/
|
||||||
public function login(LoginRequest $request)
|
public function login(LoginRequest $request)
|
||||||
{
|
{
|
||||||
$credential = $request->get('username');
|
$user = User::where('username', $request->get('username'))->first();
|
||||||
|
|
||||||
// Buscar por username o email
|
|
||||||
$user = User::where('username', $credential)
|
|
||||||
->orWhere('email', $credential)
|
|
||||||
->first();
|
|
||||||
|
|
||||||
if (!$user || !$user->validateForPassportPasswordGrant($request->get('password'))) {
|
if (!$user || !$user->validateForPassportPasswordGrant($request->get('password'))) {
|
||||||
return ApiResponse::UNPROCESSABLE_CONTENT->response([
|
return ApiResponse::UNPROCESSABLE_CONTENT->response([
|
||||||
@ -62,27 +56,35 @@ public function logout()
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Contraseña olvidada
|
* Contraseña olvidada
|
||||||
|
* Nota: Sin email, el reset se maneja por token directo
|
||||||
*/
|
*/
|
||||||
public function forgotPassword(ForgotRequest $request)
|
public function forgotPassword(ForgotRequest $request)
|
||||||
{
|
{
|
||||||
$data = $request->validated();
|
$data = $request->validated();
|
||||||
|
|
||||||
$user = User::where('email', $data['email'])->first();
|
$user = User::where('username', $data['username'])->first();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
return ApiResponse::NOT_FOUND->response([
|
||||||
|
'username' => ['Usuario no encontrado']
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$token = $this->generateToken($user);
|
$token = $this->generateToken($user);
|
||||||
|
|
||||||
$user->notify(new ForgotPasswordNotification($token));
|
// Sin email, retornar el token directamente (para uso administrativo)
|
||||||
|
|
||||||
return ApiResponse::OK->response([
|
return ApiResponse::OK->response([
|
||||||
'is_sent' => true
|
'is_generated' => true,
|
||||||
|
'token' => $token,
|
||||||
|
'message' => 'Token generado. Válido por 15 minutos.',
|
||||||
]);
|
]);
|
||||||
} catch (\Throwable $th) {
|
} catch (\Throwable $th) {
|
||||||
Log::channel('mail')->info("Email: {$data['email']}");
|
Log::channel('mail')->info("Username: {$data['username']}");
|
||||||
Log::channel('mail')->error($th->getMessage());
|
Log::channel('mail')->error($th->getMessage());
|
||||||
|
|
||||||
return ApiResponse::INTERNAL_ERROR->response([
|
return ApiResponse::INTERNAL_ERROR->response([
|
||||||
'is_sent' => false,
|
'is_generated' => false,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,7 +30,7 @@ public function authorize(): bool
|
|||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'username' => ['required', 'string'], // Acepta username o email
|
'username' => ['required', 'string'],
|
||||||
'password' => ['required', 'min:8'],
|
'password' => ['required', 'min:8'],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -38,7 +38,7 @@ public function rules(): array
|
|||||||
public function messages(): array
|
public function messages(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'username.required' => 'El usuario o email es requerido',
|
'username.required' => 'El usuario es requerido',
|
||||||
'password.required' => 'La contraseña es requerida',
|
'password.required' => 'La contraseña es requerida',
|
||||||
'password.min' => 'La contraseña debe tener al menos 8 caracteres',
|
'password.min' => 'La contraseña debe tener al menos 8 caracteres',
|
||||||
];
|
];
|
||||||
|
|||||||
@ -28,7 +28,15 @@ public function authorize(): bool
|
|||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'email' => ['required', 'email', 'exists:users,email']
|
'username' => ['required', 'string', 'exists:users,username']
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'username.required' => 'El nombre de usuario es requerido',
|
||||||
|
'username.exists' => 'El usuario no existe',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All rights reserved
|
* @copyright (c) 2025 Notsoweb Software (https://notsoweb.com) - All rights reserved
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -34,27 +33,19 @@ public function rules(): array
|
|||||||
'name' => ['required', 'string', 'max:255'],
|
'name' => ['required', 'string', 'max:255'],
|
||||||
'paternal' => ['required', 'string', 'max:255'],
|
'paternal' => ['required', 'string', 'max:255'],
|
||||||
'maternal' => ['required', 'string', 'max:255'],
|
'maternal' => ['required', 'string', 'max:255'],
|
||||||
'email' => ['required', 'string', 'email', 'max:255', 'unique:users'],
|
'username' => ['required', 'string', 'max:255', 'unique:users', 'alpha_dash'],
|
||||||
'phone' => ['nullable', 'numeric', 'digits:10'],
|
'phone' => ['nullable', 'numeric', 'digits:10'],
|
||||||
'password' => ['required', 'string', 'min:8'],
|
'password' => ['required', 'string', 'min:8'],
|
||||||
'roles' => ['nullable', 'array']
|
'roles' => ['nullable', 'array']
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function messages(): array
|
||||||
* Preparar datos antes de la validación
|
|
||||||
* Genera el username automáticamente
|
|
||||||
*/
|
|
||||||
protected function prepareForValidation(): void
|
|
||||||
{
|
{
|
||||||
if ($this->has('name') && $this->has('paternal')) {
|
return [
|
||||||
$this->merge([
|
'username.required' => 'El nombre de usuario es requerido',
|
||||||
'username' => User::generateUsername(
|
'username.unique' => 'El nombre de usuario ya está en uso',
|
||||||
$this->input('name'),
|
'username.alpha_dash' => 'El usuario solo puede contener letras, números, guiones y guiones bajos',
|
||||||
$this->input('paternal'),
|
];
|
||||||
$this->input('maternal')
|
|
||||||
)
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,9 +34,18 @@ public function rules(): array
|
|||||||
'name' => ['required', 'string', 'max:255'],
|
'name' => ['required', 'string', 'max:255'],
|
||||||
'paternal' => ['required', 'string', 'max:255'],
|
'paternal' => ['required', 'string', 'max:255'],
|
||||||
'maternal' => ['required', 'string', 'max:255'],
|
'maternal' => ['required', 'string', 'max:255'],
|
||||||
'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')->ignore($this->route('user'))],
|
'username' => ['required', 'string', 'max:255', 'alpha_dash', Rule::unique('users')->ignore($this->route('user'))],
|
||||||
'phone' => ['nullable', 'numeric', 'digits:10'],
|
'phone' => ['nullable', 'numeric', 'digits:10'],
|
||||||
'roles' => ['nullable', 'array']
|
'roles' => ['nullable', 'array']
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'username.required' => 'El nombre de usuario es requerido',
|
||||||
|
'username.unique' => 'El nombre de usuario ya está en uso',
|
||||||
|
'username.alpha_dash' => 'El usuario solo puede contener letras, números, guiones y guiones bajos',
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -46,7 +46,6 @@ class User extends Authenticatable
|
|||||||
'paternal',
|
'paternal',
|
||||||
'maternal',
|
'maternal',
|
||||||
'username',
|
'username',
|
||||||
'email',
|
|
||||||
'phone',
|
'phone',
|
||||||
'password',
|
'password',
|
||||||
'profile_photo_path',
|
'profile_photo_path',
|
||||||
@ -67,7 +66,6 @@ class User extends Authenticatable
|
|||||||
protected function casts(): array
|
protected function casts(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'email_verified_at' => 'datetime',
|
|
||||||
'password' => 'hashed',
|
'password' => 'hashed',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -145,90 +143,4 @@ public function responsibleModule()
|
|||||||
{
|
{
|
||||||
return $this->hasOne(Module::class, 'responsible_id');
|
return $this->hasOne(Module::class, 'responsible_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generar username automático al crear usuario
|
|
||||||
* Formato: inicial nombre + inicial segundo nombre + apellido paterno + inicial materno
|
|
||||||
*/
|
|
||||||
public static function generateUsername(?string $name, ?string $paternal, ?string $maternal = null): string
|
|
||||||
{
|
|
||||||
// Validar que al menos tengamos nombre y apellido paterno
|
|
||||||
if (empty($name) || empty($paternal)) {
|
|
||||||
return self::ensureUniqueUsername('user');
|
|
||||||
}
|
|
||||||
|
|
||||||
$name = self::normalizeString($name);
|
|
||||||
$paternal = self::normalizeString($paternal);
|
|
||||||
$maternal = $maternal ? self::normalizeString($maternal) : '';
|
|
||||||
|
|
||||||
// Separar nombres y obtener iniciales
|
|
||||||
$nameParts = preg_split('/\s+/', trim($name));
|
|
||||||
$firstInitial = !empty($nameParts[0]) ? substr($nameParts[0], 0, 1) : '';
|
|
||||||
$secondInitial = isset($nameParts[1]) && !empty($nameParts[1]) ? substr($nameParts[1], 0, 1) : '';
|
|
||||||
$maternalInitial = !empty($maternal) ? substr($maternal, 0, 1) : '';
|
|
||||||
|
|
||||||
// Construir username
|
|
||||||
$baseUsername = $firstInitial . $secondInitial . $paternal . $maternalInitial;
|
|
||||||
|
|
||||||
// Si el username queda vacío, usar fallback
|
|
||||||
if (empty($baseUsername)) {
|
|
||||||
$baseUsername = 'user';
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::ensureUniqueUsername($baseUsername);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Normalizar string: quitar acentos, convertir a minúsculas, solo letras
|
|
||||||
*/
|
|
||||||
private static function normalizeString(string $string): string
|
|
||||||
{
|
|
||||||
// Convertir a minúsculas
|
|
||||||
$string = mb_strtolower($string);
|
|
||||||
|
|
||||||
// Reemplazar caracteres acentuados
|
|
||||||
$replacements = [
|
|
||||||
'á' => 'a',
|
|
||||||
'é' => 'e',
|
|
||||||
'í' => 'i',
|
|
||||||
'ó' => 'o',
|
|
||||||
'ú' => 'u',
|
|
||||||
'ä' => 'a',
|
|
||||||
'ë' => 'e',
|
|
||||||
'ï' => 'i',
|
|
||||||
'ö' => 'o',
|
|
||||||
'ü' => 'u',
|
|
||||||
'à' => 'a',
|
|
||||||
'è' => 'e',
|
|
||||||
'ì' => 'i',
|
|
||||||
'ò' => 'o',
|
|
||||||
'ù' => 'u',
|
|
||||||
'ñ' => 'n',
|
|
||||||
'ç' => 'c',
|
|
||||||
];
|
|
||||||
|
|
||||||
$string = strtr($string, $replacements);
|
|
||||||
|
|
||||||
// Eliminar cualquier caracter que no sea letra o espacio
|
|
||||||
$string = preg_replace('/[^a-z\s]/', '', $string);
|
|
||||||
|
|
||||||
return $string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asegurar que el username sea único, agregando número si es necesario
|
|
||||||
*/
|
|
||||||
private static function ensureUniqueUsername(string $baseUsername): string
|
|
||||||
{
|
|
||||||
$username = $baseUsername;
|
|
||||||
$counter = 1;
|
|
||||||
|
|
||||||
while (self::where('username', $username)->exists()) {
|
|
||||||
$counter++;
|
|
||||||
$username = $baseUsername . $counter;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $username;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,7 @@ public function created(User $user): void
|
|||||||
UserEvent::report(
|
UserEvent::report(
|
||||||
model: $user,
|
model: $user,
|
||||||
event: __FUNCTION__,
|
event: __FUNCTION__,
|
||||||
key: 'email'
|
key: 'username'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ public function updated(User $user): void
|
|||||||
UserEvent::report(
|
UserEvent::report(
|
||||||
model: $user,
|
model: $user,
|
||||||
event: __FUNCTION__,
|
event: __FUNCTION__,
|
||||||
key: 'email',
|
key: 'username',
|
||||||
reportChanges: true
|
reportChanges: true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -48,7 +48,7 @@ public function deleted(User $user): void
|
|||||||
UserEvent::report(
|
UserEvent::report(
|
||||||
model: $user,
|
model: $user,
|
||||||
event: __FUNCTION__,
|
event: __FUNCTION__,
|
||||||
key: 'email'
|
key: 'username'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ public function restored(User $user): void
|
|||||||
UserEvent::report(
|
UserEvent::report(
|
||||||
model: $user,
|
model: $user,
|
||||||
event: __FUNCTION__,
|
event: __FUNCTION__,
|
||||||
key: 'email'
|
key: 'username'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ public function forceDeleted(User $user): void
|
|||||||
UserEvent::report(
|
UserEvent::report(
|
||||||
model: $user,
|
model: $user,
|
||||||
event: __FUNCTION__,
|
event: __FUNCTION__,
|
||||||
key: 'email'
|
key: 'username'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
// Eliminar email si existe
|
||||||
|
if (Schema::hasColumn('users', 'email')) {
|
||||||
|
$table->dropUnique(['email']);
|
||||||
|
$table->dropColumn('email');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eliminar email_verified_at si existe
|
||||||
|
if (Schema::hasColumn('users', 'email_verified_at')) {
|
||||||
|
$table->dropColumn('email_verified_at');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
if (!Schema::hasColumn('users', 'email')) {
|
||||||
|
$table->string('email')->unique()->after('maternal');
|
||||||
|
$table->timestamp('email_verified_at')->nullable()->after('email');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -21,24 +21,28 @@ class UserSeeder extends Seeder
|
|||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
$developer = UserSecureSupport::create('developer@golsystems.com.mx');
|
$developer = UserSecureSupport::create('developer');
|
||||||
|
|
||||||
User::create([
|
User::updateOrCreate(
|
||||||
'name' => 'Developer',
|
['username' => 'developer'],
|
||||||
'paternal' => 'golsystems',
|
[
|
||||||
'maternal' => 'Software',
|
'name' => 'Developer',
|
||||||
'email' => $developer->email,
|
'paternal' => 'golsystems',
|
||||||
'password' => $developer->hash,
|
'maternal' => 'Software',
|
||||||
])->assignRole(__('developer'));
|
'password' => $developer->hash,
|
||||||
|
]
|
||||||
|
)->assignRole(__('developer'));
|
||||||
|
|
||||||
$admin = UserSecureSupport::create('admin@golsystems.com.mx');
|
$admin = UserSecureSupport::create('admin');
|
||||||
|
|
||||||
User::create([
|
User::updateOrCreate(
|
||||||
'name' => 'Admin',
|
['username' => 'admin'],
|
||||||
'paternal' => 'golsystems',
|
[
|
||||||
'maternal' => 'Software',
|
'name' => 'Admin',
|
||||||
'email' => $admin->email,
|
'paternal' => 'golsystems',
|
||||||
'password' => $admin->hash,
|
'maternal' => 'Software',
|
||||||
])->assignRole(__('admin'));
|
'password' => $admin->hash,
|
||||||
|
]
|
||||||
|
)->assignRole(__('admin'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user