feat(release): prepare 1.1.0 candidate
This commit is contained in:
parent
5a34f66981
commit
5205dcdfa9
736 changed files with 115667 additions and 12203 deletions
|
|
@ -2,7 +2,24 @@
|
|||
// Supabase 연결 정보 SSOT — 데스크톱, 웹, 모바일 모두 여기서 참조.
|
||||
// Anon key는 클라이언트용 공개 키(RLS 보호)이므로 소스에 포함해도 안전.
|
||||
|
||||
interface MobileE2eSupabaseOverride {
|
||||
url?: unknown
|
||||
anonKey?: unknown
|
||||
}
|
||||
|
||||
const runtimeOverride = (
|
||||
globalThis as typeof globalThis & { __D3RO_MOBILE_E2E_SUPABASE__?: MobileE2eSupabaseOverride }
|
||||
).__D3RO_MOBILE_E2E_SUPABASE__
|
||||
const runtimeUrl = runtimeOverride?.url
|
||||
const runtimeAnonKey = runtimeOverride?.anonKey
|
||||
const validRuntimeOverride = runtimeUrl === 'http://10.0.2.2:55321'
|
||||
&& typeof runtimeAnonKey === 'string'
|
||||
&& /^sb_publishable_[A-Za-z0-9_-]{20,}$/.test(runtimeAnonKey)
|
||||
|
||||
export const SUPABASE_LOCAL_MOBILE_E2E = validRuntimeOverride
|
||||
|
||||
export const SUPABASE_URL =
|
||||
(validRuntimeOverride ? runtimeUrl : undefined) ||
|
||||
(typeof process !== 'undefined' &&
|
||||
(process.env?.NEXT_PUBLIC_SUPABASE_URL ||
|
||||
process.env?.SUPABASE_URL ||
|
||||
|
|
@ -10,6 +27,7 @@ export const SUPABASE_URL =
|
|||
'https://llnocwyqvhgwpdjcqqyw.supabase.co'
|
||||
|
||||
export const SUPABASE_ANON_KEY =
|
||||
(validRuntimeOverride ? runtimeAnonKey : undefined) ||
|
||||
(typeof process !== 'undefined' &&
|
||||
(process.env?.NEXT_PUBLIC_SUPABASE_ANON_KEY ||
|
||||
process.env?.SUPABASE_ANON_KEY ||
|
||||
|
|
|
|||
|
|
@ -1712,7 +1712,8 @@ export interface AdMediationAuctionRequest {
|
|||
}
|
||||
|
||||
export interface AdMediationAuctionResult {
|
||||
winner: AdCreativePayload
|
||||
/** Null means every configured provider failed or returned no fill. */
|
||||
winner: AdCreativePayload | null
|
||||
winningBidEcpm: number
|
||||
participatingBids: Array<{
|
||||
networkId: AdNetworkId | string
|
||||
|
|
@ -1878,23 +1879,27 @@ export interface RefundEligibilityResult {
|
|||
// Phase 18: Multi-PG Billing & Checkout
|
||||
// ============================================================
|
||||
|
||||
export type PaymentGatewayProvider = 'toss' | 'stripe' | 'portone'
|
||||
export type PaymentGatewayProvider = 'stripe'
|
||||
export type CheckoutTier = 'pro' | 'pro_plus'
|
||||
|
||||
export interface CheckoutSessionParams {
|
||||
tier: LicenseTier
|
||||
billingCycle: 'monthly' | 'annual'
|
||||
currency: 'KRW' | 'USD' | 'EUR'
|
||||
tier: CheckoutTier
|
||||
provider: PaymentGatewayProvider
|
||||
taxId?: string
|
||||
customerEmail?: string
|
||||
}
|
||||
|
||||
export interface CheckoutSessionResult {
|
||||
checkoutUrl?: string
|
||||
clientSecret?: string
|
||||
orderId: string
|
||||
amount: number
|
||||
currency: string
|
||||
status: 'pending' | 'completed' | 'failed'
|
||||
checkoutUrl: string
|
||||
provider: 'stripe'
|
||||
status: 'pending'
|
||||
}
|
||||
|
||||
export interface VerifyPaymentResult {
|
||||
success: boolean
|
||||
activeTier: 'free' | CheckoutTier
|
||||
}
|
||||
|
||||
export interface SubscriptionStatusResult {
|
||||
tier: 'free' | CheckoutTier
|
||||
valid: boolean
|
||||
expiresAt: number | null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,16 +5,16 @@
|
|||
import { generateKeyPairSync, sign, verify, createPrivateKey, createPublicKey } from 'crypto'
|
||||
import type { LicenseTier, Feature } from '../types'
|
||||
|
||||
/** 기본 내장 Ed25519 공개키 (SPKI PEM 형식) */
|
||||
export const DEFAULT_LICENSE_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
||||
/**
|
||||
* 공개된 개발 전용 Ed25519 공개키 (SPKI PEM 형식).
|
||||
*
|
||||
* 대응 비밀키가 과거 저장소에 포함됐으므로 운영 라이선스 검증에는 절대 사용할 수
|
||||
* 없다. 운영 호출자는 별도로 보관한 키쌍의 공개키를 반드시 명시해야 한다.
|
||||
*/
|
||||
export const DEVELOPMENT_LICENSE_PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
|
||||
MCowBQYDK2VwAyEA45oxl+jQCX6kR8C582mn9B/qBaX8pvWrsZSXKolM8B4=
|
||||
-----END PUBLIC KEY-----`
|
||||
|
||||
/** 기본 내장 Ed25519 비밀키 (PKCS8 PEM 형식 — 개발/어드민 발급용) */
|
||||
export const DEFAULT_LICENSE_PRIVATE_KEY = `-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEILnj6K9ZyiJIXTvXwJ8gEow9nkcUmRqdeTp5yurw9CGG
|
||||
-----END PRIVATE KEY-----`
|
||||
|
||||
/** 라이센스 서명 페이로드 */
|
||||
export interface SignedLicensePayload {
|
||||
/** 라이센스 고유 ID */
|
||||
|
|
@ -55,6 +55,29 @@ export interface LicenseVerificationResult {
|
|||
message: string
|
||||
}
|
||||
|
||||
export interface LicenseVerificationOptions {
|
||||
/** 테스트/개발 fixture 키 허용. production 환경에서는 true여도 무시한다. */
|
||||
allowDevelopmentKeys?: boolean
|
||||
/** 호출 환경을 명시한다. 생략하면 NODE_ENV를 사용한다. */
|
||||
environment?: string
|
||||
}
|
||||
|
||||
const DEVELOPMENT_LICENSES: ReadonlyMap<string, { tier: LicenseTier; licenseId: string; message: string }> = new Map([
|
||||
['D3RO-PRO-TEST-KEY1', { tier: 'pro', licenseId: 'dev-pro', message: 'Dev Pro License Activated' }],
|
||||
['D3RO-PLUS-TEST-KEY1', { tier: 'pro_plus', licenseId: 'dev-pro-plus', message: 'Dev Pro+ License Activated' }],
|
||||
['D3RO-TEAM-TEST-KEY1', { tier: 'team', licenseId: 'dev-team', message: 'Dev Team License Activated' }],
|
||||
])
|
||||
|
||||
function allowsDevelopmentLicenses(options: LicenseVerificationOptions | undefined): boolean {
|
||||
if (options?.allowDevelopmentKeys !== true) return false
|
||||
const environment = options.environment ?? process.env.NODE_ENV
|
||||
return environment === 'development' || environment === 'test'
|
||||
}
|
||||
|
||||
function normalizePublicKeyPem(value: string): string {
|
||||
return value.trim().replace(/\r\n/g, '\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* 라이센스 페이로드를 정규화된 JSON 문자열로 변환 (서명 일관성 보장)
|
||||
*/
|
||||
|
|
@ -118,59 +141,28 @@ export function issueSignedLicenseKey(payload: SignedLicensePayload, privateKeyP
|
|||
export function verifySignedLicenseKey(
|
||||
licenseKey: string,
|
||||
currentMachineId?: string,
|
||||
publicKeyPem: string = DEFAULT_LICENSE_PUBLIC_KEY,
|
||||
publicKeyPem?: string,
|
||||
options?: LicenseVerificationOptions,
|
||||
): LicenseVerificationResult {
|
||||
const trimmed = licenseKey.trim()
|
||||
const developmentLicensesAllowed = allowsDevelopmentLicenses(options)
|
||||
|
||||
// 1. 레거시 개발자 테스트 키 확인 (하위 호환성)
|
||||
if (trimmed.startsWith('D3RO-PRO-') && trimmed.length >= 14) {
|
||||
// 1. 개발 fixture는 정확히 일치하고 test/development에서 명시적으로 허용된 경우만 인정한다.
|
||||
const developmentLicense = developmentLicensesAllowed ? DEVELOPMENT_LICENSES.get(trimmed) : undefined
|
||||
if (developmentLicense) {
|
||||
return {
|
||||
valid: true,
|
||||
tier: 'pro',
|
||||
tier: developmentLicense.tier,
|
||||
reason: 'dev_key',
|
||||
payload: {
|
||||
licenseId: 'dev-pro',
|
||||
tier: 'pro',
|
||||
licenseId: developmentLicense.licenseId,
|
||||
tier: developmentLicense.tier,
|
||||
customerEmail: 'developer@d3ro.voice',
|
||||
issuedAt: Date.now(),
|
||||
expiresAt: null,
|
||||
machineId: null,
|
||||
},
|
||||
message: 'Dev Pro License Activated',
|
||||
}
|
||||
}
|
||||
|
||||
if (trimmed.startsWith('D3RO-PLUS-') && trimmed.length >= 15) {
|
||||
return {
|
||||
valid: true,
|
||||
tier: 'pro_plus',
|
||||
reason: 'dev_key',
|
||||
payload: {
|
||||
licenseId: 'dev-pro-plus',
|
||||
tier: 'pro_plus',
|
||||
customerEmail: 'developer@d3ro.voice',
|
||||
issuedAt: Date.now(),
|
||||
expiresAt: null,
|
||||
machineId: null,
|
||||
},
|
||||
message: 'Dev Pro+ License Activated',
|
||||
}
|
||||
}
|
||||
|
||||
if (trimmed.startsWith('D3RO-TEAM-') && trimmed.length >= 15) {
|
||||
return {
|
||||
valid: true,
|
||||
tier: 'team',
|
||||
reason: 'dev_key',
|
||||
payload: {
|
||||
licenseId: 'dev-team',
|
||||
tier: 'team',
|
||||
customerEmail: 'developer@d3ro.voice',
|
||||
issuedAt: Date.now(),
|
||||
expiresAt: null,
|
||||
machineId: null,
|
||||
},
|
||||
message: 'Dev Team License Activated',
|
||||
message: developmentLicense.message,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -185,6 +177,20 @@ export function verifySignedLicenseKey(
|
|||
}
|
||||
}
|
||||
|
||||
const configuredPublicKey = publicKeyPem ? normalizePublicKeyPem(publicKeyPem) : undefined
|
||||
const usesDevelopmentPublicKey =
|
||||
!configuredPublicKey || configuredPublicKey === normalizePublicKeyPem(DEVELOPMENT_LICENSE_PUBLIC_KEY)
|
||||
if (usesDevelopmentPublicKey && !developmentLicensesAllowed) {
|
||||
return {
|
||||
valid: false,
|
||||
tier: 'free',
|
||||
reason: 'invalid_signature',
|
||||
payload: null,
|
||||
message: 'Production license verification key is not configured',
|
||||
}
|
||||
}
|
||||
const verificationPublicKey = configuredPublicKey || DEVELOPMENT_LICENSE_PUBLIC_KEY
|
||||
|
||||
try {
|
||||
const rawBase64 = trimmed.replace('D3RO-LIC-', '')
|
||||
const jsonStr = Buffer.from(rawBase64, 'base64url').toString('utf-8')
|
||||
|
|
@ -204,7 +210,7 @@ export function verifySignedLicenseKey(
|
|||
|
||||
// 3. Ed25519 디지털 서명 검증
|
||||
try {
|
||||
const publicKey = createPublicKey(publicKeyPem)
|
||||
const publicKey = createPublicKey(verificationPublicKey)
|
||||
const canonicalData = canonicalizePayload(payload)
|
||||
const dataBuffer = Buffer.from(canonicalData, 'utf-8')
|
||||
const signatureBuffer = Buffer.from(signature, 'base64')
|
||||
|
|
|
|||
|
|
@ -108,8 +108,8 @@ export function markdownToSimpleHtml(md: string): string {
|
|||
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
|
||||
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
|
||||
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
|
||||
.replace(/^\- \[.\] (.+)$/gm, '<li>$1</li>')
|
||||
.replace(/^\- (.+)$/gm, '<li>$1</li>')
|
||||
.replace(/^- \[.\] (.+)$/gm, '<li>$1</li>')
|
||||
.replace(/^- (.+)$/gm, '<li>$1</li>')
|
||||
.replace(/^\* (.+)$/gm, '<li>$1</li>')
|
||||
.replace(/\n{2,}/g, '</p><p>')
|
||||
.replace(/^(?!<[h|l|t|p])/gm, '')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue