feat: 로또 추천 API, 텔레그램 봇 연동, 관리자 페이지 추가
- 로또 번호 추천 구독자 전용 페이지 (/services/lotto/recommend) - NAS 몬테카를로 API 연동 + 클라이언트 사이드 폴백 - 무료 미리보기 1개 + 구독자용 프리미엄 번호 추천 - 구독 플랜 변경: 골드(900원)/플래티넘(2,900원)/다이아(9,900원) - 텔레그램 봇 연동: 연결/해제, 웹훅, /start 명령 처리 - 마이페이지 텔레그램 연결 UI + 가이드 모달 - 관리자 페이지 (/admin): 대시보드, 회원, 서비스, 문의 관리 - Supabase 마이그레이션: profiles 텔레그램 컬럼, 신규 상품 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
33
lib/admin-auth.ts
Normal file
33
lib/admin-auth.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { createHmac } from 'crypto';
|
||||
|
||||
const TOKEN_TTL = 24 * 60 * 60 * 1000; // 24시간
|
||||
|
||||
export function createAdminToken(): string {
|
||||
const secret = process.env.ADMIN_JWT_SECRET!;
|
||||
const payload = JSON.stringify({ iat: Date.now(), exp: Date.now() + TOKEN_TTL });
|
||||
const encoded = Buffer.from(payload).toString('base64url');
|
||||
const sig = createHmac('sha256', secret).update(encoded).digest('base64url');
|
||||
return `${encoded}.${sig}`;
|
||||
}
|
||||
|
||||
export function verifyAdminTokenNode(token: string): boolean {
|
||||
try {
|
||||
const secret = process.env.ADMIN_JWT_SECRET;
|
||||
if (!secret) return false;
|
||||
const [encoded, sig] = token.split('.');
|
||||
if (!encoded || !sig) return false;
|
||||
const expected = createHmac('sha256', secret).update(encoded).digest('base64url');
|
||||
if (sig !== expected) return false;
|
||||
const { exp } = JSON.parse(Buffer.from(encoded, 'base64url').toString());
|
||||
return Date.now() < exp;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function checkAdminCredentials(id: string, password: string): boolean {
|
||||
const adminId = process.env.ADMIN_ID;
|
||||
const adminPassword = process.env.ADMIN_PASSWORD;
|
||||
if (!adminId || !adminPassword) return false;
|
||||
return id === adminId && password === adminPassword;
|
||||
}
|
||||
Reference in New Issue
Block a user