인증 코드 저장 부분 익명화.

This commit is contained in:
kimyu
2026-09-21 13:03:01 +09:00
parent 347dfc1f4a
commit 9e93c0bafa
7 changed files with 34 additions and 22 deletions

View File

@@ -120,7 +120,8 @@ export async function fetchChatAccessToken(chatChannelId: string): Promise<strin
}
const token = data.content?.accessToken;
if (!token) {
throw new Error(data.message || '채팅 accessToken을 받지 못했습니다.');
// 외부 응답 원문은 로그/화면으로 전달하지 않는다.
throw new Error('채팅 연결 인증 정보를 받지 못했습니다.');
}
return token;
}

View File

@@ -1,4 +1,5 @@
import WebSocket from 'ws';
import { createHash } from 'crypto';
import {
chatServerUrl,
fetchChannelInfo,
@@ -98,7 +99,7 @@ function messageKey(
): string | null {
const id = chat.msgUid ?? chat.messageId ?? null;
if (id != null && String(id).length > 0) {
return `${kind}:${String(id)}`;
return hashMessageKey(`${kind}:${String(id)}`);
}
const time = messageTimeMs(chat);
const profile = parseMaybeJson<{ nickname?: string; userIdHash?: string }>(
@@ -109,9 +110,16 @@ function messageKey(
const msg = String(chat.msg ?? chat.content ?? '');
if (time == null && !who && !msg) return null;
if (kind === 'donation') {
return `donation:${time ?? 0}:${who}:${Number(extras?.payAmount ?? 0)}:${msg}`;
return hashMessageKey(
`donation:${time ?? 0}:${who}:${Number(extras?.payAmount ?? 0)}:${msg}`,
);
}
return `chat:${time ?? 0}:${who}:${msg}`;
return hashMessageKey(`chat:${time ?? 0}:${who}:${msg}`);
}
/** 중복 검사용 키에는 사용자 해시·닉네임·메시지 원문을 보관하지 않는다. */
function hashMessageKey(value: string): string {
return createHash('sha256').update(value).digest('base64url');
}
function rememberMessageKey(handle: SessionHandle, key: string | null): boolean {
@@ -475,11 +483,12 @@ export async function connectRoomChat(roomId: string) {
setRoomMeta(roomId, { channelName: info.channelName, chatStatus: 'connecting' });
notifyRoom(roomId);
await openSocket(roomId, channelId);
} catch (e) {
const message = e instanceof Error ? e.message : '채팅 연결 실패';
} catch {
// 외부 라이브러리 오류에는 URL·토큰·식별자가 섞일 수 있어 원문을 보관하지 않는다.
const message = '채팅 연결에 실패했습니다. 방송 상태를 확인해 주세요.';
setRoomMeta(roomId, { chatStatus: 'error', chatError: message });
notifyRoom(roomId);
throw e instanceof Error ? e : new Error(message);
throw new Error(message);
}
}

View File

@@ -32,11 +32,11 @@ export async function exchangeAuthorizationCode(code: string, state: string) {
tokenType?: string;
expiresIn?: string | number;
};
message?: string;
code?: number;
};
if (!res.ok || !json.content?.accessToken) {
throw new Error(json.message || `토큰 발급 실패 (${res.status})`);
// 외부 API 원문은 계정 정보 등이 섞일 수 있으므로 전달·기록하지 않는다.
throw new Error(`치지직 인증 처리 실패 (${res.status})`);
}
return {
accessToken: json.content.accessToken,
@@ -54,10 +54,9 @@ export async function fetchAuthedUser(accessToken: string) {
});
const json = (await res.json().catch(() => ({}))) as {
content?: { channelId?: string; channelName?: string };
message?: string;
};
if (!res.ok || !json.content?.channelId) {
throw new Error(json.message || `유저 정보 조회 실패 (${res.status})`);
throw new Error(`치지직 사용자 확인 실패 (${res.status})`);
}
return {
channelId: json.content.channelId.toLowerCase(),