diff --git a/app/Http/Controllers/DevTools/FlowTesterController.php b/app/Http/Controllers/DevTools/FlowTesterController.php
index 711f5485..fa21a3ee 100644
--- a/app/Http/Controllers/DevTools/FlowTesterController.php
+++ b/app/Http/Controllers/DevTools/FlowTesterController.php
@@ -25,10 +25,12 @@ public function index(): View
->orderByDesc('created_at')
->paginate(20);
- // 세션에 저장된 토큰
- $savedToken = session('flow_tester_token');
+ // 세션에 저장된 토큰 (API Explorer와 공유)
+ $savedToken = session('api_explorer_token');
+ $selectedUserId = session('api_explorer_user_id');
+ $selectedUser = $selectedUserId ? \App\Models\User::find($selectedUserId) : null;
- return view('dev-tools.flow-tester.index', compact('flows', 'savedToken'));
+ return view('dev-tools.flow-tester.index', compact('flows', 'savedToken', 'selectedUser'));
}
/**
@@ -335,12 +337,68 @@ public function runDetail(int $runId): View
/*
|--------------------------------------------------------------------------
- | Token Management
+ | Token & User Management (API Explorer와 공유)
|--------------------------------------------------------------------------
*/
/**
- * Bearer 토큰 저장
+ * 현재 테넌트의 사용자 목록
+ */
+ public function users()
+ {
+ $tenantId = auth()->user()->tenant_id;
+
+ $users = \App\Models\User::where('tenant_id', $tenantId)
+ ->select(['id', 'name', 'email', 'tenant_id'])
+ ->orderBy('name')
+ ->limit(100)
+ ->get();
+
+ return response()->json($users);
+ }
+
+ /**
+ * 사용자 선택 (Sanctum 토큰 발급)
+ */
+ public function selectUser(Request $request)
+ {
+ $validated = $request->validate([
+ 'user_id' => 'required|integer',
+ ]);
+
+ $user = \App\Models\User::find($validated['user_id']);
+
+ if (! $user) {
+ return response()->json([
+ 'success' => false,
+ 'message' => '사용자를 찾을 수 없습니다.',
+ ], 404);
+ }
+
+ // Sanctum 토큰 발급
+ $token = $user->createToken('flow-tester', ['*'])->plainTextToken;
+
+ // 세션에 저장 (API Explorer와 공유)
+ session([
+ 'api_explorer_token' => $token,
+ 'api_explorer_user_id' => $user->id,
+ ]);
+
+ return response()->json([
+ 'success' => true,
+ 'message' => "'{$user->name}' 사용자로 인증되었습니다.",
+ 'user' => [
+ 'id' => $user->id,
+ 'name' => $user->name,
+ 'email' => $user->email,
+ 'tenant_id' => $user->tenant_id,
+ ],
+ 'token_preview' => substr($token, 0, 20).'...',
+ ]);
+ }
+
+ /**
+ * Bearer 토큰 저장 (직접 입력)
*/
public function saveToken(Request $request)
{
@@ -348,7 +406,11 @@ public function saveToken(Request $request)
'token' => 'required|string',
]);
- session(['flow_tester_token' => $validated['token']]);
+ // API Explorer와 같은 세션 키 사용
+ session([
+ 'api_explorer_token' => $validated['token'],
+ 'api_explorer_user_id' => null, // 직접 입력 시 사용자 정보 없음
+ ]);
return response()->json([
'success' => true,
@@ -361,11 +423,11 @@ public function saveToken(Request $request)
*/
public function clearToken()
{
- session()->forget('flow_tester_token');
+ session()->forget(['api_explorer_token', 'api_explorer_user_id']);
return response()->json([
'success' => true,
- 'message' => '토큰이 초기화되었습니다.',
+ 'message' => '인증이 초기화되었습니다.',
]);
}
@@ -374,11 +436,19 @@ public function clearToken()
*/
public function tokenStatus()
{
- $token = session('flow_tester_token');
+ $token = session('api_explorer_token');
+ $userId = session('api_explorer_user_id');
+ $user = $userId ? \App\Models\User::find($userId) : null;
return response()->json([
'has_token' => ! empty($token),
'token_preview' => $token ? substr($token, 0, 20).'...' : null,
+ 'user' => $user ? [
+ 'id' => $user->id,
+ 'name' => $user->name,
+ 'email' => $user->email,
+ 'tenant_id' => $user->tenant_id,
+ ] : null,
]);
}
}
diff --git a/app/Services/FlowTester/VariableBinder.php b/app/Services/FlowTester/VariableBinder.php
index 5e186337..d1fe459e 100644
--- a/app/Services/FlowTester/VariableBinder.php
+++ b/app/Services/FlowTester/VariableBinder.php
@@ -17,6 +17,10 @@
* - {{$uuid}} - 랜덤 UUID
* - {{$random:N}} - N자리 랜덤 숫자
* - {{$session.token}} - 세션에 저장된 Bearer 토큰
+ * - {{$hmac.exp}} - HMAC 만료 시간 (현재 + 300초)
+ * - {{$hmac.signature}} - HMAC-SHA256 서명
+ * - {{$hmac.user_id}} - 현재 로그인 사용자 ID
+ * - {{$hmac.tenant_id}} - 현재 테넌트 ID
* - {{$faker.xxx}} - Faker 기반 랜덤 데이터 생성
*/
class VariableBinder
@@ -152,12 +156,15 @@ function ($m) {
// {{$auth.apiKey}} → .env의 API Key
$input = str_replace('{{$auth.apiKey}}', env('FLOW_TESTER_API_KEY', ''), $input);
- // {{$session.token}} → 세션에 저장된 Bearer 토큰
+ // {{$session.token}} → 세션에 저장된 Bearer 토큰 (API Explorer와 공유)
if (str_contains($input, '{{$session.token}}')) {
- $token = session('flow_tester_token', '');
+ $token = session('api_explorer_token', '');
$input = str_replace('{{$session.token}}', $token, $input);
}
+ // {{$hmac.xxx}} → HMAC 인증용 동적 값 생성
+ $input = $this->resolveHmac($input);
+
// {{$faker.xxx}} → Faker 기반 랜덤 데이터 생성
$input = $this->resolveFaker($input);
@@ -339,6 +346,58 @@ private function getAuthToken(): string
return env('FLOW_TESTER_API_TOKEN', '');
}
+ /**
+ * HMAC 인증용 동적 값 생성
+ *
+ * 지원 패턴:
+ * - {{$hmac.exp}} - 만료 시간 (현재 + 300초)
+ * - {{$hmac.user_id}} - 선택된 사용자 ID (또는 현재 로그인 사용자)
+ * - {{$hmac.tenant_id}} - 선택된 사용자의 테넌트 ID
+ * - {{$hmac.signature}} - HMAC-SHA256 서명
+ *
+ * 서명 생성 방식:
+ * - payload: "{user_id}:{tenant_id}:{exp}"
+ * - signature: hash_hmac('sha256', payload, exchange_secret)
+ *
+ * 세션에서 선택된 사용자 정보를 우선 사용 (API Explorer와 공유)
+ */
+ private function resolveHmac(string $input): string
+ {
+ // HMAC 변수가 없으면 조기 반환
+ if (! str_contains($input, '{{$hmac.')) {
+ return $input;
+ }
+
+ // 세션에서 선택된 사용자 ID 가져오기 (API Explorer와 공유)
+ $selectedUserId = session('api_explorer_user_id');
+
+ // 선택된 사용자가 있으면 해당 사용자 정보 사용, 없으면 현재 로그인 사용자
+ if ($selectedUserId) {
+ $user = \App\Models\User::find($selectedUserId);
+ } else {
+ $user = auth()->user();
+ }
+
+ $userId = $user?->id ?? env('FLOW_TESTER_USER_ID', '');
+ $tenantId = $user?->tenant_id ?? env('FLOW_TESTER_TENANT_ID', '');
+
+ // 만료 시간 (현재 + 300초)
+ $exp = time() + 300;
+
+ // 서명 생성
+ $exchangeSecret = config('services.api.exchange_secret', '');
+ $payload = "{$userId}:{$tenantId}:{$exp}";
+ $signature = hash_hmac('sha256', $payload, $exchangeSecret);
+
+ // 각 HMAC 변수 치환
+ $input = str_replace('{{$hmac.exp}}', (string) $exp, $input);
+ $input = str_replace('{{$hmac.user_id}}', (string) $userId, $input);
+ $input = str_replace('{{$hmac.tenant_id}}', (string) $tenantId, $input);
+ $input = str_replace('{{$hmac.signature}}', $signature, $input);
+
+ return $input;
+ }
+
/**
* 참조 경로 해석 (step1.pageId 또는 page_create_1.pageId → 실제 값)
*
diff --git a/resources/views/dev-tools/flow-tester/index.blade.php b/resources/views/dev-tools/flow-tester/index.blade.php
index 98f32fc2..e346785d 100644
--- a/resources/views/dev-tools/flow-tester/index.blade.php
+++ b/resources/views/dev-tools/flow-tester/index.blade.php
@@ -13,7 +13,13 @@
사용자를 선택하면 해당 사용자의 Sanctum 토큰이 자동 발급됩니다.
+✅ 세션에 저장된 토큰이 있습니다.
- @endif -플로우에서 \{\{$session.token\}\}로 참조할 수 있습니다.
플로우에서 @{{$session.token}}로 참조할 수 있습니다.