60 lines
2.5 KiB
TypeScript
60 lines
2.5 KiB
TypeScript
/**
|
|
* Custom-domain routes: set/get a user's domain, regenerate its nginx config,
|
|
* and admin verification. Real ACME/DNS ownership verification is deferred;
|
|
* the `verified` flag is set by an admin as a placeholder until then.
|
|
* @module dshs/web/routes/domain
|
|
*/
|
|
|
|
import type { FastifyPluginAsync } from 'fastify'
|
|
import { requireAdmin, requireAuth } from '../middleware/authn.js'
|
|
import { isValidDomain, renderServerBlock } from '../../nginx/generate.js'
|
|
|
|
const putSchema = {
|
|
body: {
|
|
type: 'object',
|
|
required: ['domain'],
|
|
additionalProperties: false,
|
|
properties: { domain: { type: 'string', minLength: 1, maxLength: 253 } },
|
|
},
|
|
} as const
|
|
|
|
export const domainRoutes: FastifyPluginAsync = async (app) => {
|
|
app.get('/api/domain', { preHandler: requireAuth }, async (request) => {
|
|
const domain = await app.db.findDomainByUser(request.user!.id)
|
|
if (domain === undefined) return { domain: null }
|
|
return { domain: domain.domain, verified: domain.verified === 1, nginx_config: domain.nginxConfig }
|
|
})
|
|
|
|
app.put('/api/domain', { preHandler: requireAuth, schema: putSchema }, async (request, reply) => {
|
|
const normalized = (request.body as { domain: string }).domain.toLowerCase().trim()
|
|
if (!isValidDomain(normalized)) return reply.code(400).send({ error: 'invalid_domain' })
|
|
const config = renderServerBlock(normalized, request.user!.id, app.config.port)
|
|
await app.db.upsertDomain(request.user!.id, normalized, config)
|
|
return { domain: normalized, verified: false, nginx_config: config }
|
|
})
|
|
|
|
app.post('/api/nginx/regen', { preHandler: requireAuth }, async (request, reply) => {
|
|
const domain = await app.db.findDomainByUser(request.user!.id)
|
|
if (domain === undefined) return reply.code(404).send({ error: 'no_domain' })
|
|
const config = renderServerBlock(domain.domain, request.user!.id, app.config.port)
|
|
await app.db.upsertDomain(request.user!.id, domain.domain, config)
|
|
return { domain: domain.domain, nginx_config: config }
|
|
})
|
|
|
|
app.get('/api/admin/domains', { preHandler: requireAdmin }, async () => ({
|
|
domains: (await app.db.listDomains()).map((d) => ({
|
|
id: d.id,
|
|
userId: d.userId,
|
|
domain: d.domain,
|
|
verified: d.verified === 1,
|
|
})),
|
|
}))
|
|
|
|
app.post('/api/admin/domains/:id/verify', { preHandler: requireAdmin }, async (request, reply) => {
|
|
const { id } = request.params as { id: string }
|
|
if ((await app.db.findDomainById(id)) === undefined) return reply.code(404).send({ error: 'not_found' })
|
|
await app.db.setDomainVerified(id, true)
|
|
return { ok: true }
|
|
})
|
|
}
|