From 00569cf4beb2bfac6a54870ea329859e12ab5bc1 Mon Sep 17 00:00:00 2001 From: hskwon Date: Mon, 18 Aug 2025 16:37:02 +0900 Subject: [PATCH] =?UTF-8?q?fix=20:=20Auth=20-=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Api/V1/ApiController.php | 39 +++- .../Controllers/Api/V1/UserController.php | 7 - app/Http/Middleware/ApiKeyMiddleware.php | 1 + app/Swagger/v1/AuthApi.php | 220 ++++++++++++------ routes/api.php | 10 +- 5 files changed, 187 insertions(+), 90 deletions(-) diff --git a/app/Http/Controllers/Api/V1/ApiController.php b/app/Http/Controllers/Api/V1/ApiController.php index 9d73431..673c043 100644 --- a/app/Http/Controllers/Api/V1/ApiController.php +++ b/app/Http/Controllers/Api/V1/ApiController.php @@ -2,10 +2,13 @@ namespace App\Http\Controllers\Api\V1; +use App\Helpers\ApiResponse; use App\Http\Controllers\Controller; use App\Models\Members\User; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; +use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; @@ -65,8 +68,6 @@ public function login(Request $request) ]); } - - public function logout(Request $request) { //인증토큰 삭제 @@ -75,4 +76,38 @@ public function logout(Request $request) return response()->json(['message' => '로그아웃 완료']); } + + public function signup(Request $request) + { + // 신규 회원 생성 + 역할 부여 지원 + $v = Validator::make($request->all(), [ + 'user_id' => 'required|string|max:255|unique:users,user_id', + 'name' => 'required|string|max:255', + 'email' => 'required|email|max:100|unique:users,email', + 'phone' => 'nullable|string|max:30', + 'password' => 'required|string|min:8|max:64', + ]); + + if ($v->fails()) { + return ApiResponse::error($v->errors()->first(), 422); + } + + $payload = $v->validated(); + + return DB::transaction(function () use ($payload) { + // 신규 사용자 생성 + $user = User::create([ + 'user_id' => $payload['user_id'], + 'name' => $payload['name'], + 'email' => $payload['email'], + 'phone' => $payload['phone'] ?? null, + 'password' => $payload['password'], // 캐스트가 알아서 해싱 + ]); + + return ApiResponse::response('result', [ + 'user' => $user->only(['id','user_id','name','email','phone']), + ]); + }); + } + } diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 4e0107c..f23e19d 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -16,13 +16,6 @@ public function index(Request $request) }, '회원목록 조회'); } - public function store(Request $request) - { - return ApiResponse::handle(function () use ($request) { - return MemberService::setMember($request->all()); - }, '회원등록'); - } - public function show($userNo) { return ApiResponse::handle(function () use ($userNo) { diff --git a/app/Http/Middleware/ApiKeyMiddleware.php b/app/Http/Middleware/ApiKeyMiddleware.php index c2b079a..09b895e 100644 --- a/app/Http/Middleware/ApiKeyMiddleware.php +++ b/app/Http/Middleware/ApiKeyMiddleware.php @@ -71,6 +71,7 @@ public function handle(Request $request, Closure $next) // 화이트리스트(인증 예외 라우트) $allowWithoutAuth = [ 'api/v1/login', + 'api/v1/signup', 'api/v1/debug-apikey', // 추가적으로 허용하고 싶은 라우트 ]; diff --git a/app/Swagger/v1/AuthApi.php b/app/Swagger/v1/AuthApi.php index c36dfa6..343bc1e 100644 --- a/app/Swagger/v1/AuthApi.php +++ b/app/Swagger/v1/AuthApi.php @@ -3,80 +3,148 @@ namespace App\Swagger\v1; /** - * @OA\Get( - * path="/api/v1/debug-apikey", - * tags={"API Key 인증"}, - * summary="API Key 인증 확인", - * security={ - * {"ApiKeyAuth": {}}, - * {"BearerAuth": {}} - * }, - * @OA\Response( - * response=200, - * description="API Key 인증 성공" - * ), - * @OA\Response( - * response=401, - * description="인증 실패", - * @OA\JsonContent(ref="#/components/schemas/ErrorResponse") - * ) - * ) - * - * @OA\Post( - * path="/api/v1/login", - * summary="회원 토큰 정보확인", - * tags={"Auth"}, - * security={ - * {"ApiKeyAuth": {}} - * }, - * @OA\RequestBody( - * required=true, - * @OA\JsonContent( - * required={"user_id", "user_pwd"}, - * @OA\Property(property="user_id", type="string", example="test"), - * @OA\Property(property="user_pwd", type="string", example="testpass") - * ) - * ), - * @OA\Response( - * response=200, - * description="로그인 성공", - * @OA\JsonContent( - * @OA\Property(property="success", type="boolean", example=true), - * @OA\Property(property="message", type="string", example="로그인 성공"), - * @OA\Property(property="data", type="object", - * @OA\Property(property="user_token", type="string", example="abc123xyz") - * ) - * ) - * ), - * @OA\Response( - * response=401, - * description="로그인 실패", - * @OA\JsonContent(ref="#/components/schemas/ErrorResponse") - * ) - * ) - * - * @OA\Post( - * path="/api/v1/logout", - * summary="로그아웃 (Access 및 Token 무효화)", - * tags={"Auth"}, - * security={ - * {"ApiKeyAuth": {}}, - * {"BearerAuth": {}} - * }, - * @OA\Response( - * response=200, - * description="로그아웃 성공", - * @OA\JsonContent( - * @OA\Property(property="success", type="boolean", example=true), - * @OA\Property(property="message", type="string", example="로그아웃 성공"), - * @OA\Property(property="data", type="null", example=null) - * ) - * ), - * @OA\Response( - * response=401, - * description="인증 실패", - * @OA\JsonContent(ref="#/components/schemas/ErrorResponse") - * ) - * ) + * @OA\Tag(name="Auth", description="로그인/로그아웃/회원가입") */ -class AuthApi {} + +/** + * 회원가입 요청/응답 스키마 + * ----------------------------------------------------------------------------- + * 필요 시 공용 components 영역으로 이동해도 됩니다. + */ + +/** + * @OA\Schema( + * schema="SignupRequest", + * type="object", + * required={"user_id","name","email","password"}, + * @OA\Property(property="user_id", type="string", maxLength=255, example="userId", description="로그인 ID (고유)"), + * @OA\Property(property="name", type="string", maxLength=255, example="Kent"), + * @OA\Property(property="email", type="string", maxLength=100, example="codebridge@gmail.com"), + * @OA\Property(property="phone", type="string", maxLength=30, nullable=true, example="010-4820-9104"), + * @OA\Property(property="password", type="string", minLength=8, maxLength=64, example="StrongPass!1234") + * ) + * + * @OA\Schema( + * schema="SignupResponseData", + * type="object", + * @OA\Property( + * property="user", + * ref="#/components/schemas/Member" + * ) + * ) + * + * @OA\Schema( + * schema="MemberBrief", + * type="object", + * description="회원 요약 정보(회원가입 응답용)", + * required={"id","user_id","name","email","phone"}, + * @OA\Property(property="id", type="integer", example=6), + * @OA\Property(property="user_id", type="string", example="userId"), + * @OA\Property(property="name", type="string", example="Kent"), + * @OA\Property(property="email", type="string", example="codebridge1@gmail.com"), + * @OA\Property(property="phone", type="string", example="010-4820-9104") + * ) + * + */ +class AuthApi +{ + /** + * @OA\Get( + * path="/api/v1/debug-apikey", + * tags={"Auth"}, + * summary="API Key 인증 확인", + * security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}}, + * @OA\Response(response=200, description="API Key 인증 성공"), + * @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")) + * ) + */ + public function debugApiKey() {} + + /** + * @OA\Post( + * path="/api/v1/login", + * tags={"Auth"}, + * summary="로그인 (토큰 발급)", + * security={{"ApiKeyAuth": {}}}, + * @OA\RequestBody( + * required=true, + * @OA\JsonContent( + * required={"user_id","user_pwd"}, + * @OA\Property(property="user_id", type="string", example="hamss"), + * @OA\Property(property="user_pwd", type="string", example="StrongPass!1234") + * ) + * ), + * @OA\Response( + * response=200, + * description="로그인 성공", + * @OA\JsonContent( + * type="object", + * @OA\Property(property="success", type="boolean", example=true), + * @OA\Property(property="message", type="string", example="로그인 성공"), + * @OA\Property(property="data", type="object", + * @OA\Property(property="user_token", type="string", example="abc123xyz") + * ) + * ) + * ), + * @OA\Response(response=401, description="로그인 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")) + * ) + */ + public function login() {} + + /** + * @OA\Post( + * path="/api/v1/logout", + * tags={"Auth"}, + * summary="로그아웃 (Access 및 Token 무효화)", + * security={{"ApiKeyAuth": {}}, {"BearerAuth": {}}}, + * @OA\Response( + * response=200, + * description="로그아웃 성공", + * @OA\JsonContent( + * @OA\Property(property="success", type="boolean", example=true), + * @OA\Property(property="message", type="string", example="로그아웃 성공"), + * @OA\Property(property="data", type="null", example=null) + * ) + * ), + * @OA\Response(response=401, description="인증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")) + * ) + */ + public function logout() {} + + /** + * @OA\Post( + * path="/api/v1/signup", + * tags={"Auth"}, + * summary="회원가입", + * description="신규 회원을 생성합니다. (API Key 필요)", + * security={{"ApiKeyAuth": {}}}, + * @OA\RequestBody(required=true, @OA\JsonContent(ref="#/components/schemas/SignupRequest")), + * @OA\Response( + * response=200, + * description="회원가입 성공", + * @OA\JsonContent( + * type="object", + * @OA\Property( + * property="data", + * type="object", + * @OA\Property(property="user", ref="#/components/schemas/MemberBrief") + * ), + * example={ + * "data": { + * "user": { + * "id": 6, + * "user_id": "userId", + * "name": "Kent", + * "email": "codebridge@gmail.com", + * "phone": "010-4820-9104" + * } + * } + * } + * ) + * ), + * @OA\Response(response=422, description="검증 실패", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")), + * @OA\Response(response=401, description="인증 실패(API Key 누락/오류)", @OA\JsonContent(ref="#/components/schemas/ErrorResponse")) + * ) + */ + public function signup() {} +} diff --git a/routes/api.php b/routes/api.php index e364ac5..444523b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -33,8 +33,9 @@ Route::middleware('auth.apikey')->group(function () { # Auth API - Route::post('login', [ApiController::class, 'login']); - Route::middleware('auth:sanctum')->post('/logout', [ApiController::class, 'logout']); + Route::post('login', [ApiController::class, 'login'])->name('v1.users.login'); + Route::middleware('auth:sanctum')->post('logout', [ApiController::class, 'logout'])->name('v1.users.logout'); + Route::post('signup', [ApiController::class, 'signup'])->name('v1.users.signup'); // Common API @@ -79,13 +80,12 @@ Route::prefix('users')->group(function () { Route::get('index', [UserController::class, 'index'])->name('v1.users.index'); // 회원 목록 조회 Route::get('show/{user_no}', [UserController::class, 'show'])->name('v1.users.show'); // 회원 상세 조회 - Route::post('store', [UserController::class, 'store'])->name('v1.users.store')->middleware('permission:AC'); // 회원 저장 (등록/수정) Route::get('me', [UserController::class, 'me'])->name('v1.users.users.me'); // 내 정보 조회 - Route::put('me', [UserController::class, 'meUpdate'])->name('v1.users.me.update'); // 내 정보 수정 + Route::put('me', [UserController::class, 'meUpdate'])->name('v1.users.me.update'); // 내 정보 수정 Route::put('me/password', [UserController::class, 'changePassword'])->name('v1.users.me.password'); // 비밀번호 변겅 - Route::get('me/tenants', [UserController::class, 'tenants'])->name('v1.users.me.tenants.index'); // 내 테넌트 목록 + Route::get('me/tenants', [UserController::class, 'tenants'])->name('v1.users.me.tenants.index'); // 내 테넌트 목록 Route::patch('me/tenants/switch', [UserController::class, 'switchTenant'])->name('v1.users.me.tenants.switch'); // 활성 테넌트 전환 });