diff --git a/nixclient/src/pages/SSL.tsx b/nixclient/src/pages/SSL.tsx index 0223c1e..70cc091 100644 --- a/nixclient/src/pages/SSL.tsx +++ b/nixclient/src/pages/SSL.tsx @@ -10,14 +10,14 @@ export default function SSL() { const fetch = async () => { setLoading(true) - try { const r = await api.get('/nixserver/ssl'); setCerts(r.data.data) } + try { const r = await api.get('/nixclient/ssl'); setCerts(r.data.data) } finally { setLoading(false) } } useEffect(() => { fetch() }, []) const renew = async (id: number) => { - try { await api.post(`/nixserver/ssl/${id}/renew`); fetch() } + try { await api.post(`/nixclient/ssl/${id}/renew`); fetch() } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Renewal failed') } } diff --git a/server/src/routes/nixclient/index.ts b/server/src/routes/nixclient/index.ts index d1a7a27..dfcf4d9 100644 --- a/server/src/routes/nixclient/index.ts +++ b/server/src/routes/nixclient/index.ts @@ -6,6 +6,7 @@ import domainRoutes from './domains.js' import ftpRoutes from './ftp.js' import cronRoutes from './cron.js' import statsRoutes from './stats.js' +import sslRoutes from './ssl.js' export default async function nixclientRoutes(fastify: FastifyInstance) { fastify.register(fileRoutes, { prefix: '/files' }) @@ -15,4 +16,5 @@ export default async function nixclientRoutes(fastify: FastifyInstance) { fastify.register(ftpRoutes, { prefix: '/ftp' }) fastify.register(cronRoutes, { prefix: '/cron' }) fastify.register(statsRoutes, { prefix: '/stats' }) + fastify.register(sslRoutes, { prefix: '/ssl' }) } diff --git a/server/src/routes/nixclient/ssl.ts b/server/src/routes/nixclient/ssl.ts new file mode 100644 index 0000000..c86706f --- /dev/null +++ b/server/src/routes/nixclient/ssl.ts @@ -0,0 +1,46 @@ +import type { FastifyInstance } from 'fastify' +import prisma from '../../db/client.js' +import { requireAuth } from '../../middleware/auth.js' +import { exec } from '../../core/exec.js' + +export default async function sslRoutes(fastify: FastifyInstance) { + const preHandler = [requireAuth] + + // GET /api/nixclient/ssl — certs for the authenticated user's account only + fastify.get('/', { preHandler }, async (request, reply) => { + const user = request.user! + const accountId = user.sub + + const certs = await prisma.sslCertificate.findMany({ + where: { accountId }, + orderBy: { createdAt: 'desc' }, + }) + return reply.send({ success: true, data: certs }) + }) + + // POST /api/nixclient/ssl/:id/renew + fastify.post('/:id/renew', { preHandler }, async (request, reply) => { + const { id } = request.params as { id: string } + const user = request.user! + + const cert = await prisma.sslCertificate.findFirst({ + where: { id: parseInt(id, 10), accountId: user.sub }, + }) + if (!cert) return reply.code(404).send({ success: false, error: 'Certificate not found' }) + + const result = await exec('certbot', ['renew', '--cert-name', cert.domain, '--non-interactive']) + if (result.exitCode !== 0) { + return reply.code(500).send({ success: false, error: `Renewal failed: ${result.stderr}` }) + } + + const expiresAt = new Date() + expiresAt.setDate(expiresAt.getDate() + 90) + + const updated = await prisma.sslCertificate.update({ + where: { id: cert.id }, + data: { status: 'active', issuedAt: new Date(), expiresAt }, + }) + + return reply.send({ success: true, data: updated }) + }) +}