summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/api/auth/first-auth/route.ts19
-rw-r--r--app/api/auth/select-sgips-user/route.ts85
-rw-r--r--components/login/login-form.tsx215
-rw-r--r--lib/users/auth/verifyCredentails.ts192
-rw-r--r--lib/users/session/helper.ts65
5 files changed, 460 insertions, 116 deletions
diff --git a/app/api/auth/first-auth/route.ts b/app/api/auth/first-auth/route.ts
index e8d86a02..6952b472 100644
--- a/app/api/auth/first-auth/route.ts
+++ b/app/api/auth/first-auth/route.ts
@@ -17,6 +17,16 @@ interface FirstAuthResponse {
tempAuthKey?: string
userId?: number
email?: string
+ otpUsers?: Array<{
+ id: string
+ name: string
+ vndrcd: string
+ phone: string
+ email: string
+ nation_cd: string
+ userId: number
+ vendorInfo?: any
+ }>
error?: string
errorCode?: string
}
@@ -116,6 +126,15 @@ export async function POST(request: NextRequest): Promise<NextResponse<FirstAuth
}
// 1차 인증 성공 응답
+ // S-GIPS의 경우 otpUsers 배열 반환
+ if (provider === 'sgips' && authResult.otpUsers) {
+ return NextResponse.json({
+ success: true,
+ otpUsers: authResult.otpUsers
+ })
+ }
+
+ // 일반 사용자의 경우 기존 응답
return NextResponse.json({
success: true,
tempAuthKey: authResult.tempAuthKey,
diff --git a/app/api/auth/select-sgips-user/route.ts b/app/api/auth/select-sgips-user/route.ts
new file mode 100644
index 00000000..75c2012f
--- /dev/null
+++ b/app/api/auth/select-sgips-user/route.ts
@@ -0,0 +1,85 @@
+// /api/auth/select-sgips-user/route.ts
+// 선택된 S-GIPS 사용자에 대한 임시 인증 세션 생성 API 엔드포인트
+
+import { authHelpers } from '@/lib/users/session/helper'
+import { NextRequest, NextResponse } from 'next/server'
+
+// 요청 데이터 타입
+interface SelectUserRequest {
+ userId: number
+ email: string
+ name: string
+}
+
+// 응답 데이터 타입
+interface SelectUserResponse {
+ success: boolean
+ tempAuthKey?: string
+ userId?: number
+ email?: string
+ error?: string
+}
+
+export async function POST(request: NextRequest): Promise<NextResponse<SelectUserResponse>> {
+ try {
+ // 요청 데이터 파싱
+ const body: SelectUserRequest = await request.json()
+ const { userId, email, name } = body
+
+ // 입력 검증
+ if (!userId || !email || !name) {
+ return NextResponse.json(
+ {
+ success: false,
+ error: '필수 입력값이 누락되었습니다.'
+ },
+ { status: 400 }
+ )
+ }
+
+ // 선택된 사용자에 대한 임시 인증 세션 생성
+ const result = await authHelpers.createTempAuthForSelectedUser({
+ userId,
+ email,
+ name
+ })
+
+ if (!result.success) {
+ return NextResponse.json(
+ {
+ success: false,
+ error: '임시 인증 세션 생성에 실패했습니다.'
+ },
+ { status: 500 }
+ )
+ }
+
+ // 성공 응답
+ return NextResponse.json({
+ success: true,
+ tempAuthKey: result.tempAuthKey,
+ userId: result.userId,
+ email: result.email
+ })
+
+ } catch (error) {
+ console.error('Select S-GIPS user API error:', error)
+
+ // 에러 응답
+ return NextResponse.json(
+ {
+ success: false,
+ error: '서버 오류가 발생했습니다. 잠시 후 다시 시도해주세요.'
+ },
+ { status: 500 }
+ )
+ }
+}
+
+// GET 요청은 지원하지 않음
+export async function GET() {
+ return NextResponse.json(
+ { error: 'Method not allowed' },
+ { status: 405 }
+ )
+}
diff --git a/components/login/login-form.tsx b/components/login/login-form.tsx
index 8e9509c8..b0a0e574 100644
--- a/components/login/login-form.tsx
+++ b/components/login/login-form.tsx
@@ -24,6 +24,15 @@ import Loading from "../common/loading/loading";
type LoginMethod = 'username' | 'sgips';
+type OtpUser = {
+ name: string;
+ vndrcd: string;
+ phone: string;
+ email: string;
+ nation_cd: string;
+ userId: number; // 백엔드에서 생성된 로컬 DB 사용자 ID
+};
+
export function LoginForm() {
const params = useParams() || {};
const pathname = usePathname() || '';
@@ -44,7 +53,7 @@ export function LoginForm() {
const [showMfaForm, setShowMfaForm] = useState(false);
const [mfaToken, setMfaToken] = useState('');
const [tempAuthKey, setTempAuthKey] = useState('');
- const [mfaUserId, setMfaUserId] = useState(null);
+ const [mfaUserId, setMfaUserId] = useState<number | null>(null);
const [mfaUserEmail, setMfaUserEmail] = useState('');
const [mfaCountdown, setMfaCountdown] = useState(0);
@@ -52,10 +61,15 @@ export function LoginForm() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
- // S-Gips 로그인 폼 데이터
+ // S-Gips 로그인 폼 데이터
const [sgipsUsername, setSgipsUsername] = useState('');
const [sgipsPassword, setSgipsPassword] = useState('');
+ // OTP 사용자 선택 관련 상태
+ const [otpUsers, setOtpUsers] = useState<OtpUser[]>([]);
+ const [showUserSelectionDialog, setShowUserSelectionDialog] = useState(false);
+ const [selectedOtpUser, setSelectedOtpUser] = useState<OtpUser | null>(null);
+
const [isMfaLoading, setIsMfaLoading] = useState(false);
const [isSmsLoading, setIsSmsLoading] = useState(false);
@@ -189,25 +203,40 @@ export function LoginForm() {
}
};
- // SMS 토큰 전송 (userId 파라미터 추가)
+ // SMS 토큰 전송
const handleSendSms = async (userIdParam?: number) => {
const targetUserId = userIdParam || mfaUserId;
if (!targetUserId || mfaCountdown > 0) return;
setIsSmsLoading(true);
try {
+ const requestBody: any = { userId: targetUserId };
+
+ // S-GIPS 사용자인 경우 추가 정보 포함
+ if (selectedOtpUser) {
+ requestBody.phone = selectedOtpUser.phone;
+ requestBody.name = selectedOtpUser.name;
+ }
+
const response = await fetch('/api/auth/send-sms', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ userId: targetUserId }),
+ body: JSON.stringify(requestBody),
});
if (response.ok) {
setMfaCountdown(60);
- toast({
- title: t('smsSent'),
- description: t('smsCodeSent'),
- });
+ if (selectedOtpUser) {
+ toast({
+ title: t('smsSent'),
+ description: t('smsCodeSentTo', { name: selectedOtpUser.name, phone: selectedOtpUser.phone }),
+ });
+ } else {
+ toast({
+ title: t('smsSent'),
+ description: t('smsCodeSent'),
+ });
+ }
} else {
const errorData = await response.json();
toast({
@@ -391,30 +420,24 @@ export function LoginForm() {
const authResult = await performFirstAuth(sgipsUsername, sgipsPassword, 'sgips');
if (authResult.success) {
- toast({
- title: t('sgipsAuthComplete'),
- description: t('proceedingSmsAuth'),
- });
-
- // MFA 화면으로 전환
- setTempAuthKey(authResult.tempAuthKey);
- setMfaUserId(authResult.userId);
- setMfaUserEmail(authResult.email);
- setShowMfaForm(true);
-
- // 자동으로 SMS 전송 (userId 직접 전달)
- setTimeout(() => {
- handleSendSms(authResult.userId);
- }, 500);
+ const users = authResult.otpUsers || [];
+
+ if (users.length === 0) {
+ toast({
+ title: t('errorTitle'),
+ description: t('noUsersFound'),
+ variant: 'destructive',
+ });
+ return;
+ }
- toast({
- title: t('smsAuthStarted'),
- description: t('sendingCodeToSgipsPhone'),
- });
+ // 사용자 선택 다이얼로그 표시 (항상)
+ setOtpUsers(users);
+ setShowUserSelectionDialog(true);
}
} catch (error: unknown) {
console.error('S-Gips login error:', error);
-
+
const errorMessage = getErrorMessage(error as { errorCode?: string; message?: string }, 'sgips');
toast({
@@ -427,6 +450,78 @@ export function LoginForm() {
}
};
+ // 선택된 OTP 사용자와 함께 MFA 진행
+ const proceedWithSelectedUser = async (user: OtpUser, tempAuthKey: string) => {
+ try {
+ // 사용자 정보를 기반으로 MFA 진행
+ setTempAuthKey(tempAuthKey);
+ setSelectedOtpUser(user);
+ setMfaUserId(user.userId); // 선택된 사용자의 userId 설정
+ setMfaUserEmail(user.email);
+ setShowMfaForm(true);
+
+ // 선택된 사용자의 정보를 이용해 SMS 전송 준비
+ // 실제로는 userId가 필요하므로 API에서 받아와야 함
+ // 여기서는 임시로 user 객체를 저장하고 SMS 전송 시 사용
+ setTimeout(() => {
+ // 실제 구현에서는 user 정보를 기반으로 SMS 전송
+ // 현재는 기존 로직 유지하되, 선택된 사용자 정보 활용
+ handleSendSms();
+ }, 500);
+
+ toast({
+ title: t('sgipsAuthComplete'),
+ description: t('sendingCodeToSelectedUser', { name: user.name }),
+ });
+ } catch (error) {
+ console.error('Proceeding with selected user error:', error);
+ toast({
+ title: t('errorTitle'),
+ description: t('mfaSetupError'),
+ variant: 'destructive',
+ });
+ }
+ };
+
+ // OTP 사용자 선택 처리
+ const handleUserSelection = async (user: OtpUser) => {
+ setShowUserSelectionDialog(false);
+
+ try {
+ // 선택된 사용자에 대한 임시 인증 세션 생성 요청
+ const response = await fetch('/api/auth/select-sgips-user', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ userId: user.userId,
+ email: user.email,
+ name: user.name
+ }),
+ });
+
+ const result = await response.json();
+
+ if (!response.ok || !result.success) {
+ toast({
+ title: t('errorTitle'),
+ description: result.error || t('mfaSetupError'),
+ variant: 'destructive',
+ });
+ return;
+ }
+
+ // 임시 인증 세션 생성 성공, MFA 진행
+ await proceedWithSelectedUser(user, result.tempAuthKey);
+ } catch (error) {
+ console.error('User selection error:', error);
+ toast({
+ title: t('errorTitle'),
+ description: t('mfaSetupError'),
+ variant: 'destructive',
+ });
+ }
+ };
+
// MFA 화면에서 뒤로 가기
const handleBackToLogin = () => {
setShowMfaForm(false);
@@ -435,6 +530,9 @@ export function LoginForm() {
setMfaUserId(null);
setMfaUserEmail('');
setMfaCountdown(0);
+ setSelectedOtpUser(null);
+ setShowUserSelectionDialog(false);
+ setOtpUsers([]);
};
// 세션 로딩 중이거나 이미 인증된 상태에서는 로딩 표시
@@ -491,7 +589,10 @@ export function LoginForm() {
</div>
<h1 className="text-2xl font-bold">{t('smsVerification')}</h1>
<p className="text-sm text-muted-foreground mt-2">
- {t('firstAuthCompleteFor', { email: mfaUserEmail })}
+ {selectedOtpUser
+ ? t('firstAuthCompleteForSgips', { name: selectedOtpUser.name, email: mfaUserEmail })
+ : t('firstAuthCompleteFor', { email: mfaUserEmail })
+ }
</p>
<p className="text-xs text-muted-foreground mt-1">
{t('enterSixDigitCodeInstructions')}
@@ -738,6 +839,62 @@ export function LoginForm() {
</div>
)}
+ {/* OTP 사용자 선택 다이얼로그 */}
+ {showUserSelectionDialog && !showMfaForm && (
+ <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
+ <div className="bg-white rounded-lg p-6 w-full max-w-md mx-4 max-h-[80vh] overflow-y-auto">
+ <div className="flex justify-between items-center mb-4">
+ <h3 className="text-lg font-semibold">{t('selectUser')}</h3>
+ <button
+ onClick={() => setShowUserSelectionDialog(false)}
+ className="text-gray-400 hover:text-gray-600"
+ >
+ ✕
+ </button>
+ </div>
+ <div className="space-y-3">
+ <p className="text-sm text-gray-600 mb-4">
+ {t('selectUserDescription')}
+ </p>
+ {otpUsers.map((user, index) => (
+ <div
+ key={index}
+ className="border rounded-lg p-4 hover:bg-gray-50 cursor-pointer"
+ onClick={() => handleUserSelection(user)}
+ >
+ <div className="flex justify-between items-start">
+ <div className="flex-1">
+ <div className="font-medium text-gray-900">{user.name}</div>
+ <div className="text-sm text-gray-600">{user.email}</div>
+ <div className="text-sm text-gray-500">{user.phone}</div>
+ <div className="text-xs text-gray-400 mt-1">Vendor: {user.vndrcd}</div>
+ </div>
+ <Button
+ size="sm"
+ variant="outline"
+ onClick={(e) => {
+ e.stopPropagation();
+ handleUserSelection(user);
+ }}
+ >
+ {t('select')}
+ </Button>
+ </div>
+ </div>
+ ))}
+ </div>
+ <div className="flex justify-end mt-6">
+ <Button
+ variant="outline"
+ onClick={() => setShowUserSelectionDialog(false)}
+ >
+ {t('cancel')}
+ </Button>
+ </div>
+ </div>
+ </div>
+ )}
+
{/* 비밀번호 재설정 다이얼로그 */}
{showForgotPassword && !showMfaForm && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
diff --git a/lib/users/auth/verifyCredentails.ts b/lib/users/auth/verifyCredentails.ts
index b3dcd270..b0cbd6c9 100644
--- a/lib/users/auth/verifyCredentails.ts
+++ b/lib/users/auth/verifyCredentails.ts
@@ -482,14 +482,16 @@ export async function verifySGipsCredentials(
password: string
): Promise<{
success: boolean;
- user?: {
+ otpUsers?: Array<{
id: string;
name: string;
- email: string;
+ vndrcd: string;
phone: string;
- companyId?: number;
+ email: string;
+ nation_cd: string;
+ userId?: number; // 로컬 DB 사용자 ID (없으면 생성)
vendorInfo?: any; // 벤더 추가 정보
- };
+ }>;
error?: string;
}> {
try {
@@ -535,42 +537,104 @@ export async function verifySGipsCredentials(
// 2. S-Gips API 응답 확인
if (data.message === "success" && data.code === "0") {
- // 3. username의 앞 8자리로 vendorCode 추출
- const vendorCode = username.substring(0, 8);
-
- // 4. 데이터베이스에서 벤더 정보 조회
- const vendorInfo = await getVendorByCode(vendorCode);
-
- if (!vendorInfo) {
- return {
- success: false,
- error: 'VENDOR_NOT_FOUND'
- };
+ const otpUsers = data.otpUsers || [];
+
+ if (otpUsers.length === 0) {
+ return { success: false, error: 'NO_USERS_FOUND' };
}
- // 5. 사용자 정보 구성
+ // 3. 각 OTP 사용자에 대해 로컬 DB 사용자 확인/생성
+ const processedOtpUsers = await Promise.all(
+ otpUsers.map(async (otpUser: any) => {
+ try {
+ // email로 기존 사용자 검색
+ const localUser = await db
+ .select()
+ .from(users)
+ .where(eq(users.email, otpUser.email))
+ .limit(1);
+
+ let userId: number;
+
+ if (!localUser[0]) {
+ // 사용자가 없으면 벤더코드로 벤더 정보 조회 후 새 사용자 생성
+ const vendorInfo = await getVendorByCode(otpUser.vndrcd);
+
+ if (!vendorInfo) {
+ console.warn(`벤더를 찾을 수 없음: ${otpUser.vndrcd}`);
+ // 벤더가 없어도 사용자 생성은 시도 (기본 정보로)
+ const newUser = await db
+ .insert(users)
+ .values({
+ name: otpUser.name,
+ email: otpUser.email,
+ phone: otpUser.phone,
+ domain: 'partners',
+ mfaEnabled: true,
+ })
+ .returning();
+
+ userId = newUser[0].id;
+ } else {
+ // 벤더 정보를 바탕으로 사용자 생성
+ const newUser = await db
+ .insert(users)
+ .values({
+ name: otpUser.name,
+ email: otpUser.email,
+ phone: otpUser.phone,
+ companyId: vendorInfo.id,
+ domain: 'partners',
+ mfaEnabled: true,
+ })
+ .returning();
+
+ userId = newUser[0].id;
+ }
+ } else {
+ // 기존 사용자가 있으면 S-GIPS 정보로 전화번호 업데이트
+ await db
+ .update(users)
+ .set({
+ phone: otpUser.phone,
+ name: otpUser.name,
+ })
+ .where(eq(users.id, localUser[0].id));
+
+ userId = localUser[0].id;
+ console.log(`S-GIPS 사용자 정보 업데이트: ${otpUser.email} - phone: ${otpUser.phone}`);
+ }
+
+ return {
+ id: otpUser.vndrcd || username,
+ name: otpUser.name,
+ vndrcd: otpUser.vndrcd,
+ phone: otpUser.phone,
+ email: otpUser.email,
+ nation_cd: otpUser.nation_cd,
+ userId: userId,
+ vendorInfo: otpUser.vndrcd ? await getVendorByCode(otpUser.vndrcd) : null,
+ };
+ } catch (error) {
+ console.error(`OTP 사용자 처리 중 오류: ${otpUser.email}`, error);
+ // 오류가 발생해도 다른 사용자는 처리 계속
+ return {
+ id: otpUser.vndrcd || username,
+ name: otpUser.name,
+ vndrcd: otpUser.vndrcd,
+ phone: otpUser.phone,
+ email: otpUser.email,
+ nation_cd: otpUser.nation_cd,
+ userId: undefined, // 생성 실패
+ vendorInfo: null,
+ };
+ }
+ })
+ );
+
return {
success: true,
- user: {
- id: username, // 또는 vendorInfo.id를 사용
- name: vendorInfo.representativeName || vendorInfo.vendorName,
- email: vendorInfo.representativeEmail || vendorInfo.email || '',
- phone: vendorInfo.representativePhone || vendorInfo.phone || '',
- companyId: vendorInfo.id,
- vendorInfo: {
- vendorName: vendorInfo.vendorName,
- vendorCode: vendorInfo.vendorCode,
- status: vendorInfo.status,
- taxId: vendorInfo.taxId,
- address: vendorInfo.address,
- country: vendorInfo.country,
- website: vendorInfo.website,
- vendorTypeId: vendorInfo.vendorTypeId,
- businessSize: vendorInfo.businessSize,
- creditRating: vendorInfo.creditRating,
- cashFlowRating: vendorInfo.cashFlowRating,
- }
- },
+ otpUsers: processedOtpUsers.filter(user => user.userId !== undefined), // userId가 있는 사용자만 반환
};
}
@@ -589,15 +653,16 @@ export async function authenticateWithSGips(
password: string
): Promise<{
success: boolean;
- user?: {
- id: number;
+ otpUsers?: Array<{
+ id: string;
name: string;
+ vndrcd: string;
+ phone: string;
email: string;
- imageUrl?: string | null;
- companyId?: number | null;
- techCompanyId?: number | null;
- domain?: string | null;
- };
+ nation_cd: string;
+ userId: number;
+ vendorInfo?: any;
+ }>;
requiresMfa: boolean;
mfaToken?: string;
error?: string;
@@ -606,7 +671,7 @@ export async function authenticateWithSGips(
// 1. S-Gips API로 인증
const sgipsResult = await verifySGipsCredentials(username, password);
- if (!sgipsResult.success || !sgipsResult.user) {
+ if (!sgipsResult.success || !sgipsResult.otpUsers || sgipsResult.otpUsers.length === 0) {
return {
success: false,
requiresMfa: false,
@@ -614,45 +679,12 @@ export async function authenticateWithSGips(
};
}
- // 2. 로컬 DB에서 사용자 확인 또는 생성
- let localUser = await db
- .select()
- .from(users)
- .where(eq(users.email, sgipsResult.user.email))
- .limit(1);
-
- if (!localUser[0]) {
- // 사용자가 없으면 새로 생성 (S-Gips 사용자는 자동 생성)
- const newUser = await db
- .insert(users)
- .values({
- name: sgipsResult.user.name,
- email: sgipsResult.user.email,
- phone: sgipsResult.user.phone,
- companyId: sgipsResult.user.companyId,
- domain: 'partners', // S-Gips 사용자는 partners 도메인
- mfaEnabled: true, // S-Gips 사용자는 MFA 필수
- })
- .returning();
-
- localUser = newUser;
- }
-
- const user = localUser[0];
-
+ // 2. verifySGipsCredentials에서 이미 사용자 생성/매핑이 완료되었으므로
+ // otpUsers 배열을 그대로 반환 (임시 인증 세션은 개별 사용자 선택 시 생성)
return {
success: true,
- user: {
- id: user.id,
- name: user.name,
- email: user.email,
- imageUrl: user.imageUrl,
- companyId: user.companyId,
- techCompanyId: user.techCompanyId,
- domain: user.domain,
- },
+ otpUsers: sgipsResult.otpUsers,
requiresMfa: true,
- // mfaToken,
};
} catch (error) {
console.error('S-Gips authentication error:', error);
diff --git a/lib/users/session/helper.ts b/lib/users/session/helper.ts
index f99ca80a..03bfd7bc 100644
--- a/lib/users/session/helper.ts
+++ b/lib/users/session/helper.ts
@@ -6,20 +6,35 @@ export const authHelpers = {
// 1차 인증 검증 및 임시 키 생성 (DB 버전)
async performFirstAuth(username: string, password: string, provider: 'email' | 'sgips') {
console.log('performFirstAuth started:', { username, provider })
-
+
try {
let authResult;
-
+
if (provider === 'sgips') {
authResult = await authenticateWithSGips(username, password)
} else {
authResult = await verifyExternalCredentials(username, password)
}
-
- if (!authResult.success || !authResult.user) {
+
+ if (!authResult.success) {
return { success: false, error: authResult.error || 'INVALID_CREDENTIALS' }
}
-
+
+ // S-GIPS의 경우 otpUsers 배열 반환
+ if (provider === 'sgips' && authResult.otpUsers) {
+ console.log('S-GIPS auth successful with otpUsers:', authResult.otpUsers.length)
+
+ return {
+ success: true,
+ otpUsers: authResult.otpUsers
+ }
+ }
+
+ // 일반 사용자의 경우 기존 로직
+ if (!authResult.user) {
+ return { success: false, error: 'INVALID_CREDENTIALS' }
+ }
+
// DB에 임시 인증 세션 생성
const expiresAt = new Date(Date.now() + (10 * 60 * 1000)) // 10분 후 만료
const tempAuthKey = await SessionRepository.createTempAuthSession({
@@ -28,7 +43,7 @@ export const authHelpers = {
authMethod: provider,
expiresAt
})
-
+
console.log('Temp auth stored in DB:', {
tempAuthKey,
userId: authResult.user.id,
@@ -36,7 +51,7 @@ export const authHelpers = {
authMethod: provider,
expiresAt
})
-
+
return {
success: true,
tempAuthKey,
@@ -57,6 +72,42 @@ export const authHelpers = {
// 임시 인증 정보 삭제 (DB 버전)
async clearTempAuth(tempAuthKey: string) {
await SessionRepository.markTempAuthSessionAsUsed(tempAuthKey)
+ },
+
+ // 선택된 S-GIPS 사용자에 대한 임시 인증 세션 생성
+ async createTempAuthForSelectedUser(selectedUser: {
+ userId: number;
+ email: string;
+ name: string;
+ }) {
+ console.log('Creating temp auth for selected S-GIPS user:', selectedUser)
+
+ try {
+ const expiresAt = new Date(Date.now() + (10 * 60 * 1000)) // 10분 후 만료
+ const tempAuthKey = await SessionRepository.createTempAuthSession({
+ userId: selectedUser.userId,
+ email: selectedUser.email,
+ authMethod: 'sgips',
+ expiresAt
+ })
+
+ console.log('Temp auth created for selected user:', {
+ tempAuthKey,
+ userId: selectedUser.userId,
+ email: selectedUser.email,
+ expiresAt
+ })
+
+ return {
+ success: true,
+ tempAuthKey,
+ userId: selectedUser.userId,
+ email: selectedUser.email
+ }
+ } catch (error) {
+ console.error('Error creating temp auth for selected user:', error)
+ return { success: false, error: 'SYSTEM_ERROR' }
+ }
}
}
\ No newline at end of file