fix: validation rules

This commit is contained in:
Orion Kindel 2023-06-11 21:12:09 -05:00
parent 95fbc5167a
commit 4b9a7613fa
Signed by untrusted user who does not match committer: orion
GPG Key ID: 6D4165AE4C928719
2 changed files with 65 additions and 25 deletions

View File

@ -7,8 +7,8 @@
persist: ['data']
network:
# valid values for interface: ['public', 'local']
interface: ''
port: 5432
interface: 'public'
port: 1
domain: 'db.foo.org'
ssl: false
pg_admin:
@ -20,8 +20,8 @@
allowed_ssh_public_keys: ['ssh-ed25519 <snip> my special ssh key']
persist: []
network:
interface: ''
port: 5432
interface: 'public'
port: 2
domain: 'api.foo.org'
ssl: true
- ui:
@ -30,7 +30,7 @@
allowed_ssh_public_keys: ['ssh-ed25519 <snip> my special ssh key']
persist: []
network:
interface: ''
port: 5432
interface: 'public'
port: 3
domain: 'foo.org'
ssl: true

View File

@ -8,8 +8,10 @@ const parseSegmentLinuxUser = (o) =>
"linux_user" in o
? "username" in o.linux_user
? Either.right({
username: o.linux_user.username,
allowedSshPublicKeys: o.allowed_ssh_public_keys || [],
username: o.linux_user.username.trim(),
allowedSshPublicKeys: (o.allowed_ssh_public_keys || []).map(
(pubkey) => pubkey.trim()
),
})
: Either.left(new Error("linux_user.username required"))
: Either.left(new Error("linux_user required"));
@ -45,7 +47,10 @@ const parseSegmentPgAdmin = flow(
)
)
),
Either.map((pg) => ({ username: pg.username, password: pg.password }))
Either.map((pg) => ({
username: pg.username.trim(),
password: pg.password.trim(),
}))
);
const parseSegmentNetwork = flow(
@ -68,39 +73,56 @@ const parseSegmentNetwork = flow(
)
),
Either.flatMap((n) =>
"port" in n
? Either.right(n)
: Either.left(
new Error(
["network.port required", `in: ${JSON.stringify(n)}`].join("\n")
)
)
),
Either.flatMap((n) =>
"domain" in n
"port" in n && typeof n.port === "number" && Number.isInteger(n.port)
? Either.right(n)
: Either.left(
new Error(
[
"network.domain required",
" (to not listen on a public domain, set this property to empty string '')",
"network.port required and must be an integer",
`in: ${JSON.stringify(n)}`,
].join("\n")
)
)
),
Either.flatMap((n) =>
"ssl" in n
n.interface === 'public' || (n.interface === "local" && !"domain" in n)
? Either.right(n)
: Either.left(
new Error(
["network.ssl required", `in: ${JSON.stringify(n)}`].join("\n")
[
"network.interface is 'local', network.domain must not be present.",
`in: ${JSON.stringify(n)}`,
].join("\n")
)
)
),
Either.flatMap((n) =>
n.interface === 'public' && "domain" in n && typeof n.domain === "string" && n.domain.length > 0
? Either.right(n)
: Either.left(
new Error(
[
"network.interface is 'public', network.domain be set to a value.",
`in: ${JSON.stringify(n)}`,
].join("\n")
)
)
),
Either.flatMap((n) =>
"ssl" in n && typeof n.ssl === "boolean"
? Either.right(n)
: Either.left(
new Error(
[
"network.ssl required and must be a bool",
`in: ${JSON.stringify(n)}`,
].join("\n")
)
)
),
Either.map((n) => ({
ssl: n.ssl,
domain: n.domain || undefined,
domain: (n.domain || "").trim(),
interface: n.interface,
port: n.port,
}))
@ -153,13 +175,31 @@ const parseService = (svc) =>
)
);
const ensureUniq = ({getWith, onConflict, isConflict}) => array => pipe(array,
Array_.map(getWith),
Array_.reduce(Either.right([]), (res, cur) =>
Either.flatMap(seen =>
isConflict(seen, cur)
? Either.left(onConflict(cur))
: Either.right([...seen, cur])
)(res)
),
Either.map(() => array)
);
const ensureServicesDoNotOverlap = flow(
ensureUniq({getWith: s => s.linuxUser.username, onConflict: usr => new Error(`linux_user.username must be unique: ${usr}`), isConflict: (seen, cur) => seen.includes(cur)}),
Either.flatMap(ensureUniq({getWith: s => s.network.port, onConflict: port => new Error(`network.port must be unique: ${port}`), isConflict: (seen, cur) => seen.includes(cur)})),
Either.flatMap(ensureUniq({getWith: s => s.network.domain, onConflict: domain => new Error(`network.domain must be unique: ${domain}`), isConflict: (seen, cur) => seen.includes(cur)}))
);
const parse = flow(
(cfg) => Either.tryCatch(() => yaml.parse(cfg), id),
Either.filterOrElse(
(p) => p instanceof Array,
() => new Error("config must have top-level array elements")
),
Either.flatMap((svcs) => Either.sequenceArray(svcs.map(parseService)))
Either.flatMap((svcs) => Either.sequenceArray(svcs.map(parseService))),
Either.tap(ensureServicesDoNotOverlap)
);
module.exports = { parse };