fix: ssr open graph tags (#27639)

fix: SSR open graph tags
This commit is contained in:
Jason Rasmussen 2026-04-09 12:16:41 -04:00 committed by GitHub
parent b5bed02300
commit 7b269d1638
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 38 additions and 50 deletions

46
pnpm-lock.yaml generated
View File

@ -553,9 +553,6 @@ importers:
sanitize-filename:
specifier: ^1.6.3
version: 1.6.4
sanitize-html:
specifier: ^2.14.0
version: 2.17.2
semver:
specifier: ^7.6.2
version: 7.7.4
@ -659,9 +656,6 @@ importers:
'@types/react':
specifier: ^19.0.0
version: 19.2.14
'@types/sanitize-html':
specifier: ^2.13.0
version: 2.16.1
'@types/semver':
specifier: ^7.5.8
version: 7.7.1
@ -5167,9 +5161,6 @@ packages:
'@types/retry@0.12.2':
resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==}
'@types/sanitize-html@2.16.1':
resolution: {integrity: sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA==}
'@types/sax@1.2.7':
resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==}
@ -7971,9 +7962,6 @@ packages:
webpack:
optional: true
htmlparser2@10.1.0:
resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
htmlparser2@6.1.0:
resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==}
@ -8303,10 +8291,6 @@ packages:
resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
engines: {node: '>=0.10.0'}
is-plain-object@5.0.0:
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
engines: {node: '>=0.10.0'}
is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
@ -9692,9 +9676,6 @@ packages:
parse-numeric-range@1.3.0:
resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==}
parse-srcset@1.0.2:
resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==}
parse5-htmlparser2-tree-adapter@7.1.0:
resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==}
@ -10892,9 +10873,6 @@ packages:
sanitize-filename@1.6.4:
resolution: {integrity: sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==}
sanitize-html@2.17.2:
resolution: {integrity: sha512-EnffJUl46VE9uvZ0XeWzObHLurClLlT12gsOk1cHyP2Ol1P0BnBnsXmShlBmWVJM+dKieQI68R0tsPY5m/B+Jg==}
sass@1.97.1:
resolution: {integrity: sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==}
engines: {node: '>=14.0.0'}
@ -17459,10 +17437,6 @@ snapshots:
'@types/retry@0.12.2': {}
'@types/sanitize-html@2.16.1':
dependencies:
htmlparser2: 10.1.0
'@types/sax@1.2.7':
dependencies:
'@types/node': 24.12.2
@ -20865,13 +20839,6 @@ snapshots:
optionalDependencies:
webpack: 5.104.1
htmlparser2@10.1.0:
dependencies:
domelementtype: 2.3.0
domhandler: 5.0.3
domutils: 3.2.2
entities: 7.0.1
htmlparser2@6.1.0:
dependencies:
domelementtype: 2.3.0
@ -21200,8 +21167,6 @@ snapshots:
dependencies:
isobject: 3.0.1
is-plain-object@5.0.0: {}
is-potential-custom-element-name@1.0.1:
optional: true
@ -22933,8 +22898,6 @@ snapshots:
parse-numeric-range@1.3.0: {}
parse-srcset@1.0.2: {}
parse5-htmlparser2-tree-adapter@7.1.0:
dependencies:
domhandler: 5.0.3
@ -24278,15 +24241,6 @@ snapshots:
dependencies:
truncate-utf8-bytes: 1.0.2
sanitize-html@2.17.2:
dependencies:
deepmerge: 4.3.1
escape-string-regexp: 4.0.0
htmlparser2: 10.1.0
is-plain-object: 5.0.0
parse-srcset: 1.0.2
postcss: 8.5.8
sass@1.97.1:
dependencies:
chokidar: 4.0.3

View File

@ -110,7 +110,6 @@
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
"sanitize-filename": "^1.6.3",
"sanitize-html": "^2.14.0",
"semver": "^7.6.2",
"sharp": "^0.34.5",
"sirv": "^3.0.0",
@ -147,7 +146,6 @@
"@types/picomatch": "^4.0.0",
"@types/pngjs": "^6.0.5",
"@types/react": "^19.0.0",
"@types/sanitize-html": "^2.13.0",
"@types/semver": "^7.5.8",
"@types/supertest": "^7.0.0",
"@types/ua-parser-js": "^0.7.36",

View File

@ -0,0 +1,36 @@
import { ApiService, render } from 'src/services/api.service';
describe(ApiService.name, () => {
describe('render', () => {
it('should correctly render open graph tags', () => {
const output = render('<!-- metadata:tags -->', {
title: 'title',
description: 'description',
imageUrl: 'https://demo.immich.app/api/assets/123',
});
expect(output).toContain('<meta property="og:title" content="title" />');
expect(output).toContain('<meta property="og:description" content="description" />');
expect(output).toContain('<meta property="og:image" content="https://demo.immich.app/api/assets/123" />');
});
it('should escape html tags', () => {
expect(
render('<!-- metadata:tags -->', {
title: "<script>console.log('hello')</script>Test",
description: 'description',
}),
).toContain(
'<meta property="og:title" content="&lt;script&gt;console.log(&#39;hello&#39;)&lt;/script&gt;Test" />',
);
});
it('should escape quotes', () => {
expect(
render('<!-- metadata:tags -->', {
title: `0;url=https://example.com" http-equiv="refresh`,
description: 'description',
}),
).toContain('<meta property="og:title" content="0;url=https://example.com&quot; http-equiv=&quot;refresh" />');
});
});
});

View File

@ -1,7 +1,7 @@
import { Injectable, NotAcceptableException } from '@nestjs/common';
import { NextFunction, Request, Response } from 'express';
import { escape } from 'lodash';
import { readFileSync } from 'node:fs';
import sanitizeHtml from 'sanitize-html';
import { ConfigRepository } from 'src/repositories/config.repository';
import { LoggingRepository } from 'src/repositories/logging.repository';
import { AuthService } from 'src/services/auth.service';
@ -10,7 +10,7 @@ import { OpenGraphTags } from 'src/utils/misc';
export const render = (index: string, meta: OpenGraphTags) => {
const [title, description, imageUrl] = [meta.title, meta.description, meta.imageUrl].map((item) =>
item ? sanitizeHtml(item, { allowedTags: [] }) : '',
item ? escape(item) : '',
);
const tags = `