From 1b63ca538ecc196f280672a6106eb7b9a9744bb9 Mon Sep 17 00:00:00 2001 From: Jannis Braun <151788261+TheZwiss@users.noreply.github.com> Date: Sat, 25 Apr 2026 21:14:58 +0200 Subject: [PATCH] feat(federation): normalizeOriginForCompare helper --- .../server/src/utils/federationAuth.test.ts | 30 +++++++++++++++++++ packages/server/src/utils/federationAuth.ts | 28 +++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/packages/server/src/utils/federationAuth.test.ts b/packages/server/src/utils/federationAuth.test.ts index 4b0a55b7..af7151d3 100644 --- a/packages/server/src/utils/federationAuth.test.ts +++ b/packages/server/src/utils/federationAuth.test.ts @@ -78,3 +78,33 @@ describe('verifyPeerSignature', () => { expect(verifyPeerSignature(body, sig, timestamp, nonce, peer)).toBe(false); }); }); + +import { normalizeOriginForCompare } from './federationAuth.js'; + +describe('normalizeOriginForCompare', () => { + it('canonicalizes a bare host', () => { + expect(normalizeOriginForCompare('nova.ddns.net')).toBe('nova.ddns.net'); + }); + it('strips https:// scheme', () => { + expect(normalizeOriginForCompare('https://nova.ddns.net')).toBe('nova.ddns.net'); + }); + it('strips http:// scheme', () => { + expect(normalizeOriginForCompare('http://localhost:3005')).toBe('localhost:3005'); + }); + it('strips trailing slash', () => { + expect(normalizeOriginForCompare('https://nova.ddns.net/')).toBe('nova.ddns.net'); + }); + it('lowercases the host', () => { + expect(normalizeOriginForCompare('HTTPS://Nova.DDNS.net')).toBe('nova.ddns.net'); + }); + it('returns null for null input', () => { + expect(normalizeOriginForCompare(null)).toBeNull(); + }); + it('returns null for empty string', () => { + expect(normalizeOriginForCompare('')).toBeNull(); + }); + it('treats bare and full-URL forms as equal', () => { + expect(normalizeOriginForCompare('nova.ddns.net')) + .toBe(normalizeOriginForCompare('https://nova.ddns.net')); + }); +}); diff --git a/packages/server/src/utils/federationAuth.ts b/packages/server/src/utils/federationAuth.ts index 7e47eb2a..c80d05e1 100644 --- a/packages/server/src/utils/federationAuth.ts +++ b/packages/server/src/utils/federationAuth.ts @@ -182,3 +182,31 @@ export function getOurOrigin(): string { } return `http://localhost:${config.port}`; } + +/** + * Canonicalize a homeInstance / origin value for comparison. + * + * The homeInstance column is stored in two shapes depending on the code path + * that wrote it: + * - `auth.ts` registration writes the bare host the client sent (e.g. `nova.ddns.net`). + * - `resolveOrCreateReplicatedUser` writes the bare host (`extractDomain(...)`). + * - `getOurOrigin()` returns the full URL (`https://nova.ddns.net`). + * + * All federation authority / self-friend comparisons must route through this + * helper to avoid false-fires across the dual storage convention. Returns the + * lowercased host (with optional :port), no scheme, no trailing slash. + * + * NOTE: A federation-wide audit + canonical-storage migration is tracked + * separately. This helper papers over the inconsistency at comparison sites. + */ +export function normalizeOriginForCompare(value: string | null | undefined): string | null { + if (!value) return null; + let s = value.trim(); + if (!s) return null; + // Strip scheme if present + s = s.replace(/^https?:\/\//i, ''); + // Strip trailing slashes + s = s.replace(/\/+$/, ''); + if (!s) return null; + return s.toLowerCase(); +}