@channel.io/channel-talk-integration-mcp
Version:
Channel Talk Integration MCP - Installation guides for Channel Talk SDK across multiple platforms
571 lines (461 loc) • 17.6 kB
JavaScript
import { z } from "zod";
export function registerGetMemberHashGuideTool(server) {
server.tool("get_member_hash_guide", `case1: 유저가 채널톡 멤버 해시(보안화 설정)에 대해 알고 싶다고 말하면 이 툴을 호출합니다.
case2: 보안 강화를 위한 멤버 해시 설정 방법을 안내할 때 이 툴을 호출합니다.`, {
language: z
.enum(["ko", "en"])
.optional()
.describe("Language for the guide. Use 'en' for English, defaults to Korean if not specified"),
}, (args) => {
try {
const language = args.language;
const result = language === "en"
? getEnglishMemberHashGuide()
: getKoreanMemberHashGuide();
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to execute get_member_hash_guide: ${message}`);
}
});
console.log("Registered tool: get_member_hash_guide");
}
function getKoreanMemberHashGuide() {
return {
result: `# 채널톡 멤버 해시(Member Hash) 보안 설정 가이드
채널톡의 멤버 해시 기능을 통해 고객 정보를 안전하게 보호하는 방법을 안내합니다.
## 🔐 멤버 해시 기능이란?
멤버 해시(Member Hash)는 고객의 \`memberId\`를 **HMAC-SHA256**을 이용해 암호화하여 적합한 유저의 부트 요청만 허용할 수 있도록 하는 보안 기능입니다.
### ⚠️ 보안 위험성
**예측 가능한 memberId 사용의 위험:**
- 아이디, 이메일과 같은 예측 가능한 값을 \`memberId\`로 사용할 경우
- 제3자가 다른 고객의 \`memberId\`를 쉽게 유추할 수 있음
- 고객의 전화번호, 채팅 기록 등 **민감한 정보 탈취 가능**
- 인증되지 않은 사용자가 다른 고객으로 위장하여 부트 가능
**💡 해결책: 멤버 해시 기능 사용을 강력히 권장**
---
## 🚀 멤버 해시 설정 방법
### 1단계: 시크릿 키 발급
1. **채널 설정 > 보안 및 개발 > 보안** 메뉴로 이동
2. "고객 정보 암호화" 섹션에서 **"신규 발급하기"** 클릭
3. 시크릿 키 발급 완료
### 2단계: 서버에서 멤버 해시 생성
${getServerHashingExamples()}
### 3단계: 클라이언트에서 멤버 해시 사용
${getClientImplementationExamples()}
---
## 🔍 멤버 해시 동작 방식
### 멤버 해시 기능 **활성화** 시:
- **멤버 유저 부트**: \`memberId\`와 \`memberHash\` 모두 필요
- **익명 유저 부트**: \`memberId\`와 \`memberHash\` 값을 비워두고 부트
- 해시 값 검증 실패 시 권한 오류 발생
### 멤버 해시 기능 **비활성화** 시:
- 어떤 검증도 수행하지 않음
- \`memberHash\` 값은 무시됨
---
## 🛠️ 문제 해결 가이드
### 주의사항
1. **설치 완료 후 활성화**: 채널톡 SDK 설치 완료 후 멤버 해시 기능 활성화
2. **시크릿 키 보안**: 서버 측에서만 시크릿 키 사용, 클라이언트 노출 금지
3. **시크릿 키 재발급**: 키 노출 시 즉시 재발급 후 재설정
### 검증 방법
- **채널 설정 > 보안 및 개발 > 고객 정보 암호화**에서 "조회하기" 버튼으로 시크릿 키 확인
- **"해시 검증 테스트하기"**로 멤버 해시 값 검증 가능
- 새로고침 버튼으로 시크릿 키 재발급 가능
### 지원 범위
✅ **지원**: SDK 직접 설치한 자체 개발 사이트, 일부 빌더사(Cafe24, 메이크샵, 워드프레스)
❌ **제한적 지원**: 일반적인 빌더사 (연동 방식 차이로 인한 제약)
---
## 📋 구현 체크리스트
1. **시크릿 키 발급**: 채널 데스크에서 시크릿 키 생성
2. **서버 구현**: 백엔드에서 HMAC-SHA256 해싱 로직 구현
3. **클라이언트 연동**: 프론트엔드에서 해시값 받아서 부트에 사용
4. **테스트**: 해시 검증 테스트로 정상 동작 확인
5. **보안 점검**: 시크릿 키 노출 여부 확인
6. **모니터링**: 권한 오류 발생 여부 모니터링
**📚 참고 문서**: https://developers.channel.io/reference/member-hash-kr`,
};
}
function getEnglishMemberHashGuide() {
return {
result: `# Channel Talk Member Hash Security Setup Guide
This guide explains how to securely protect customer information using Channel Talk's member hash feature.
## 🔐 What is Member Hash?
Member Hash is a security feature that encrypts customer \`memberId\` using **HMAC-SHA256** to allow only legitimate user boot requests.
### ⚠️ Security Risks
**Risks of using predictable memberId:**
- Using predictable values like ID or email as \`memberId\`
- Third parties can easily infer other customers' \`memberId\`
- Possible theft of **sensitive information** like phone numbers and chat history
- Unauthorized users can impersonate other customers and boot
**💡 Solution: Strongly recommend using member hash feature**
---
## 🚀 Member Hash Setup Method
### Step 1: Generate Secret Key
1. Go to **Channel Settings > Security & Development > Security**
2. Click **"Generate New"** in the "Customer Information Encryption" section
3. Secret key generation completed
### Step 2: Generate Member Hash on Server
${getServerHashingExamplesEn()}
### Step 3: Use Member Hash on Client
${getClientImplementationExamplesEn()}
---
## 🔍 Member Hash Operation Method
### When Member Hash is **Enabled**:
- **Member User Boot**: Both \`memberId\` and \`memberHash\` required
- **Anonymous User Boot**: Boot with empty \`memberId\` and \`memberHash\` values
- Permission error occurs when hash validation fails
### When Member Hash is **Disabled**:
- No validation performed
- \`memberHash\` value is ignored
---
## 🛠️ Troubleshooting Guide
### Precautions
1. **Activate After Installation**: Enable member hash feature after Channel Talk SDK installation
2. **Secret Key Security**: Use secret key only on server side, prohibit client exposure
3. **Secret Key Reissuance**: Immediately reissue and reconfigure when key is exposed
### Verification Method
- Check secret key with "View" button in **Channel Settings > Security & Development > Customer Information Encryption**
- Verify member hash value with **"Test Hash Verification"**
- Reissue secret key with refresh button
### Support Range
✅ **Supported**: Self-developed sites with direct SDK installation, some builders (Cafe24, MakeShop, WordPress)
❌ **Limited Support**: General builders (constraints due to integration method differences)
---
## 📋 Implementation Checklist
1. **Generate Secret Key**: Create secret key in Channel Desk
2. **Server Implementation**: Implement HMAC-SHA256 hashing logic in backend
3. **Client Integration**: Use hash value from frontend for boot
4. **Testing**: Verify normal operation with hash verification test
5. **Security Check**: Check for secret key exposure
6. **Monitoring**: Monitor for permission error occurrences
**📚 Reference Documentation**: https://developers.channel.io/reference/member-hash-kr`,
};
}
function getServerHashingExamples() {
return `**🔧 서버 측 해싱 구현 예시:**
### JavaScript (Node.js)
\`\`\`javascript
const crypto = require('crypto');
function generateMemberHash(memberId, secretKey) {
const hash = crypto.createHmac('sha256', Buffer.from(secretKey, 'hex'))
.update(memberId)
.digest('hex');
return hash;
}
// 사용 예시
const memberId = 'user123';
const secretKey = '4629de5def93d6a2abea6afa9bd5476d9c6cbc04223f9a2f7e517b535dde3e25';
const memberHash = generateMemberHash(memberId, secretKey);
\`\`\`
### Python
\`\`\`python
import hmac
import hashlib
import binascii
def generate_member_hash(member_id, secret_key):
hash_value = hmac.new(
binascii.unhexlify(bytearray(secret_key, "utf-8")),
msg=member_id.encode('utf-8'),
digestmod=hashlib.sha256
).hexdigest()
return hash_value
# 사용 예시
member_id = "user123"
secret_key = "4629de5def93d6a2abea6afa9bd5476d9c6cbc04223f9a2f7e517b535dde3e25"
member_hash = generate_member_hash(member_id, secret_key)
\`\`\`
### Java
\`\`\`java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
public static String generateMemberHash(String memberId, String secretKey) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(hexToBytes(secretKey), "HmacSHA256"));
byte[] hash = mac.doFinal(memberId.getBytes());
StringBuilder sb = new StringBuilder(hash.length * 2);
for (byte b: hash) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static byte[] hexToBytes(String hex) {
return DatatypeConverter.parseHexBinary(hex);
}
\`\`\`
### PHP
\`\`\`php
function generateMemberHash($memberId, $secretKey) {
return hash_hmac('sha256', $memberId, pack("H*", $secretKey));
}
// 사용 예시
$memberId = 'user123';
$secretKey = '4629de5def93d6a2abea6afa9bd5476d9c6cbc04223f9a2f7e517b535dde3e25';
$memberHash = generateMemberHash($memberId, $secretKey);
\`\`\``;
}
function getServerHashingExamplesEn() {
return `**🔧 Server-side Hashing Implementation Examples:**
### JavaScript (Node.js)
\`\`\`javascript
const crypto = require('crypto');
function generateMemberHash(memberId, secretKey) {
const hash = crypto.createHmac('sha256', Buffer.from(secretKey, 'hex'))
.update(memberId)
.digest('hex');
return hash;
}
// Usage example
const memberId = 'user123';
const secretKey = '4629de5def93d6a2abea6afa9bd5476d9c6cbc04223f9a2f7e517b535dde3e25';
const memberHash = generateMemberHash(memberId, secretKey);
\`\`\`
### Python
\`\`\`python
import hmac
import hashlib
import binascii
def generate_member_hash(member_id, secret_key):
hash_value = hmac.new(
binascii.unhexlify(bytearray(secret_key, "utf-8")),
msg=member_id.encode('utf-8'),
digestmod=hashlib.sha256
).hexdigest()
return hash_value
# Usage example
member_id = "user123"
secret_key = "4629de5def93d6a2abea6afa9bd5476d9c6cbc04223f9a2f7e517b535dde3e25"
member_hash = generate_member_hash(member_id, secret_key)
\`\`\`
### Java
\`\`\`java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
public static String generateMemberHash(String memberId, String secretKey) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(hexToBytes(secretKey), "HmacSHA256"));
byte[] hash = mac.doFinal(memberId.getBytes());
StringBuilder sb = new StringBuilder(hash.length * 2);
for (byte b: hash) {
sb.append(String.format("%02x", b));
}
return sb.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static byte[] hexToBytes(String hex) {
return DatatypeConverter.parseHexBinary(hex);
}
\`\`\`
### PHP
\`\`\`php
function generateMemberHash($memberId, $secretKey) {
return hash_hmac('sha256', $memberId, pack("H*", $secretKey));
}
// Usage example
$memberId = 'user123';
$secretKey = '4629de5def93d6a2abea6afa9bd5476d9c6cbc04223f9a2f7e517b535dde3e25';
$memberHash = generateMemberHash($memberId, $secretKey);
\`\`\``;
}
function getClientImplementationExamples() {
return `**🌐 클라이언트 구현 예시:**
### 웹 (JavaScript SDK)
\`\`\`javascript
// 서버에서 멤버 해시를 받아와서 사용
async function bootChannelTalkWithHash(userId) {
try {
// 서버 API 호출하여 멤버 해시 획득
const response = await fetch('/api/get-member-hash', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: userId })
});
const { memberHash } = await response.json();
// 멤버 해시와 함께 부트
ChannelIO('boot', {
"pluginKey": "YOUR_PLUGIN_KEY",
"memberId": userId,
"memberHash": memberHash,
"profile": {
"name": "사용자명",
"mobileNumber": "010-1234-5678"
}
});
} catch (error) {
console.error('Channel Talk boot failed:', error);
}
}
\`\`\`
### React 예시
\`\`\`javascript
import { useEffect, useState } from 'react';
import * as ChannelService from '@channel.io/channel-web-sdk-loader';
function useSecureChannelTalk(user) {
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
if (!user) {
// 익명 유저로 부트
ChannelService.shutdown();
ChannelService.boot({
pluginKey: "YOUR_PLUGIN_KEY"
});
return;
}
// 멤버 유저로 부트 (멤버 해시 포함)
const bootWithMemberHash = async () => {
try {
const response = await fetch('/api/get-member-hash', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: user.id })
});
const { memberHash } = await response.json();
ChannelService.shutdown();
ChannelService.boot({
pluginKey: "YOUR_PLUGIN_KEY",
memberId: user.id,
memberHash: memberHash,
profile: {
name: user.name,
mobileNumber: user.phone,
email: user.email
}
});
} catch (error) {
console.error('Failed to boot with member hash:', error);
}
};
bootWithMemberHash();
}, [user]);
}
\`\`\`
### 서버 API 엔드포인트 예시 (Express.js)
\`\`\`javascript
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/api/get-member-hash', (req, res) => {
const { userId } = req.body;
// 환경변수에서 시크릿 키 가져오기 (보안상 중요!)
const secretKey = process.env.CHANNEL_TALK_SECRET_KEY;
if (!secretKey) {
return res.status(500).json({ error: 'Secret key not configured' });
}
// 멤버 해시 생성
const memberHash = crypto.createHmac('sha256', Buffer.from(secretKey, 'hex'))
.update(userId)
.digest('hex');
res.json({ memberHash });
});
\`\`\``;
}
function getClientImplementationExamplesEn() {
return `**🌐 Client Implementation Examples:**
### Web (JavaScript SDK)
\`\`\`javascript
// Get member hash from server and use it
async function bootChannelTalkWithHash(userId) {
try {
// Call server API to get member hash
const response = await fetch('/api/get-member-hash', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: userId })
});
const { memberHash } = await response.json();
// Boot with member hash
ChannelIO('boot', {
"pluginKey": "YOUR_PLUGIN_KEY",
"memberId": userId,
"memberHash": memberHash,
"profile": {
"name": "User Name",
"mobileNumber": "010-1234-5678"
}
});
} catch (error) {
console.error('Channel Talk boot failed:', error);
}
}
\`\`\`
### React Example
\`\`\`javascript
import { useEffect, useState } from 'react';
import * as ChannelService from '@channel.io/channel-web-sdk-loader';
function useSecureChannelTalk(user) {
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
if (!user) {
// Boot as anonymous user
ChannelService.shutdown();
ChannelService.boot({
pluginKey: "YOUR_PLUGIN_KEY"
});
return;
}
// Boot as member user (with member hash)
const bootWithMemberHash = async () => {
try {
const response = await fetch('/api/get-member-hash', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userId: user.id })
});
const { memberHash } = await response.json();
ChannelService.shutdown();
ChannelService.boot({
pluginKey: "YOUR_PLUGIN_KEY",
memberId: user.id,
memberHash: memberHash,
profile: {
name: user.name,
mobileNumber: user.phone,
email: user.email
}
});
} catch (error) {
console.error('Failed to boot with member hash:', error);
}
};
bootWithMemberHash();
}, [user]);
}
\`\`\`
### Server API Endpoint Example (Express.js)
\`\`\`javascript
const express = require('express');
const crypto = require('crypto');
const app = express();
app.post('/api/get-member-hash', (req, res) => {
const { userId } = req.body;
// Get secret key from environment variables (important for security!)
const secretKey = process.env.CHANNEL_TALK_SECRET_KEY;
if (!secretKey) {
return res.status(500).json({ error: 'Secret key not configured' });
}
// Generate member hash
const memberHash = crypto.createHmac('sha256', Buffer.from(secretKey, 'hex'))
.update(userId)
.digest('hex');
res.json({ memberHash });
});
\`\`\``;
}
//# sourceMappingURL=get-member-hash-guide.tool.js.map