Validate host/target fields (hostname or IP; host allows */** wildcards)

Backend (utils/hostname_validate.js, enforced in routes/host.js on create/update):
- host: IPv4 or a wildcard pattern whose labels may be normal, "*" (one
  fragment) or "**" (any depth, incl. a bare "**" catch-all) — matching
  Host.lookUp. Lowered Host.host min length to 1 so "**"/"*" pass the model.
- target (ip): IPv4 or a strict hostname, no wildcards.
- Both reject scheme, "/", ":" and whitespace; 422 with per-field keys.

Frontend (val.js) mirrors the rules: host/target validators + hosts.ejs fields
point at them. Unit tests in test/unit/hostname_validate.test.js.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 11:18:47 -04:00
parent 2acc3644c4
commit 6d8dc45209
8 changed files with 321 additions and 44 deletions
+10
View File
@@ -4,9 +4,17 @@ const router = require('express').Router();
const {Host, Domain} = require('../models').models;
const authz = require('../middleware/authz');
const {normalizeHostFeatures} = require('../utils/host_features');
const {collectHostFieldErrors} = require('../utils/hostname_validate');
const Model = Host;
// Reject a malformed host/target before it reaches the model. Throws a 422
// ObjectValidateError (per-field keys) that the frontend surfaces inline.
function validateHostFields(body){
let errors = collectHostFieldErrors(body);
if(errors.length) throw Model.errors.ObjectValidateError(errors);
}
router.get('/', async function(req, res, next){
try{
let results = await Model[req.query.detail ? "listDetail" : "list"]();
@@ -25,6 +33,7 @@ router.get('/', async function(req, res, next){
router.post('/', authz.requireDomainRole('manager', authz.resolve.hostBody), async function(req, res, next){
try{
req.body.created_by = authz.reqUsername(req);
validateHostFields(req.body);
normalizeHostFeatures(req.body);
let item = await Model.create(req.body);
@@ -89,6 +98,7 @@ router.get('/:item', authz.requireDomainRole('viewer', authz.resolve.hostParam),
router.put('/:item', authz.requireDomainRole('manager', authz.resolve.hostParam), async function(req, res, next){
try{
req.body.updated_by = authz.reqUsername(req);
validateHostFields(req.body);
normalizeHostFeatures(req.body);
let item = await Model.get(req.params.item);
item = await item.update(req.body);