74e276af92
- Ports now bind to HERMES_PUBLISHED_BIND_IP (default 127.0.0.1) so NPM on the same host proxies to 127.0.0.1:7843/8645/8646 and direct LAN/internet access is blocked without firewall rules - runHermes: settle promise immediately on timeout (SIGKILL) instead of waiting for close event — prevents hanging when hermes spawns children that keep stdout/stderr open after the parent is killed - Add HERMES_ADMIN_COOKIE_SECURE env var to set Secure flag on admin session cookie when the admin UI is served over HTTPS - Document NPM deployment shapes in README Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
73 lines
2.4 KiB
JavaScript
73 lines
2.4 KiB
JavaScript
"use strict"
|
|
|
|
const test = require("node:test")
|
|
const assert = require("node:assert/strict")
|
|
const {
|
|
adminCredentialsMatch,
|
|
createSessionToken,
|
|
createApiKey,
|
|
hashSecret,
|
|
parseCookies,
|
|
serializeAdminCookie,
|
|
clearAdminCookie,
|
|
} = require("../lib/security.cjs")
|
|
|
|
test("adminCredentialsMatch returns true for correct credentials", (t) => {
|
|
assert.equal(adminCredentialsMatch("admin", "secret-value-123", {
|
|
username: "admin", password: "secret-value-123"
|
|
}), true)
|
|
})
|
|
|
|
test("adminCredentialsMatch returns false for wrong password", (t) => {
|
|
assert.equal(adminCredentialsMatch("admin", "wrong-value-123", {
|
|
username: "admin", password: "secret-value-123"
|
|
}), false)
|
|
})
|
|
|
|
test("adminCredentialsMatch returns false for wrong username", (t) => {
|
|
assert.equal(adminCredentialsMatch("notadmin", "secret-value-123", {
|
|
username: "admin", password: "secret-value-123"
|
|
}), false)
|
|
})
|
|
|
|
test("createApiKey returns plaintext matching hms_ prefix", (t) => {
|
|
const key = createApiKey()
|
|
assert.match(key.plaintext, /^hms_[A-Za-z0-9_-]{40,}$/)
|
|
assert.equal(key.suffix.length, 4)
|
|
assert.equal(key.suffix, key.plaintext.slice(-4))
|
|
})
|
|
|
|
test("createSessionToken returns 64-char hex hash", (t) => {
|
|
const token = createSessionToken()
|
|
assert.equal(token.hash.length, 64)
|
|
assert.match(token.plaintext, /^[A-Za-z0-9_-]+$/)
|
|
})
|
|
|
|
test("hashSecret produces consistent output", (t) => {
|
|
assert.equal(hashSecret("hello"), hashSecret("hello"))
|
|
assert.notEqual(hashSecret("hello"), hashSecret("world"))
|
|
})
|
|
|
|
test("parseCookies parses a multi-value cookie header", (t) => {
|
|
const result = parseCookies("a=1; hermes_admin=abc")
|
|
assert.deepEqual(result, { a: "1", hermes_admin: "abc" })
|
|
})
|
|
|
|
test("parseCookies returns empty object for null/undefined", (t) => {
|
|
assert.deepEqual(parseCookies(null), {})
|
|
assert.deepEqual(parseCookies(undefined), {})
|
|
assert.deepEqual(parseCookies(""), {})
|
|
})
|
|
|
|
test("serializeAdminCookie can mark admin session cookies Secure for HTTPS proxy deployments", (t) => {
|
|
const cookie = serializeAdminCookie("session-value", 3600, { secure: true })
|
|
assert.match(cookie, /; HttpOnly\b/)
|
|
assert.match(cookie, /; SameSite=Lax\b/)
|
|
assert.match(cookie, /; Secure\b/)
|
|
})
|
|
|
|
test("clearAdminCookie can mark cleared admin cookies Secure for HTTPS proxy deployments", (t) => {
|
|
const cookie = clearAdminCookie({ secure: true })
|
|
assert.match(cookie, /; Secure\b/)
|
|
})
|