From d8d970f5a14ae1f0db4514bd9ad5657b19436b60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Thu, 6 Aug 2026 23:31:59 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=EC=BD=94=EC=9D=B8=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=9E=90=20=ED=86=A0=ED=81=B0=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 9 +++++++++ .github/workflows/deploy.yml | 8 ++++++++ db/migrations/006_koin_admin_token.sql | 7 +++++++ src/types/index.d.ts | 6 +++++- 4 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 db/migrations/006_koin_admin_token.sql diff --git a/.env.example b/.env.example index d318391..f06ee66 100644 --- a/.env.example +++ b/.env.example @@ -36,6 +36,15 @@ CLARITY_TOKEN= # [선택] Clarity 에러 알림에 표기할 관리자 이름 ADMIN_NAME= +# ── KOIN 관리자 API ────────────────────────────────────── +# [선택] KOIN 관리자 API 로그인 및 액세스 토큰 저장 +KOIN_API_BASE_URL=https://api.stage.koreatech.in +KOIN_ADMIN_EMAIL= +# KOIN API 명세에 따라 SHA-256으로 처리된 비밀번호 +KOIN_ADMIN_PASSWORD= +# 32바이트 암호화 키를 Base64로 인코딩한 값 +KOIN_TOKEN_ENCRYPTION_KEY= + # ── 로컬 모델(RAG, Ollama 사이드카) ────────────────────── # [선택] 기본 http://localhost:11434 (봇과 같은 박스의 Ollama). 보통 비워둠 OLLAMA_BASE_URL= diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d63fe9f..5eccb6a 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -54,6 +54,10 @@ jobs: ADMIN_NAME: ${{ secrets.ADMIN_NAME }} GOOGLE_REDIRECT_URI: ${{ secrets.GOOGLE_REDIRECT_URI }} NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }} + KOIN_API_BASE_URL: ${{ secrets.KOIN_API_BASE_URL }} + KOIN_ADMIN_EMAIL: ${{ secrets.KOIN_ADMIN_EMAIL }} + KOIN_ADMIN_PASSWORD: ${{ secrets.KOIN_ADMIN_PASSWORD }} + KOIN_TOKEN_ENCRYPTION_KEY: ${{ secrets.KOIN_TOKEN_ENCRYPTION_KEY }} run: | cat < .env SLACK_BOT_TOKEN=$SLACK_BOT_TOKEN @@ -76,6 +80,10 @@ jobs: ADMIN_NAME=$ADMIN_NAME GOOGLE_REDIRECT_URI=$GOOGLE_REDIRECT_URI NOTION_TOKEN=$NOTION_TOKEN + KOIN_API_BASE_URL=$KOIN_API_BASE_URL + KOIN_ADMIN_EMAIL=$KOIN_ADMIN_EMAIL + KOIN_ADMIN_PASSWORD=$KOIN_ADMIN_PASSWORD + KOIN_TOKEN_ENCRYPTION_KEY=$KOIN_TOKEN_ENCRYPTION_KEY GDRIVE_FOLDER_IDS=1fMJGkRIuGWBCHnN0pI5Teab9dKtnkw4s,1cxrf10solU3-qPk_1gWmQOSe1KmMM7dA,1XHBKifEhBQdM4DJAOm44YkNxZpMdGAzF,1eKuZ-lzwpxlJu4-9KciF3XYj3GN-lu28,1X2wch3Av6DMYHYufWy6ybol2saRaVdOq,10c1R4a9-HgNrV4t-Vzj6crGhHK1s06HP,1y0yv2oItH3a_Obo5Jvw_ifH3DlbbHbLp,1iEUpsaoquhwCNBLI7hIDznhAuO33YMGo,1dEVWkUaxKpubTdfawzTtE4km-a7LPsRo,1UAAUK7yqHcyCVZI6Xou3u9Eh4YqyAYYc,1XYia9-0r_F-JGU-wLQdmLB8WFllT3ssa,1_HZB6ztDxOloF9Va-Qw2X38iYFiMMPIW,1-aBgwPhmDeSO327VdvR3NFnYqoNu6Anj,1fyrohFc92yjzZ_FuGAs0yssQcxzSgz4t,1J0y8zCdN9-P_ASkZuO3g30_SUKSVRb7r,1MBMf16gw-XPzv9SRrAPq8L-UqWSf7QV9 EOF diff --git a/db/migrations/006_koin_admin_token.sql b/db/migrations/006_koin_admin_token.sql new file mode 100644 index 0000000..54945f4 --- /dev/null +++ b/db/migrations/006_koin_admin_token.sql @@ -0,0 +1,7 @@ +-- 006_koin_admin_token.sql +-- KOIN 관리자 API 액세스 토큰. AES-256-GCM 암호문만 저장하며 최신 1건을 유지한다. + +CREATE TABLE koin_admin_token ( + access_token text NOT NULL, + created_at timestamptz NOT NULL DEFAULT now() +); diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 5fa4953..a6e323f 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -17,6 +17,10 @@ interface ImportMetaEnv { readonly GOOGLE_PROJECT_ID: string; readonly CREDENTIALS_PATH: string; readonly ADMIN_NAME: string; + readonly KOIN_API_BASE_URL?: string; + readonly KOIN_ADMIN_EMAIL?: string; + readonly KOIN_ADMIN_PASSWORD?: string; + readonly KOIN_TOKEN_ENCRYPTION_KEY?: string; readonly OLLAMA_BASE_URL?: string; readonly OLLAMA_GEN_MODEL?: string; readonly NOTION_TOKEN?: string; @@ -24,4 +28,4 @@ interface ImportMetaEnv { } interface ImportMeta { env: ImportMetaEnv; -} \ No newline at end of file +} From 8ef0464561e62bfdf0f62ef146e6f4812bfc7f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Thu, 6 Aug 2026 23:36:10 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=EC=BD=94=EC=9D=B8=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=9E=90=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/koin/adminLogin.ts | 87 +++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/services/koin/adminLogin.ts diff --git a/src/services/koin/adminLogin.ts b/src/services/koin/adminLogin.ts new file mode 100644 index 0000000..4678fe5 --- /dev/null +++ b/src/services/koin/adminLogin.ts @@ -0,0 +1,87 @@ +import { createCipheriv, randomBytes } from "node:crypto"; +import type { Pool } from "pg"; + +const AES_ALGORITHM = "aes-256-gcm"; +const AES_IV_LENGTH = 12; +const ENCRYPTION_VERSION = "v1"; + +interface KoinAdminLoginResponse { + token: string; +} + +function requireEnv(name: string, value: string | undefined): string { + if (!value) { + throw new Error(`${name} 환경변수가 설정되지 않았습니다.`); + } + return value; +} + +function encryptAccessToken(accessToken: string, encodedKey: string): string { + const key = Buffer.from(encodedKey, "base64"); + if (key.length !== 32) { + throw new Error("KOIN_TOKEN_ENCRYPTION_KEY는 Base64로 인코딩한 32바이트 키여야 합니다."); + } + + const iv = randomBytes(AES_IV_LENGTH); + const cipher = createCipheriv(AES_ALGORITHM, key, iv); + const ciphertext = Buffer.concat([ + cipher.update(accessToken, "utf8"), + cipher.final(), + ]); + const authTag = cipher.getAuthTag(); + + return [ + ENCRYPTION_VERSION, + iv.toString("base64"), + authTag.toString("base64"), + ciphertext.toString("base64"), + ].join("."); +} + +async function replaceAccessToken(pool: Pool, encryptedAccessToken: string): Promise { + const client = await pool.connect(); + try { + await client.query("BEGIN"); + await client.query("LOCK TABLE koin_admin_token IN EXCLUSIVE MODE"); + await client.query("DELETE FROM koin_admin_token"); + await client.query( + "INSERT INTO koin_admin_token(access_token) VALUES($1)", + [encryptedAccessToken], + ); + await client.query("COMMIT"); + } catch (error) { + await client.query("ROLLBACK"); + throw error; + } finally { + client.release(); + } +} + +export async function loginKoinAdmin(pool: Pool): Promise { + const baseURL = requireEnv("KOIN_API_BASE_URL", import.meta.env.KOIN_API_BASE_URL); + const email = requireEnv("KOIN_ADMIN_EMAIL", import.meta.env.KOIN_ADMIN_EMAIL); + const password = requireEnv("KOIN_ADMIN_PASSWORD", import.meta.env.KOIN_ADMIN_PASSWORD); + const encryptionKey = requireEnv( + "KOIN_TOKEN_ENCRYPTION_KEY", + import.meta.env.KOIN_TOKEN_ENCRYPTION_KEY, + ); + + const response = await $fetch("admin/user/login", { + baseURL, + method: "POST", + body: { + email, + password, + }, + timeout: 10_000, + }); + + if (typeof response.token !== "string" || response.token.length === 0) { + throw new Error("KOIN 관리자 로그인 응답에 액세스 토큰이 없습니다."); + } + + const encryptedAccessToken = encryptAccessToken(response.token, encryptionKey); + await replaceAccessToken(pool, encryptedAccessToken); + + return response.token; +} From d5a90be5d16c2a61ee1ac3f4da6205be7f05ea3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Thu, 6 Aug 2026 23:39:33 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20=EC=BD=94=EC=9D=B8=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=9E=90=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=ED=95=84=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 2 +- src/types/index.d.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.env.example b/.env.example index f06ee66..affd0ee 100644 --- a/.env.example +++ b/.env.example @@ -37,7 +37,7 @@ CLARITY_TOKEN= ADMIN_NAME= # ── KOIN 관리자 API ────────────────────────────────────── -# [선택] KOIN 관리자 API 로그인 및 액세스 토큰 저장 +# [필수] KOIN 관리자 API 로그인 및 액세스 토큰 저장 KOIN_API_BASE_URL=https://api.stage.koreatech.in KOIN_ADMIN_EMAIL= # KOIN API 명세에 따라 SHA-256으로 처리된 비밀번호 diff --git a/src/types/index.d.ts b/src/types/index.d.ts index a6e323f..0f4596c 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -17,10 +17,10 @@ interface ImportMetaEnv { readonly GOOGLE_PROJECT_ID: string; readonly CREDENTIALS_PATH: string; readonly ADMIN_NAME: string; - readonly KOIN_API_BASE_URL?: string; - readonly KOIN_ADMIN_EMAIL?: string; - readonly KOIN_ADMIN_PASSWORD?: string; - readonly KOIN_TOKEN_ENCRYPTION_KEY?: string; + readonly KOIN_API_BASE_URL: string; + readonly KOIN_ADMIN_EMAIL: string; + readonly KOIN_ADMIN_PASSWORD: string; + readonly KOIN_TOKEN_ENCRYPTION_KEY: string; readonly OLLAMA_BASE_URL?: string; readonly OLLAMA_GEN_MODEL?: string; readonly NOTION_TOKEN?: string; From c77b08a80bdaf4c272e5290f109bd5fa498fa422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Thu, 6 Aug 2026 23:41:24 +0900 Subject: [PATCH 4/7] =?UTF-8?q?refactor:=20=EC=BD=94=EC=9D=B8=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=9E=90=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/koin/adminLogin.ts | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/services/koin/adminLogin.ts b/src/services/koin/adminLogin.ts index 4678fe5..2d79452 100644 --- a/src/services/koin/adminLogin.ts +++ b/src/services/koin/adminLogin.ts @@ -9,13 +9,6 @@ interface KoinAdminLoginResponse { token: string; } -function requireEnv(name: string, value: string | undefined): string { - if (!value) { - throw new Error(`${name} 환경변수가 설정되지 않았습니다.`); - } - return value; -} - function encryptAccessToken(accessToken: string, encodedKey: string): string { const key = Buffer.from(encodedKey, "base64"); if (key.length !== 32) { @@ -58,13 +51,10 @@ async function replaceAccessToken(pool: Pool, encryptedAccessToken: string): Pro } export async function loginKoinAdmin(pool: Pool): Promise { - const baseURL = requireEnv("KOIN_API_BASE_URL", import.meta.env.KOIN_API_BASE_URL); - const email = requireEnv("KOIN_ADMIN_EMAIL", import.meta.env.KOIN_ADMIN_EMAIL); - const password = requireEnv("KOIN_ADMIN_PASSWORD", import.meta.env.KOIN_ADMIN_PASSWORD); - const encryptionKey = requireEnv( - "KOIN_TOKEN_ENCRYPTION_KEY", - import.meta.env.KOIN_TOKEN_ENCRYPTION_KEY, - ); + const baseURL = import.meta.env.KOIN_API_BASE_URL; + const email = import.meta.env.KOIN_ADMIN_EMAIL; + const password = import.meta.env.KOIN_ADMIN_PASSWORD; + const encryptionKey = import.meta.env.KOIN_TOKEN_ENCRYPTION_KEY; const response = await $fetch("admin/user/login", { baseURL, From ca36900ba0fe735d2bb9eefad5a47878d58c3431 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Thu, 6 Aug 2026 23:42:58 +0900 Subject: [PATCH 5/7] =?UTF-8?q?refactor:=20=EC=BD=94=EC=9D=B8=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=EC=9E=90=20=ED=86=A0=ED=81=B0=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 4 +- .github/workflows/deploy.yml | 2 - db/migrations/006_koin_admin_token.sql | 7 ---- src/services/koin/adminLogin.ts | 54 +------------------------- src/types/index.d.ts | 1 - 5 files changed, 2 insertions(+), 66 deletions(-) delete mode 100644 db/migrations/006_koin_admin_token.sql diff --git a/.env.example b/.env.example index affd0ee..14d1fd0 100644 --- a/.env.example +++ b/.env.example @@ -37,13 +37,11 @@ CLARITY_TOKEN= ADMIN_NAME= # ── KOIN 관리자 API ────────────────────────────────────── -# [필수] KOIN 관리자 API 로그인 및 액세스 토큰 저장 +# [필수] KOIN 관리자 API 로그인 KOIN_API_BASE_URL=https://api.stage.koreatech.in KOIN_ADMIN_EMAIL= # KOIN API 명세에 따라 SHA-256으로 처리된 비밀번호 KOIN_ADMIN_PASSWORD= -# 32바이트 암호화 키를 Base64로 인코딩한 값 -KOIN_TOKEN_ENCRYPTION_KEY= # ── 로컬 모델(RAG, Ollama 사이드카) ────────────────────── # [선택] 기본 http://localhost:11434 (봇과 같은 박스의 Ollama). 보통 비워둠 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5eccb6a..753d7bd 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -57,7 +57,6 @@ jobs: KOIN_API_BASE_URL: ${{ secrets.KOIN_API_BASE_URL }} KOIN_ADMIN_EMAIL: ${{ secrets.KOIN_ADMIN_EMAIL }} KOIN_ADMIN_PASSWORD: ${{ secrets.KOIN_ADMIN_PASSWORD }} - KOIN_TOKEN_ENCRYPTION_KEY: ${{ secrets.KOIN_TOKEN_ENCRYPTION_KEY }} run: | cat < .env SLACK_BOT_TOKEN=$SLACK_BOT_TOKEN @@ -83,7 +82,6 @@ jobs: KOIN_API_BASE_URL=$KOIN_API_BASE_URL KOIN_ADMIN_EMAIL=$KOIN_ADMIN_EMAIL KOIN_ADMIN_PASSWORD=$KOIN_ADMIN_PASSWORD - KOIN_TOKEN_ENCRYPTION_KEY=$KOIN_TOKEN_ENCRYPTION_KEY GDRIVE_FOLDER_IDS=1fMJGkRIuGWBCHnN0pI5Teab9dKtnkw4s,1cxrf10solU3-qPk_1gWmQOSe1KmMM7dA,1XHBKifEhBQdM4DJAOm44YkNxZpMdGAzF,1eKuZ-lzwpxlJu4-9KciF3XYj3GN-lu28,1X2wch3Av6DMYHYufWy6ybol2saRaVdOq,10c1R4a9-HgNrV4t-Vzj6crGhHK1s06HP,1y0yv2oItH3a_Obo5Jvw_ifH3DlbbHbLp,1iEUpsaoquhwCNBLI7hIDznhAuO33YMGo,1dEVWkUaxKpubTdfawzTtE4km-a7LPsRo,1UAAUK7yqHcyCVZI6Xou3u9Eh4YqyAYYc,1XYia9-0r_F-JGU-wLQdmLB8WFllT3ssa,1_HZB6ztDxOloF9Va-Qw2X38iYFiMMPIW,1-aBgwPhmDeSO327VdvR3NFnYqoNu6Anj,1fyrohFc92yjzZ_FuGAs0yssQcxzSgz4t,1J0y8zCdN9-P_ASkZuO3g30_SUKSVRb7r,1MBMf16gw-XPzv9SRrAPq8L-UqWSf7QV9 EOF diff --git a/db/migrations/006_koin_admin_token.sql b/db/migrations/006_koin_admin_token.sql deleted file mode 100644 index 54945f4..0000000 --- a/db/migrations/006_koin_admin_token.sql +++ /dev/null @@ -1,7 +0,0 @@ --- 006_koin_admin_token.sql --- KOIN 관리자 API 액세스 토큰. AES-256-GCM 암호문만 저장하며 최신 1건을 유지한다. - -CREATE TABLE koin_admin_token ( - access_token text NOT NULL, - created_at timestamptz NOT NULL DEFAULT now() -); diff --git a/src/services/koin/adminLogin.ts b/src/services/koin/adminLogin.ts index 2d79452..06ac9c3 100644 --- a/src/services/koin/adminLogin.ts +++ b/src/services/koin/adminLogin.ts @@ -1,60 +1,11 @@ -import { createCipheriv, randomBytes } from "node:crypto"; -import type { Pool } from "pg"; - -const AES_ALGORITHM = "aes-256-gcm"; -const AES_IV_LENGTH = 12; -const ENCRYPTION_VERSION = "v1"; - interface KoinAdminLoginResponse { token: string; } -function encryptAccessToken(accessToken: string, encodedKey: string): string { - const key = Buffer.from(encodedKey, "base64"); - if (key.length !== 32) { - throw new Error("KOIN_TOKEN_ENCRYPTION_KEY는 Base64로 인코딩한 32바이트 키여야 합니다."); - } - - const iv = randomBytes(AES_IV_LENGTH); - const cipher = createCipheriv(AES_ALGORITHM, key, iv); - const ciphertext = Buffer.concat([ - cipher.update(accessToken, "utf8"), - cipher.final(), - ]); - const authTag = cipher.getAuthTag(); - - return [ - ENCRYPTION_VERSION, - iv.toString("base64"), - authTag.toString("base64"), - ciphertext.toString("base64"), - ].join("."); -} - -async function replaceAccessToken(pool: Pool, encryptedAccessToken: string): Promise { - const client = await pool.connect(); - try { - await client.query("BEGIN"); - await client.query("LOCK TABLE koin_admin_token IN EXCLUSIVE MODE"); - await client.query("DELETE FROM koin_admin_token"); - await client.query( - "INSERT INTO koin_admin_token(access_token) VALUES($1)", - [encryptedAccessToken], - ); - await client.query("COMMIT"); - } catch (error) { - await client.query("ROLLBACK"); - throw error; - } finally { - client.release(); - } -} - -export async function loginKoinAdmin(pool: Pool): Promise { +export async function loginKoinAdmin(): Promise { const baseURL = import.meta.env.KOIN_API_BASE_URL; const email = import.meta.env.KOIN_ADMIN_EMAIL; const password = import.meta.env.KOIN_ADMIN_PASSWORD; - const encryptionKey = import.meta.env.KOIN_TOKEN_ENCRYPTION_KEY; const response = await $fetch("admin/user/login", { baseURL, @@ -70,8 +21,5 @@ export async function loginKoinAdmin(pool: Pool): Promise { throw new Error("KOIN 관리자 로그인 응답에 액세스 토큰이 없습니다."); } - const encryptedAccessToken = encryptAccessToken(response.token, encryptionKey); - await replaceAccessToken(pool, encryptedAccessToken); - return response.token; } diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 0f4596c..e8e7d76 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -20,7 +20,6 @@ interface ImportMetaEnv { readonly KOIN_API_BASE_URL: string; readonly KOIN_ADMIN_EMAIL: string; readonly KOIN_ADMIN_PASSWORD: string; - readonly KOIN_TOKEN_ENCRYPTION_KEY: string; readonly OLLAMA_BASE_URL?: string; readonly OLLAMA_GEN_MODEL?: string; readonly NOTION_TOKEN?: string; From 38d9423c83898cdd21387ca4a37f43bf3d8468a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Thu, 6 Aug 2026 23:45:45 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20=EC=BD=94=EC=9D=B8=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EC=9A=94=EC=B2=AD=20=EC=A0=9C=ED=95=9C=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/koin/adminLogin.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/services/koin/adminLogin.ts b/src/services/koin/adminLogin.ts index 06ac9c3..6528dac 100644 --- a/src/services/koin/adminLogin.ts +++ b/src/services/koin/adminLogin.ts @@ -13,8 +13,7 @@ export async function loginKoinAdmin(): Promise { body: { email, password, - }, - timeout: 10_000, + } }); if (typeof response.token !== "string" || response.token.length === 0) { From 80bb158b6903257da63abf21ea088d40ec9a0417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=8B=A0=EA=B4=80=EA=B7=9C?= Date: Thu, 6 Aug 2026 23:47:22 +0900 Subject: [PATCH 7/7] =?UTF-8?q?chore:=20=EC=BD=94=EC=9D=B8=20API=20?= =?UTF-8?q?=EA=B8=B0=EB=B3=B8=20URL=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 14d1fd0..8e0e81a 100644 --- a/.env.example +++ b/.env.example @@ -38,7 +38,7 @@ ADMIN_NAME= # ── KOIN 관리자 API ────────────────────────────────────── # [필수] KOIN 관리자 API 로그인 -KOIN_API_BASE_URL=https://api.stage.koreatech.in +KOIN_API_BASE_URL= KOIN_ADMIN_EMAIL= # KOIN API 명세에 따라 SHA-256으로 처리된 비밀번호 KOIN_ADMIN_PASSWORD=