mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
Merge branch 'fix/encrypt-free-text-and-local-ai' into release/v1.39.3
# Conflicts: # src/app/api/import/csv/route.ts
This commit is contained in:
commit
66b1aef40c
69 files changed
+3625
-367
No files matched your search
@@ -23,10 +23,11 @@
|
||||
*
|
||||
* ## Why the matchers are what they are
|
||||
*
|
||||
* A grep for `process.env.X` alone is not the read set. Four modules resolve a
|
||||
* name through a helper and index `process.env` dynamically, so their reads
|
||||
* are invisible to the direct matcher — `ALLOW_LOCAL_AI_PRIVATE_HOSTS` and
|
||||
* every `OIDC_*` among them. Each is registered below with its own call-shape
|
||||
* A grep for `process.env.X` alone is not the read set. Three modules resolve
|
||||
* a name through a helper and index `process.env` dynamically, so their reads
|
||||
* are invisible to the direct matcher — every `OIDC_*` among them. (The AI
|
||||
* private-host grants were a fourth until v1.39.3, when they became direct
|
||||
* `process.env` reads.) Each is registered below with its own call-shape
|
||||
* matcher AND its own non-zero assertion, so a refactor that renames the
|
||||
* helper fails this file loudly instead of quietly shrinking the read set.
|
||||
*
|
||||
@@ -79,12 +80,6 @@ const INDIRECT_READERS: ReadonlyArray<{
|
||||
minimum: 1,
|
||||
why: "the retention windows are parsed through a local intEnv() helper",
|
||||
},
|
||||
{
|
||||
file: "lib/ai/local-host-allowlist.ts",
|
||||
pattern: /ENV_VAR\s*=\s*["']([A-Z][A-Z0-9_]*)["']/g,
|
||||
minimum: 1,
|
||||
why: "the name is hoisted into an ENV_VAR constant, then indexed",
|
||||
},
|
||||
{
|
||||
file: "lib/boot/readiness-summary.ts",
|
||||
pattern: /\benv\.([A-Z][A-Z0-9_]*)/g,
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* The native OIDC leg ends at `healthlog://oidc-callback?error=<reason>`, and
|
||||
* the app maps each reason to its own message. The only written contract for
|
||||
* that vocabulary is the login route's 302 description in the OpenAPI
|
||||
* registry (the callback itself is provider-driven and has no entry).
|
||||
*
|
||||
* An app build once matched against hyphenated shorthand from a coordination
|
||||
* note (`identity-conflict`) while the server sent `oidc_identity_conflict`,
|
||||
* so every reason fell through to the generic message. This binds the two
|
||||
* ends that can be read exactly: every reason either route can send on a
|
||||
* redirect a native client may receive is named in that description, and
|
||||
* nothing is named there that no route sends.
|
||||
*
|
||||
* Its limit: it reads string literals passed to the redirect helpers, so a
|
||||
* reason assembled at runtime would slip past it. None is today.
|
||||
*/
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
const ROOT = join(__dirname, "..");
|
||||
const read = (rel: string) => readFileSync(join(ROOT, rel), "utf8");
|
||||
|
||||
/** Literal reasons passed to one of the named redirect helpers. */
|
||||
function reasons(source: string, helpers: string[]): Set<string> {
|
||||
const found = new Set<string>();
|
||||
const pattern = new RegExp(
|
||||
`\\b(?:${helpers.join("|")})\\(\\s*"(oidc_[a-z_]+)"\\s*\\)`,
|
||||
"g",
|
||||
);
|
||||
for (const m of source.matchAll(pattern)) found.add(m[1]);
|
||||
return found;
|
||||
}
|
||||
|
||||
describe("native OIDC error reasons are documented", () => {
|
||||
// The login route answers both legs through `loginError`; the callback's
|
||||
// native-aware helper is `failRedirect`. `errorRedirect` in the callback is
|
||||
// the browser-only branch taken before the state (and so the native flag)
|
||||
// could be read, and never reaches the app.
|
||||
const sent = new Set([
|
||||
...reasons(read("app/api/auth/oidc/login/route.ts"), ["loginError"]),
|
||||
...reasons(read("app/api/auth/oidc/callback/route.ts"), ["failRedirect"]),
|
||||
]);
|
||||
|
||||
// The login route's 302 description is one string literal on one line.
|
||||
const description =
|
||||
read("lib/openapi/routes/auth.ts")
|
||||
.split("\n")
|
||||
.find(
|
||||
(line) =>
|
||||
line.includes("healthlog://oidc-callback?error=<reason>") &&
|
||||
line.includes("oidc_invalid_request"),
|
||||
) ?? "";
|
||||
const documented = new Set(
|
||||
[...description.matchAll(/`(oidc_[a-z_]+)`/g)].map((m) => m[1]),
|
||||
);
|
||||
|
||||
it("finds the reasons on both ends", () => {
|
||||
expect(sent.size).toBeGreaterThanOrEqual(8);
|
||||
expect(documented.size).toBeGreaterThanOrEqual(8);
|
||||
});
|
||||
|
||||
it("documents every reason a route sends", () => {
|
||||
expect([...sent].filter((r) => !documented.has(r))).toEqual([]);
|
||||
});
|
||||
|
||||
it("documents no reason that no route sends", () => {
|
||||
expect([...documented].filter((r) => !sent.has(r))).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user