feat(flow-tester): 세션 인증 모드(useSessionAuth) 지원
- VariableBinder: {{$session.xxx}} 변수 지원 추가
- {{$session.token}}: 세션 Bearer 토큰
- {{$session.user_id}}: 세션 사용자 ID
- {{$session.user.email/name}}: 사용자 정보
- {{$session.tenant_id}}: 테넌트 ID
- getSessionAuth() 메서드 추가
- FlowExecutor: useSessionAuth 옵션 처리
- login 스텝에서 API 호출 대신 세션 토큰 사용
- {{login.token}} 등 자동 바인딩
- 세션 토큰 없을 시 명확한 에러 메시지
페이지 인증 완료 시 플로우의 login 스텝에서
실제 API 호출 없이 세션 인증 정보를 활용할 수 있음
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -213,6 +213,11 @@ private function executeStep(array $step): array
|
||||
$startTime = microtime(true);
|
||||
|
||||
try {
|
||||
// 0. useSessionAuth 옵션 처리 - 세션 인증 정보로 대체
|
||||
if (! empty($step['useSessionAuth'])) {
|
||||
return $this->executeSessionAuthStep($step, $stepId, $stepName, $startTime);
|
||||
}
|
||||
|
||||
// 1. 딜레이 적용
|
||||
$delay = $step['delay'] ?? 0;
|
||||
if ($delay > 0) {
|
||||
@@ -302,6 +307,74 @@ private function executeStep(array $step): array
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 세션 인증 스텝 실행
|
||||
*
|
||||
* useSessionAuth: true 옵션이 있는 login 스텝에서 사용
|
||||
* 실제 API 호출 없이 세션에 저장된 토큰을 바인딩합니다.
|
||||
*
|
||||
* @param array $step 스텝 정의
|
||||
* @param string $stepId 스텝 ID
|
||||
* @param string $stepName 스텝 이름
|
||||
* @param float $startTime 시작 시간
|
||||
* @return array 스텝 결과
|
||||
*/
|
||||
private function executeSessionAuthStep(array $step, string $stepId, string $stepName, float $startTime): array
|
||||
{
|
||||
// 세션 인증 정보 조회
|
||||
$sessionAuth = $this->binder->getSessionAuth();
|
||||
|
||||
// 세션 토큰이 없으면 실패
|
||||
if (empty($sessionAuth['token'])) {
|
||||
return $this->buildStepResult($stepId, $stepName, $startTime, false, [
|
||||
'error' => '세션 인증 정보가 없습니다. 페이지에서 먼저 인증을 완료해주세요.',
|
||||
'reason' => '✗ 세션 토큰 없음 - 페이지 인증 필요',
|
||||
'useSessionAuth' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
// 세션 토큰을 스텝 결과로 바인딩 ({{login.token}} 등에서 사용 가능)
|
||||
$extracted = [
|
||||
'token' => $sessionAuth['token'],
|
||||
'access_token' => $sessionAuth['token'], // 호환성을 위해 추가
|
||||
];
|
||||
|
||||
// 사용자 정보도 바인딩
|
||||
if ($sessionAuth['user']) {
|
||||
$extracted['user_id'] = $sessionAuth['user']['id'];
|
||||
$extracted['user_name'] = $sessionAuth['user']['name'];
|
||||
$extracted['user_email'] = $sessionAuth['user']['email'];
|
||||
}
|
||||
if ($sessionAuth['tenant_id']) {
|
||||
$extracted['tenant_id'] = $sessionAuth['tenant_id'];
|
||||
}
|
||||
|
||||
// 스텝 결과 저장 (다음 스텝에서 {{login.token}} 등으로 참조 가능)
|
||||
$this->binder->setStepResult($stepId, $extracted, [
|
||||
'message' => '세션 인증 사용',
|
||||
'user' => $sessionAuth['user'],
|
||||
'tenant_id' => $sessionAuth['tenant_id'],
|
||||
]);
|
||||
|
||||
return $this->buildStepResult($stepId, $stepName, $startTime, true, [
|
||||
'description' => $step['description'] ?? '세션 인증 정보 사용',
|
||||
'reason' => '✓ 세션 인증 사용 (API 호출 생략)',
|
||||
'useSessionAuth' => true,
|
||||
'sessionUser' => $sessionAuth['user'],
|
||||
'extracted' => $extracted,
|
||||
'response' => [
|
||||
'status' => 200,
|
||||
'body' => [
|
||||
'message' => '세션 인증 정보를 사용합니다.',
|
||||
'access_token' => substr($sessionAuth['token'], 0, 20).'...',
|
||||
'user' => $sessionAuth['user'],
|
||||
'tenant_id' => $sessionAuth['tenant_id'],
|
||||
],
|
||||
'duration' => 0,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 플로우 정의 검증
|
||||
*/
|
||||
|
||||
@@ -156,11 +156,8 @@ function ($m) {
|
||||
// {{$auth.apiKey}} → .env의 API Key
|
||||
$input = str_replace('{{$auth.apiKey}}', env('FLOW_TESTER_API_KEY', ''), $input);
|
||||
|
||||
// {{$session.token}} → 세션에 저장된 Bearer 토큰 (API Explorer와 공유)
|
||||
if (str_contains($input, '{{$session.token}}')) {
|
||||
$token = session('api_explorer_token', '');
|
||||
$input = str_replace('{{$session.token}}', $token, $input);
|
||||
}
|
||||
// {{$session.xxx}} → 세션에 저장된 인증 정보 (API Explorer와 공유)
|
||||
$input = $this->resolveSessionVariables($input);
|
||||
|
||||
// {{$hmac.xxx}} → HMAC 인증용 동적 값 생성
|
||||
$input = $this->resolveHmac($input);
|
||||
@@ -493,4 +490,109 @@ public function getContext(): array
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 세션 변수 처리
|
||||
*
|
||||
* 지원 패턴:
|
||||
* - {{$session.token}} - 세션에 저장된 Bearer 토큰
|
||||
* - {{$session.user_id}} - 세션 사용자 ID
|
||||
* - {{$session.user.email}} - 세션 사용자 이메일
|
||||
* - {{$session.user.name}} - 세션 사용자 이름
|
||||
* - {{$session.tenant_id}} - 세션 사용자의 기본 테넌트 ID
|
||||
*/
|
||||
private function resolveSessionVariables(string $input): string
|
||||
{
|
||||
// 세션 변수가 없으면 조기 반환
|
||||
if (! str_contains($input, '{{$session.')) {
|
||||
return $input;
|
||||
}
|
||||
|
||||
// {{$session.token}} → 세션에 저장된 Bearer 토큰
|
||||
if (str_contains($input, '{{$session.token}}')) {
|
||||
$token = session('api_explorer_token', '');
|
||||
$input = str_replace('{{$session.token}}', $token, $input);
|
||||
}
|
||||
|
||||
// {{$session.user_id}} → 세션 사용자 ID
|
||||
if (str_contains($input, '{{$session.user_id}}')) {
|
||||
$userId = session('api_explorer_user_id', '');
|
||||
$input = str_replace('{{$session.user_id}}', (string) $userId, $input);
|
||||
}
|
||||
|
||||
// 사용자 상세 정보가 필요한 경우 (user.email, user.name, tenant_id)
|
||||
$needsUserDetails = str_contains($input, '{{$session.user.') || str_contains($input, '{{$session.tenant_id}}');
|
||||
|
||||
if ($needsUserDetails) {
|
||||
$userId = session('api_explorer_user_id');
|
||||
$user = $userId ? \App\Models\User::find($userId) : null;
|
||||
|
||||
// {{$session.user.email}}
|
||||
if (str_contains($input, '{{$session.user.email}}')) {
|
||||
$email = $user?->email ?? '';
|
||||
$input = str_replace('{{$session.user.email}}', $email, $input);
|
||||
}
|
||||
|
||||
// {{$session.user.name}}
|
||||
if (str_contains($input, '{{$session.user.name}}')) {
|
||||
$name = $user?->name ?? '';
|
||||
$input = str_replace('{{$session.user.name}}', $name, $input);
|
||||
}
|
||||
|
||||
// {{$session.tenant_id}} → 사용자의 기본 테넌트 ID
|
||||
if (str_contains($input, '{{$session.tenant_id}}')) {
|
||||
$tenantId = '';
|
||||
if ($user) {
|
||||
$userTenant = \Illuminate\Support\Facades\DB::table('user_tenants')
|
||||
->where('user_id', $user->id)
|
||||
->where('is_default', true)
|
||||
->whereNull('deleted_at')
|
||||
->first();
|
||||
$tenantId = $userTenant?->tenant_id ?? '';
|
||||
}
|
||||
$input = str_replace('{{$session.tenant_id}}', (string) $tenantId, $input);
|
||||
}
|
||||
}
|
||||
|
||||
return $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* 세션 인증 정보 조회 (FlowExecutor에서 사용)
|
||||
*
|
||||
* @return array{token: string, user_id: int|null, user: array|null, tenant_id: int|null}
|
||||
*/
|
||||
public function getSessionAuth(): array
|
||||
{
|
||||
$token = session('api_explorer_token', '');
|
||||
$userId = session('api_explorer_user_id');
|
||||
$user = null;
|
||||
$tenantId = null;
|
||||
|
||||
if ($userId) {
|
||||
$userModel = \App\Models\User::find($userId);
|
||||
if ($userModel) {
|
||||
$user = [
|
||||
'id' => $userModel->id,
|
||||
'name' => $userModel->name,
|
||||
'email' => $userModel->email,
|
||||
];
|
||||
|
||||
// 기본 테넌트 조회
|
||||
$userTenant = \Illuminate\Support\Facades\DB::table('user_tenants')
|
||||
->where('user_id', $userModel->id)
|
||||
->where('is_default', true)
|
||||
->whereNull('deleted_at')
|
||||
->first();
|
||||
$tenantId = $userTenant?->tenant_id;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'token' => $token,
|
||||
'user_id' => $userId,
|
||||
'user' => $user,
|
||||
'tenant_id' => $tenantId,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user