fix(apns): add diagnostic dump on PEM parse failure (v1.4.47.3)

v1.4.47.2 normalisation didn't recover the production PEM. Emit a structured diagnostic on the existing parse-failure branch (counts + sha256_prefix, no secret bytes) so the operator can compare the in-container shape against the source .p8.
This commit is contained in:
Marc-André Bombeck authored and Marc-André Bombeck committed 2026-05-23 09:59:21 +02:00
1 parent 0be10edc66
commit d4b8c3bb5a
3 files changed
+47 -3

No files matched your search

+22
View File
@@ -1,5 +1,27 @@
# Changelog
## [1.4.47.3] — 2026-05-22 — APNs PEM diagnostic dump on parse failure
v1.4.47.2 added defensive PEM normalisation but the production env-var was still rejected at `crypto.createPrivateKey` with `error:1E08010C:DECODER routines::unsupported`. The base64 body itself appears to be corrupted somewhere along the Coolify → docker-compose → `process.env` path; normalising the wrapping cannot recover it.
This release adds a one-line diagnostic dump on parse failure so the operator can compare the in-container shape against the source `.p8`:
```
APNs key did not parse as PEM: <err> [diag raw_len=… norm_len=…
escaped_newlines=… real_newlines=… begin_markers=… end_markers=…
base64_chars=… other_chars=… sha256_prefix=…]
```
No secret bytes leave the warning — only character-class counts, structural marker counts, and a 16-char SHA-256 prefix that the operator can compare against `shasum -a 256 AuthKey_*.p8` locally.
### Changed
- `src/lib/notifications/senders/apns.ts:loadApnsConfig` — on the existing `crypto.createPrivateKey` failure branch, emit a structured diagnostic warning. Channel disable behaviour unchanged.
### Risk
Zero. Behaviour-preserving — only the warning message gets richer when the parse already fails.
## [1.4.47.2] — 2026-05-22 — APNs JWT signing repair (defensive PEM normalisation)
Same-day follow-up to v1.4.47.1. Coolify runtime logs surfaced
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "healthlog",
"version": "1.4.47.2",
"version": "1.4.47.3",
"description": "Self-hosted personal-health-tracking PWA with Withings integration, AI insights, and doctor-report PDF export.",
"license": "AGPL-3.0-only",
"homepage": "https://healthlog.dev",
+24 -2
View File
@@ -21,7 +21,7 @@
* separately — that's what the library is for, and pinning a single
* token to the process lifetime would expire ~50 minutes after boot.
*/
import { createPrivateKey } from "node:crypto";
import { createHash, createPrivateKey } from "node:crypto";
import { readFileSync } from "node:fs";
import apn from "@parse/node-apn";
@@ -219,7 +219,29 @@ export function loadApnsConfig(): ApnsConfig | null {
}
} catch (err) {
const message = err instanceof Error ? err.message : "parse_failed";
getEvent()?.addWarning(`APNs key did not parse as PEM: ${message}`);
// v1.4.47.3 — diagnostic dump on parse failure so operators can
// tell whether the env-var arrived in the expected shape.
// Logs nothing sensitive: only lengths, character-class counts,
// and a SHA-256 prefix of the normalised body. Never the key bytes.
const sha256 = createHash("sha256").update(signingKey).digest("hex");
const escapedNewlineCount = (inlineKey.match(/\\n/g) || []).length;
const realNewlineCount = (signingKey.match(/\n/g) || []).length;
const beginCount = (signingKey.match(/-----BEGIN/g) || []).length;
const endCount = (signingKey.match(/-----END/g) || []).length;
const base64Chars = (signingKey.match(/[A-Za-z0-9+/=]/g) || []).length;
const otherChars =
signingKey.length -
base64Chars -
realNewlineCount -
(signingKey.match(/[-\s]/g) || []).length;
getEvent()?.addWarning(
`APNs key did not parse as PEM: ${message} ` +
`[diag raw_len=${inlineKey.length} norm_len=${signingKey.length} ` +
`escaped_newlines=${escapedNewlineCount} real_newlines=${realNewlineCount} ` +
`begin_markers=${beginCount} end_markers=${endCount} ` +
`base64_chars=${base64Chars} other_chars=${otherChars} ` +
`sha256_prefix=${sha256.slice(0, 16)}]`,
);
cachedConfig = null;
return null;
}