refactor: newsletter signup posts no credential (SEC-14-R1 option a)
Rework PR #15 per security review SEC-14-R1: the client no longer carries an API token. js/newsletter-config.js ships only the non-secret endpoint, enforced https-only at config load time via validateEndpoint() (mirroring the protocol allowlist in js/reading-list.js); js/newsletter.js POSTs email-only with no Authorization header. Failure paths keep the single user-safe message that never leaks token, endpoint, status, or raw body; success still shows the confirmation. CI gains a gitleaks step that fails on any secret hit; README documents the server-side token, the residual signup-abuse risk, and the authoritative server-side validation follow-up.
This commit is contained in:
+37
-10
@@ -1,17 +1,44 @@
|
||||
/**
|
||||
* Newsletter signup configuration.
|
||||
*
|
||||
* The blog is a static site with no build step, so the serverless endpoint
|
||||
* and its API token are configured here — edit this file when deploying.
|
||||
*
|
||||
* Security note: a real API token must NEVER be committed to the repository.
|
||||
* The committed default below is intentionally empty; the deployer fills in
|
||||
* the token at deploy time (and only then). The page logic reads the token
|
||||
* from this module and never hardcodes one.
|
||||
* Only the non-secret serverless endpoint URL lives here. The serverless
|
||||
* function authenticates with an API token it reads from platform env/secrets
|
||||
* at deploy time — never from this module and never from any client-served
|
||||
* asset. Do NOT add a token or any other secret to this file: the client must
|
||||
* never carry a credential, and anything committed here is public.
|
||||
*/
|
||||
|
||||
/** The serverless endpoint the signup form POSTs to. */
|
||||
/** True when the value is an absolute URL whose protocol is https:. */
|
||||
export function isHttpsUrl(value) {
|
||||
try {
|
||||
return new URL(String(value)).protocol === "https:";
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforce the https-only rule on a configured endpoint, mirroring the protocol
|
||||
* allowlist pattern in js/reading-list.js (tightened to https). Throws when the
|
||||
* endpoint would silently downgrade submissions to plaintext.
|
||||
*
|
||||
* @param {string} endpoint
|
||||
* @returns {true}
|
||||
*/
|
||||
export function validateEndpoint(endpoint) {
|
||||
if (!isHttpsUrl(endpoint)) {
|
||||
throw new Error("NEWSLETTER_ENDPOINT must be an https:// URL");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The serverless endpoint the signup form POSTs to.
|
||||
*
|
||||
* https-only is asserted here at config load time (and covered by tests), so a
|
||||
* deployer pointing this at an http:// URL fails fast instead of shipping a
|
||||
* downgraded endpoint.
|
||||
*/
|
||||
export const NEWSLETTER_ENDPOINT = "https://example.com/api/newsletter-subscribers";
|
||||
|
||||
/** API token for the endpoint. Empty by default — set at deploy time. */
|
||||
export const NEWSLETTER_API_TOKEN = "";
|
||||
validateEndpoint(NEWSLETTER_ENDPOINT);
|
||||
|
||||
Reference in New Issue
Block a user