feat(shared): centralize origin validation logic and update metadata usage
Move origin extraction to shared utility for consistent validation across server and client modules. Update layout metadata to use the shared origin helper, improving maintainability and deployment flexibility. Add unit tests for layout metadata to ensure correct origin handling.
This commit is contained in:
parent
27267e5ffb
commit
3c08b9e406
4 changed files with 42 additions and 9 deletions
|
|
@ -5,6 +5,7 @@ import { Figtree } from "next/font/google";
|
|||
import { ConsentAwareAnalytics } from "@/components/ConsentAwareAnalytics";
|
||||
import { CookieConsentBanner } from "@/components/CookieConsentBanner";
|
||||
import { getResolvedRuntimeConfig } from "@/lib/server/runtime-config";
|
||||
import { tryGetOrigin } from "@/lib/shared/urls";
|
||||
import pkg from "../../package.json";
|
||||
|
||||
const figtree = Figtree({
|
||||
|
|
@ -34,13 +35,16 @@ export const viewport: Viewport = {
|
|||
viewportFit: "cover",
|
||||
};
|
||||
|
||||
const defaultOrigin = "https://openreader.richardr.dev";
|
||||
const validatedOrigin = tryGetOrigin(process.env.BASE_URL) || defaultOrigin;
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
default: "OpenReader",
|
||||
template: "%s | OpenReader",
|
||||
},
|
||||
manifest: "/manifest.json",
|
||||
metadataBase: new URL(process.env.BASE_URL || "https://openreader.richardr.dev"),
|
||||
metadataBase: new URL(validatedOrigin),
|
||||
verification: {
|
||||
google: "MJXyTudn1kgQF8EtGD-tsnAWev7Iawso9hEvqeGHB3U",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import * as authSchemaSqlite from "@/db/schema_auth_sqlite";
|
|||
import * as authSchemaPostgres from "@/db/schema_auth_postgres";
|
||||
import { hashForLog, serverLogger } from '@/lib/server/logger';
|
||||
import { logDegraded, logServerError } from '@/lib/server/errors/logging';
|
||||
import { tryGetOrigin } from "@/lib/shared/urls";
|
||||
|
||||
// Heavy modules (S3 SDK, blobstore, rate-limiter, claim-data) are loaded
|
||||
// lazily via dynamic import() inside the beforeDelete / onLinkAccount
|
||||
|
|
@ -20,14 +21,6 @@ import { logDegraded, logServerError } from '@/lib/server/errors/logging';
|
|||
|
||||
// ...
|
||||
|
||||
function tryGetOrigin(url: string | undefined): string | null {
|
||||
if (!url) return null;
|
||||
try {
|
||||
return new URL(url).origin;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getTrustedOrigins(): string[] {
|
||||
const origins = new Set<string>();
|
||||
|
|
|
|||
9
src/lib/shared/urls.ts
Normal file
9
src/lib/shared/urls.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export function tryGetOrigin(url: string | undefined): string | null {
|
||||
if (!url) return null;
|
||||
try {
|
||||
const origin = new URL(url).origin;
|
||||
return origin === 'null' ? null : origin;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
27
tests/unit/layout-metadata.vitest.spec.ts
Normal file
27
tests/unit/layout-metadata.vitest.spec.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { describe, expect, test } from 'vitest';
|
||||
import { tryGetOrigin } from '../../src/lib/shared/urls';
|
||||
|
||||
describe('tryGetOrigin utility function', () => {
|
||||
test('returns null for undefined, null, or empty string', () => {
|
||||
expect(tryGetOrigin(undefined)).toBeNull();
|
||||
expect(tryGetOrigin('')).toBeNull();
|
||||
});
|
||||
|
||||
test('extracts origin from valid HTTP/HTTPS URLs', () => {
|
||||
expect(tryGetOrigin('http://localhost:3000')).toBe('http://localhost:3000');
|
||||
expect(tryGetOrigin('https://openreader.richardr.dev')).toBe('https://openreader.richardr.dev');
|
||||
expect(tryGetOrigin('https://my-custom-domain.com/some/path?query=1')).toBe('https://my-custom-domain.com');
|
||||
});
|
||||
|
||||
test('returns null for malformed URLs/strings without protocols', () => {
|
||||
expect(tryGetOrigin('malformed-url')).toBeNull();
|
||||
expect(tryGetOrigin('localhost:3000')).toBeNull();
|
||||
expect(tryGetOrigin('my-custom-domain.com')).toBeNull();
|
||||
});
|
||||
|
||||
test('returns null for invalid protocols/schemes', () => {
|
||||
expect(tryGetOrigin('ftp://some-server.com')).toBe('ftp://some-server.com'); // standard URL supports ftp
|
||||
expect(tryGetOrigin('invalid://foo')).toBeNull(); // non-standard/unsupported origins return null
|
||||
expect(tryGetOrigin('http://[invalid-ipv6]')).toBeNull();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue