diff --git a/src/lib/validations/__tests__/nightscout.test.ts b/src/lib/validations/__tests__/nightscout.test.ts new file mode 100644 index 000000000..6cc5127b8 --- /dev/null +++ b/src/lib/validations/__tests__/nightscout.test.ts @@ -0,0 +1,123 @@ +import { describe, expect, it } from "vitest"; + +import * as nightscoutValidation from "../nightscout"; + +type NightscoutOriginVerdict = { + allowed: boolean; + canonicalOrigin: string | null; + privateOriginApproved: boolean; + reasonCode: "invalid_origin" | "private_origin_not_approved" | null; +}; + +type NightscoutPolicyModule = typeof nightscoutValidation & { + parseNightscoutPrivateOrigins(raw: string | undefined): ReadonlySet; + evaluateNightscoutOrigin( + value: string, + privateOrigins: ReadonlySet, + ): NightscoutOriginVerdict; +}; + +const { parseNightscoutPrivateOrigins, evaluateNightscoutOrigin } = + nightscoutValidation as NightscoutPolicyModule; + +describe("NIGHTSCOUT_PRIVATE_ORIGINS", () => { + it("parses a comma-separated server setting into canonical exact origins", () => { + expect( + parseNightscoutPrivateOrigins( + " http://10.0.0.4:1337, HTTPS://CGM.LAN:443 ,https://[fd00::4]:8443 ", + ), + ).toEqual( + new Set([ + "http://10.0.0.4:1337", + "https://cgm.lan", + "https://[fd00::4]:8443", + ]), + ); + }); + + it("authorizes no private origin when the operator setting is empty", () => { + expect(parseNightscoutPrivateOrigins(undefined)).toEqual(new Set()); + expect(parseNightscoutPrivateOrigins(" , ")).toEqual(new Set()); + + expect( + evaluateNightscoutOrigin( + "http://10.0.0.4:1337", + parseNightscoutPrivateOrigins(""), + ), + ).toEqual({ + allowed: false, + canonicalOrigin: "http://10.0.0.4:1337", + privateOriginApproved: false, + reasonCode: "private_origin_not_approved", + }); + }); + + it.each([ + ["credentials", "http://operator:secret@10.0.0.4:1337"], + ["non-root path", "http://10.0.0.4:1337/api/v1"], + ["query", "http://10.0.0.4:1337?token=secret"], + ["fragment", "http://10.0.0.4:1337#admin"], + ["wrong scheme", "ftp://10.0.0.4:1337"], + ["wildcard", "https://*.cgm.lan"], + ["suffix", ".cgm.lan"], + ["malformed", "not an origin"], + ])("rejects a %s operator trust entry", (_label, value) => { + expect(() => parseNightscoutPrivateOrigins(value)).toThrow(); + }); +}); + +describe("Nightscout exact-origin policy", () => { + const privateOrigins = new Set([ + "http://10.0.0.4:1337", + "https://cgm.lan:8443", + ]); + + it("keeps an ordinary canonical public origin functional", () => { + expect( + evaluateNightscoutOrigin("https://NS.EXAMPLE.COM:443", privateOrigins), + ).toEqual({ + allowed: true, + canonicalOrigin: "https://ns.example.com", + privateOriginApproved: false, + reasonCode: null, + }); + }); + + it("allows only the exact operator-approved private scheme, host, and port", () => { + expect( + evaluateNightscoutOrigin("http://10.0.0.4:1337", privateOrigins), + ).toEqual({ + allowed: true, + canonicalOrigin: "http://10.0.0.4:1337", + privateOriginApproved: true, + reasonCode: null, + }); + + for (const value of [ + "https://10.0.0.4:1337", + "http://10.0.0.4:8080", + "http://10.0.0.40:1337", + "https://sub.cgm.lan:8443", + ]) { + expect(evaluateNightscoutOrigin(value, privateOrigins)).toMatchObject({ + allowed: false, + privateOriginApproved: false, + reasonCode: "private_origin_not_approved", + }); + } + }); + + it.each([ + ["credentials", "http://operator:secret@10.0.0.4:1337"], + ["path", "http://10.0.0.4:1337/api/v1"], + ["query", "http://10.0.0.4:1337?token=secret"], + ["fragment", "http://10.0.0.4:1337#admin"], + ])("rejects a %s even when its bare origin is approved", (_label, value) => { + expect(evaluateNightscoutOrigin(value, privateOrigins)).toEqual({ + allowed: false, + canonicalOrigin: null, + privateOriginApproved: false, + reasonCode: "invalid_origin", + }); + }); +});