89 lines
2.6 KiB
TypeScript
89 lines
2.6 KiB
TypeScript
import { getChzzkOAuthConfig } from '@/lib/auth/mode';
|
|
|
|
const OPENAPI = 'https://openapi.chzzk.naver.com';
|
|
|
|
export function buildChzzkLoginUrl(state: string) {
|
|
const { clientId, redirectUri } = getChzzkOAuthConfig();
|
|
const url = new URL('https://chzzk.naver.com/account-interlock');
|
|
url.searchParams.set('clientId', clientId);
|
|
url.searchParams.set('redirectUri', redirectUri);
|
|
url.searchParams.set('state', state);
|
|
return url.toString();
|
|
}
|
|
|
|
export async function exchangeAuthorizationCode(code: string, state: string) {
|
|
const { clientId, clientSecret } = getChzzkOAuthConfig();
|
|
const res = await fetch(`${OPENAPI}/auth/v1/token`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
grantType: 'authorization_code',
|
|
clientId,
|
|
clientSecret,
|
|
code,
|
|
state,
|
|
}),
|
|
cache: 'no-store',
|
|
});
|
|
const json = (await res.json().catch(() => ({}))) as {
|
|
content?: {
|
|
accessToken?: string;
|
|
refreshToken?: string;
|
|
tokenType?: string;
|
|
expiresIn?: string | number;
|
|
};
|
|
code?: number;
|
|
};
|
|
if (!res.ok || !json.content?.accessToken) {
|
|
// 외부 API 원문은 계정 정보 등이 섞일 수 있으므로 전달·기록하지 않는다.
|
|
throw new Error(`치지직 인증 처리 실패 (${res.status})`);
|
|
}
|
|
return {
|
|
accessToken: json.content.accessToken,
|
|
refreshToken: json.content.refreshToken ?? null,
|
|
};
|
|
}
|
|
|
|
export async function fetchAuthedUser(accessToken: string) {
|
|
const res = await fetch(`${OPENAPI}/open/v1/users/me`, {
|
|
headers: {
|
|
Authorization: `Bearer ${accessToken}`,
|
|
'Content-Type': 'application/json',
|
|
},
|
|
cache: 'no-store',
|
|
});
|
|
const json = (await res.json().catch(() => ({}))) as {
|
|
content?: { channelId?: string; channelName?: string };
|
|
};
|
|
if (!res.ok || !json.content?.channelId) {
|
|
throw new Error(`치지직 사용자 확인 실패 (${res.status})`);
|
|
}
|
|
return {
|
|
channelId: json.content.channelId.toLowerCase(),
|
|
channelName: json.content.channelName ?? null,
|
|
};
|
|
}
|
|
|
|
/** 확인 후 치지직 토큰 폐기 (저장하지 않음) */
|
|
export async function revokeToken(
|
|
token: string,
|
|
tokenTypeHint: 'access_token' | 'refresh_token' = 'access_token',
|
|
) {
|
|
const { clientId, clientSecret } = getChzzkOAuthConfig();
|
|
try {
|
|
await fetch(`${OPENAPI}/auth/v1/token/revoke`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
clientId,
|
|
clientSecret,
|
|
token,
|
|
tokenTypeHint,
|
|
}),
|
|
cache: 'no-store',
|
|
});
|
|
} catch {
|
|
// 폐기는 best-effort
|
|
}
|
|
}
|