diff --git a/app/Http/Controllers/Api/Admin/UserController.php b/app/Http/Controllers/Api/Admin/UserController.php
index 5e9a6543..2a27b70d 100644
--- a/app/Http/Controllers/Api/Admin/UserController.php
+++ b/app/Http/Controllers/Api/Admin/UserController.php
@@ -5,6 +5,7 @@
use App\Http\Controllers\Controller;
use App\Http\Requests\StoreUserRequest;
use App\Http\Requests\UpdateUserRequest;
+use App\Models\LoginToken;
use App\Services\UserService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -292,4 +293,47 @@ public function forceDestroy(Request $request, int $id): JsonResponse
'message' => '사용자가 영구 삭제되었습니다.',
]);
}
+
+ /**
+ * DEV 사이트 자동 로그인 토큰 생성
+ * MNG → DEV 자동 로그인용 One-Time Token 발급
+ */
+ public function loginToken(Request $request, int $id): JsonResponse
+ {
+ try {
+ // 슈퍼관리자 보호: 일반관리자가 슈퍼관리자로 로그인 불가
+ if (! $this->userService->canAccessUser($id)) {
+ return response()->json([
+ 'success' => false,
+ 'message' => '사용자를 찾을 수 없습니다.',
+ ], 404);
+ }
+
+ $user = $this->userService->getUserById($id);
+
+ if (! $user) {
+ return response()->json([
+ 'success' => false,
+ 'message' => '사용자를 찾을 수 없습니다.',
+ ], 404);
+ }
+
+ // One-Time Token 생성
+ $loginToken = LoginToken::createForUser($user->id);
+
+ return response()->json([
+ 'success' => true,
+ 'message' => 'DEV 접속 토큰이 생성되었습니다.',
+ 'data' => [
+ 'url' => $loginToken->getAutoLoginUrl(),
+ 'expires_at' => $loginToken->expires_at->toIso8601String(),
+ ],
+ ]);
+ } catch (\Exception $e) {
+ return response()->json([
+ 'success' => false,
+ 'message' => 'DEV 접속 토큰 생성에 실패했습니다: '.$e->getMessage(),
+ ], 500);
+ }
+ }
}
diff --git a/app/Models/LoginToken.php b/app/Models/LoginToken.php
new file mode 100644
index 00000000..6c9f36b4
--- /dev/null
+++ b/app/Models/LoginToken.php
@@ -0,0 +1,61 @@
+ 'datetime',
+ ];
+
+ /**
+ * 토큰 만료 시간 (분)
+ */
+ public const EXPIRES_IN_MINUTES = 5;
+
+ /**
+ * 사용자 관계
+ */
+ public function user(): BelongsTo
+ {
+ return $this->belongsTo(User::class);
+ }
+
+ /**
+ * 새 토큰 생성
+ */
+ public static function createForUser(int $userId): self
+ {
+ // 기존 토큰 삭제 (해당 사용자)
+ self::where('user_id', $userId)->delete();
+
+ return self::create([
+ 'user_id' => $userId,
+ 'token' => Str::random(64),
+ 'expires_at' => now()->addMinutes(self::EXPIRES_IN_MINUTES),
+ ]);
+ }
+
+ /**
+ * DEV 사이트 자동 로그인 URL 생성
+ */
+ public function getAutoLoginUrl(): string
+ {
+ $devAppUrl = config('services.dev.app_url', 'https://dev.sam.kr');
+
+ return "{$devAppUrl}/auto-login?token={$this->token}";
+ }
+}
diff --git a/config/services.php b/config/services.php
index 8ab33f31..c96836c1 100644
--- a/config/services.php
+++ b/config/services.php
@@ -64,4 +64,16 @@
'exchange_secret' => env('INTERNAL_EXCHANGE_SECRET'),
],
+ /*
+ |--------------------------------------------------------------------------
+ | DEV Site (React Frontend)
+ |--------------------------------------------------------------------------
+ | MNG → DEV 자동 로그인 시 사용할 DEV 사이트 URL
+ | - 로컬: https://dev.sam.kr
+ | - 개발: https://dev.codebridge-x.com
+ */
+ 'dev' => [
+ 'app_url' => env('DEV_APP_URL', 'https://dev.sam.kr'),
+ ],
+
];
diff --git a/resources/views/users/index.blade.php b/resources/views/users/index.blade.php
index 4d1bb881..1710f741 100644
--- a/resources/views/users/index.blade.php
+++ b/resources/views/users/index.blade.php
@@ -115,5 +115,32 @@ class="bg-white rounded-lg shadow-sm overflow-hidden">
});
});
};
+
+ // DEV 사이트 접속 (자동 로그인)
+ window.openDevSite = function(id, name) {
+ showConfirm(`"${name}" 사용자로 DEV 사이트에 접속하시겠습니까?`, () => {
+ // 토큰 생성 API 호출
+ fetch(`/api/admin/users/${id}/login-token`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-CSRF-TOKEN': '{{ csrf_token() }}'
+ }
+ })
+ .then(response => response.json())
+ .then(data => {
+ if (data.success && data.data?.url) {
+ // 새 창에서 DEV 사이트 열기
+ window.open(data.data.url, '_blank');
+ } else {
+ showAlert(data.message || 'DEV 접속 토큰 생성에 실패했습니다.', 'error');
+ }
+ })
+ .catch(error => {
+ console.error('DEV 접속 오류:', error);
+ showAlert('DEV 접속 중 오류가 발생했습니다.', 'error');
+ });
+ }, { title: 'DEV 사이트 접속', icon: 'question' });
+ };
@endpush
\ No newline at end of file
diff --git a/resources/views/users/partials/table.blade.php b/resources/views/users/partials/table.blade.php
index 81ee0c9c..e097942d 100644
--- a/resources/views/users/partials/table.blade.php
+++ b/resources/views/users/partials/table.blade.php
@@ -115,6 +115,11 @@ class="text-red-600 hover:text-red-900">
@endif
@elseif($canModify)
+
diff --git a/routes/api.php b/routes/api.php
index 79121a18..0965d4e8 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -108,6 +108,9 @@
Route::delete('/{id}/force', [UserController::class, 'forceDestroy'])->name('forceDestroy');
});
+ // DEV 사이트 자동 로그인 토큰 생성 (MNG → DEV)
+ Route::post('/{id}/login-token', [UserController::class, 'loginToken'])->name('loginToken');
+
// 모달 관련 API
Route::get('/{id}/modal', [UserController::class, 'modal'])->name('modal');
});