feat: [users] 사용자 등록 시 비밀번호 자동 생성 및 이메일 발송

- 사용자 등록 시 비밀번호 입력 필드 제거
- 임의 비밀번호 자동 생성 후 이메일 발송
- 사용자 수정 페이지에 비밀번호 초기화 버튼 추가
- 사용자 모달에 비밀번호 초기화 버튼 추가
- 사용자 모달 프로필 이미지 없을 때 이름 첫글자 표시 (한글 지원)
- UserPasswordMail 클래스 및 이메일 템플릿 추가
This commit is contained in:
2025-12-01 10:50:16 +09:00
parent 4a454db0dc
commit 85cbe23782
10 changed files with 355 additions and 44 deletions

View File

@@ -228,6 +228,41 @@ public function modal(Request $request, int $id): JsonResponse
]);
}
/**
* 비밀번호 초기화 (임의 비밀번호 생성 + 메일 발송)
*/
public function resetPassword(Request $request, int $id): JsonResponse
{
try {
// 슈퍼관리자 보호: 일반관리자가 슈퍼관리자 비밀번호 초기화 불가
if (! $this->userService->canAccessUser($id)) {
return response()->json([
'success' => false,
'message' => '사용자를 찾을 수 없습니다.',
], 404);
}
$result = $this->userService->resetPassword($id);
if (! $result) {
return response()->json([
'success' => false,
'message' => '사용자를 찾을 수 없습니다.',
], 404);
}
return response()->json([
'success' => true,
'message' => '비밀번호가 초기화되었습니다. 새 비밀번호가 사용자 이메일로 발송되었습니다.',
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'message' => '비밀번호 초기화에 실패했습니다: '.$e->getMessage(),
], 500);
}
}
/**
* 사용자 영구 삭제 (슈퍼관리자 전용)
*/

View File

@@ -40,7 +40,7 @@ public function rules(): array
'name' => 'required|string|max:100',
'email' => 'required|email|max:255|unique:users,email',
'phone' => 'nullable|string|max:20',
'password' => 'required|string|min:8|confirmed',
// password는 시스템이 자동 생성하므로 입력 받지 않음
'role' => 'nullable|string|max:50',
'is_active' => 'nullable|boolean',
'is_super_admin' => 'nullable|boolean',

View File

@@ -0,0 +1,58 @@
<?php
namespace App\Mail;
use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class UserPasswordMail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*/
public function __construct(
public User $user,
public string $password,
public bool $isNewUser = true
) {}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
$subject = $this->isNewUser
? '[SAM] 계정이 생성되었습니다'
: '[SAM] 비밀번호가 초기화되었습니다';
return new Envelope(
subject: $subject,
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'emails.user-password',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}

View File

@@ -2,12 +2,14 @@
namespace App\Services;
use App\Mail\UserPasswordMail;
use App\Models\DepartmentUser;
use App\Models\User;
use App\Models\UserRole;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
class UserService
{
@@ -80,16 +82,15 @@ public function getUserById(int $id): ?User
}
/**
* 사용자 생성
* 사용자 생성 (관리자용: 임의 비밀번호 생성 + 메일 발송)
*/
public function createUser(array $data): User
{
$tenantId = session('selected_tenant_id');
// 비밀번호 해싱
if (isset($data['password'])) {
$data['password'] = Hash::make($data['password']);
}
// 임의 비밀번호 생성 (8자리 영문+숫자)
$plainPassword = $this->generateRandomPassword();
$data['password'] = Hash::make($plainPassword);
// is_active 처리
$data['is_active'] = isset($data['is_active']) && $data['is_active'] == '1';
@@ -116,9 +117,56 @@ public function createUser(array $data): User
$this->syncDepartments($user, $tenantId, $departmentIds);
}
// 비밀번호 안내 메일 발송
$this->sendPasswordMail($user, $plainPassword, true);
return $user;
}
/**
* 비밀번호 초기화 (관리자용: 임의 비밀번호 생성 + 메일 발송)
*/
public function resetPassword(int $id): bool
{
$user = $this->getUserById($id);
if (! $user) {
return false;
}
// 임의 비밀번호 생성
$plainPassword = $this->generateRandomPassword();
// 비밀번호 업데이트
$user->password = Hash::make($plainPassword);
$user->updated_by = auth()->id();
$user->save();
// 비밀번호 초기화 안내 메일 발송
$this->sendPasswordMail($user, $plainPassword, false);
return true;
}
/**
* 임의 비밀번호 생성 (8자리 영문+숫자 조합)
*/
private function generateRandomPassword(int $length = 8): string
{
// 영문 대소문자 + 숫자 조합으로 가독성 좋은 비밀번호 생성
// 혼동되는 문자 제외: 0, O, l, 1, I
$chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789';
return substr(str_shuffle(str_repeat($chars, 3)), 0, $length);
}
/**
* 비밀번호 안내 메일 발송
*/
private function sendPasswordMail(User $user, string $password, bool $isNewUser): void
{
Mail::to($user->email)->send(new UserPasswordMail($user, $password, $isNewUser));
}
/**
* 사용자 수정
*/