feat(release): prepare 1.1.0 candidate

This commit is contained in:
Yun Chan 2026-08-29 18:33:45 +09:00
parent 5a34f66981
commit 5205dcdfa9
736 changed files with 115667 additions and 12203 deletions

View file

@ -1,5 +1,5 @@
# apps/admin/Dockerfile
FROM node:24-alpine AS base
FROM node:24.19.0-alpine AS base
FROM base AS builder
WORKDIR /app

View file

@ -1,6 +1,6 @@
{
"name": "@d3ro/admin",
"version": "1.0.0",
"version": "1.1.0",
"private": true,
"description": "D3RO Voice Admin CRM — SaaS 관리 도구",
"scripts": {

View file

@ -2,7 +2,6 @@
// D3RO Voice Admin CRM — Release & Distribution Hub (Forgejo live feed)
import { Box, Typography, Button } from '@mui/material'
import Link from 'next/link'
import { DoubleBezelCard, TactileBadge } from '@d3ro/ui/components/ds'
import { C, FONT_SANS, FONT_MONO, panelSx, tableSx, statusBadgeSx } from '@/lib/console-theme'
import { requireManager } from '@/lib/admin-guard'
@ -91,7 +90,6 @@ function HeaderBar({ feedLive, repoHtmlUrl }: { feedLive: boolean; repoHtmlUrl:
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, flexWrap: 'wrap' }}>
<Button
component={Link}
href={`${repoHtmlUrl}/releases`}
target="_blank"
variant="outlined"
@ -109,7 +107,6 @@ function HeaderBar({ feedLive, repoHtmlUrl }: { feedLive: boolean; repoHtmlUrl:
Forgejo Releases
</Button>
<Button
component={Link}
href="https://d3ro.chanpaca.net/download.html"
target="_blank"
variant="outlined"

View file

@ -3,11 +3,7 @@
import { randomUUID } from 'node:crypto'
import { NextRequest, NextResponse } from 'next/server'
import {
issueSignedLicenseKey,
DEFAULT_LICENSE_PRIVATE_KEY,
type SignedLicensePayload
} from '@d3ro/core/utils/crypto-license'
import { issueSignedLicenseKey, type SignedLicensePayload } from '@d3ro/core/utils/crypto-license'
import type { LicenseTier } from '@d3ro/core/types'
import { AdminBackendError, fetchAdminBackend, requireVerifiedBackendSession } from '@/lib/backend-session'
@ -27,20 +23,11 @@ const MAX_DEVICES: Record<LicenseTier, number> = {
enterprise: 999
}
interface SigningKey {
privateKeyPem: string
usedDefaultKey: boolean
}
// 저장소에 포함된 기본 키쌍은 공개된 것이므로 위조 방어력이 없다. 운영 발급은
// ADMIN_LICENSE_PRIVATE_KEY(전용 키 로테이션)로만 하고, 기본 키 사용은 명시적 opt-in.
function resolveSigningKey(): SigningKey | null {
// 공개된 개발 키로의 fallback은 없다. 모든 환경에서 명시적인 전용 서명키가 필요하다.
function resolveSigningKey(): string | null {
const configured = process.env.ADMIN_LICENSE_PRIVATE_KEY?.trim()
if (configured) {
return { privateKeyPem: configured.replace(/\\n/g, '\n'), usedDefaultKey: false }
}
if (process.env.ADMIN_LICENSE_ALLOW_DEFAULT_KEY === 'true') {
return { privateKeyPem: DEFAULT_LICENSE_PRIVATE_KEY, usedDefaultKey: true }
return configured.replace(/\\n/g, '\n')
}
return null
}
@ -119,7 +106,7 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
let licenseKey: string
try {
licenseKey = issueSignedLicenseKey(payload, signingKey.privateKeyPem)
licenseKey = issueSignedLicenseKey(payload, signingKey)
} catch {
return NextResponse.json({ error: 'license_signing_failed' }, { status: 500 })
}
@ -151,7 +138,6 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
licenseKey,
licenseId: payload.licenseId,
expiresAt: payload.expiresAt,
usedDefaultKey: signingKey.usedDefaultKey,
auditRecorded
})
}

View file

@ -45,7 +45,6 @@ export function LicenseIssuerDialog({ open, onClose }: LicenseIssuerDialogProps)
const [machineId, setMachineId] = useState('')
const [teamId, setTeamId] = useState('')
const [generatedKey, setGeneratedKey] = useState<string | null>(null)
const [usedDefaultKey, setUsedDefaultKey] = useState(false)
const [auditRecorded, setAuditRecorded] = useState(true)
const [loading, setLoading] = useState(false)
const [copied, setCopied] = useState(false)
@ -77,7 +76,6 @@ export function LicenseIssuerDialog({ open, onClose }: LicenseIssuerDialogProps)
throw new Error(describeIssueError(data.error))
}
setGeneratedKey(data.licenseKey)
setUsedDefaultKey(data.usedDefaultKey === true)
setAuditRecorded(data.auditRecorded !== false)
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to generate license key')
@ -211,12 +209,6 @@ export function LicenseIssuerDialog({ open, onClose }: LicenseIssuerDialogProps)
{generatedKey && (
<Box sx={{ mt: 1, p: 2, bgcolor: 'rgba(0, 0, 0, 0.4)', borderRadius: '10px', border: `1px solid ${C.borderStrong}` }}>
{usedDefaultKey && (
<Alert severity="warning" sx={{ fontFamily: FONT_MONO, fontSize: '11px', mb: 1.5 }}>
.
ADMIN_LICENSE_PRIVATE_KEY로 .
</Alert>
)}
{!auditRecorded && (
<Alert severity="info" sx={{ fontFamily: FONT_MONO, fontSize: '11px', mb: 1.5 }}>
. .