summaryrefslogtreecommitdiff
path: root/lib/users
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-06-20 11:47:15 +0000
committerjoonhoekim <26rote@gmail.com>2025-06-20 11:47:15 +0000
commitabd9f950bbd95b9ad713a26d3fd8a7e0282b7c51 (patch)
treeaafc71d5ff23962c2d6d5e902c66ee070b7ac068 /lib/users
parent994defd6446ce20c4b4e0d6cc91688b0e64230a4 (diff)
(김준회) SAML 2.0 SSO (Knox Portal) 추가
Diffstat (limited to 'lib/users')
-rw-r--r--lib/users/repository.ts15
-rw-r--r--lib/users/saml-service.ts134
2 files changed, 149 insertions, 0 deletions
diff --git a/lib/users/repository.ts b/lib/users/repository.ts
index 3a404bde..75981b03 100644
--- a/lib/users/repository.ts
+++ b/lib/users/repository.ts
@@ -44,6 +44,21 @@ export const createUser = async (name: string, email: string): Promise<User> =>
return user
};
+// SAML 사용자 생성 (domain과 추가 정보 포함)
+export const createSAMLUser = async (
+ name: string,
+ email: string,
+ domain: 'evcp' | 'partners' = 'evcp',
+): Promise<User> => {
+ const usersRes = await db.insert(users).values({
+ name,
+ email,
+ domain,
+ }).returning();
+ const user = usersRes[0];
+ return user
+};
+
// 사용자 업데이트
export const updateUser = async (id: number, data: Partial<User>): Promise<User | null> => {
const usersRes = await db.update(users).set(data).where(eq(users.id, id)).returning();
diff --git a/lib/users/saml-service.ts b/lib/users/saml-service.ts
new file mode 100644
index 00000000..cd3c604f
--- /dev/null
+++ b/lib/users/saml-service.ts
@@ -0,0 +1,134 @@
+"use server";
+
+import { getUserByEmail, createSAMLUser } from './repository';
+import { User } from '@/db/schema/users';
+import logger from '@/lib/logger';
+
+export interface SAMLUserData {
+ email: string;
+ name: string;
+ companyId?: number;
+ techCompanyId?: number;
+ domain?: string;
+}
+
+/**
+ * SAML JIT (Just-In-Time) 사용자 생성 또는 조회
+ *
+ * @param samlData SAML에서 받은 사용자 데이터
+ * @returns DB에 저장된 사용자 정보
+ */
+export async function getOrCreateSAMLUser(samlData: SAMLUserData): Promise<User | null> {
+ try {
+ logger.info({ email: samlData.email }, 'SAML JIT: Processing user');
+
+ // 1. 기존 사용자 조회
+ const existingUser = await getUserByEmail(samlData.email);
+
+ if (existingUser) {
+ logger.info({
+ userId: existingUser.id,
+ email: existingUser.email
+ }, 'SAML JIT: Existing user found');
+
+ // 기존 사용자가 있으면 그대로 반환
+ return existingUser;
+ }
+
+ // 2. 새 사용자 생성 (JIT)
+ logger.info({
+ email: samlData.email,
+ name: samlData.name
+ }, 'SAML JIT: Creating new user');
+
+ const newUser = await createSAMLUserFromData(samlData);
+
+ logger.info({
+ userId: newUser.id,
+ email: newUser.email
+ }, 'SAML JIT: New user created successfully');
+
+ return newUser;
+
+ } catch (error) {
+ logger.error({
+ error,
+ email: samlData.email
+ }, 'SAML JIT: Failed to get or create user');
+
+ return null;
+ }
+}
+
+/**
+ * SAML 데이터로 새 사용자 생성
+ *
+ * @param samlData SAML 사용자 데이터
+ * @returns 생성된 사용자
+ */
+async function createSAMLUserFromData(samlData: SAMLUserData): Promise<User> {
+ // 1. UTF-8 name 처리
+ let userName = samlData.name || samlData.email.split('@')[0];
+
+ // 길이 제한 확인 (DB varchar(255) 제한)
+ if (userName.length > 255) {
+ userName = userName.substring(0, 252) + '...';
+ }
+
+ // 빈 문자열 방지
+ if (!userName.trim()) {
+ userName = samlData.email.split('@')[0] || 'SAML User';
+ }
+
+ // 2. domain을 evcp로 고정
+ const domain = 'evcp';
+
+ // 3. SAML 사용자 생성 (Repository 함수 재사용하고 싶었으나 domain 정보 입력 불가로 새로운 함수 정의함)
+ const newUser = await createSAMLUser(
+ userName,
+ samlData.email,
+ domain,
+ );
+
+ // 4. 생성된 사용자 검증
+ if (newUser.domain !== 'evcp') {
+ logger.error({
+ userId: newUser.id,
+ expectedDomain: 'evcp',
+ actualDomain: newUser.domain
+ }, 'SAML JIT: Domain mismatch in created user');
+ throw new Error('Failed to create SAML user with correct domain');
+ }
+
+ logger.info({
+ userId: newUser.id,
+ email: newUser.email,
+ name: newUser.name,
+ nameLength: newUser.name.length,
+ domain: newUser.domain,
+ companyId: newUser.companyId,
+ techCompanyId: newUser.techCompanyId
+ }, 'SAML JIT: New user created with UTF-8 name and evcp domain');
+
+ return newUser;
+}
+
+/**
+ * SAML 사용자 데이터 검증
+ *
+ * @param samlData 검증할 SAML 데이터
+ * @returns 유효성 여부
+ */
+export async function validateSAMLUserData(samlData: unknown): Promise<boolean> {
+ return (
+ samlData !== null &&
+ samlData !== undefined &&
+ typeof samlData === 'object' &&
+ 'email' in samlData &&
+ 'name' in samlData &&
+ typeof (samlData as Record<string, unknown>).email === 'string' &&
+ typeof (samlData as Record<string, unknown>).name === 'string' &&
+ (samlData as Record<string, unknown>).email.includes('@') &&
+ (samlData as Record<string, unknown>).name.length > 0
+ );
+} \ No newline at end of file