From aaf5bb62abd6c1daefc675a7aa7eebfac561fb3a Mon Sep 17 00:00:00 2001 From: Johann150 Date: Thu, 19 May 2022 09:54:45 +0200 Subject: [PATCH] enhance: uniform theme color (#8702) * enhance: make theme color format uniform All newly fetched instance theme colors will be uniformely formatted as hashtag followed by 6 hexadecimal digits. Colors are checked for validity and invalid colors are not handled. * better input validation for own theme color * migration to unify theme color formats Fixes theme colors of other instances as well as the local instance. * add changelog entry Co-authored-by: syuilo --- CHANGELOG.md | 3 ++ .../1652859567549-uniform-themecolor.js | 38 +++++++++++++++++++ .../server/api/endpoints/admin/update-meta.ts | 2 +- .../src/services/fetch-instance-metadata.ts | 14 +++---- 4 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 packages/backend/migration/1652859567549-uniform-themecolor.js diff --git a/CHANGELOG.md b/CHANGELOG.md index fb8b8fdee..21ae948d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,9 @@ You should also include the user name that made the change. - update dependencies @syuilo - enhance: display URL of QR code for TOTP registration @syuilo - make CAPTCHA required for signin to improve security @syuilo +- The theme color is now better validated. @Johann150 + Your own theme color may be unset if it was in an invalid format. + Admins should check their instance settings if in doubt. - Perform port diagnosis at startup only when Listen fails @mei23 ### Bugfixes diff --git a/packages/backend/migration/1652859567549-uniform-themecolor.js b/packages/backend/migration/1652859567549-uniform-themecolor.js new file mode 100644 index 000000000..bc47143e5 --- /dev/null +++ b/packages/backend/migration/1652859567549-uniform-themecolor.js @@ -0,0 +1,38 @@ +import tinycolor from 'tinycolor2'; + +export class uniformThemecolor1652859567549 { + name = 'uniformThemecolor1652859567549' + + async up(queryRunner) { + const formatColor = (color) => { + let tc = new tinycolor(color); + if (color.isValid()) { + return color.toHexString(); + } else { + return null; + } + }; + + await Promise.all(queryRunner.query('SELECT "id", "themeColor" FROM "instance" WHERE "themeColor" IS NOT NULL') + .then(instances => instances.map(instance => { + // update theme color to uniform format, e.g. #00ff00 + // invalid theme colors get set to null + instance.color = formatColor(instance.color); + + return queryRunner.query('UPDATE "instance" SET "themeColor" = :themeColor WHERE "id" = :id', instance); + }))); + + // also fix own theme color + await queryRunner.query('SELECT "themeColor" FROM "meta" WHERE "themeColor" IS NOT NULL LIMIT 1') + .then(metas => { + if (metas.length > 0) { + return queryRunner.query('UPDATE "meta" SET "themeColor" = :color', { color: formatColor(metas[0].color) }); + } + }); + } + + async down(queryRunner) { + // The original representation is not stored, so migrating back is not possible. + // The new format also works in older versions so this is not a problem. + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index b23ee9e3d..09e43301b 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -27,7 +27,7 @@ export const paramDef = { blockedHosts: { type: 'array', nullable: true, items: { type: 'string', } }, - themeColor: { type: 'string', nullable: true }, + themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' }, mascotImageUrl: { type: 'string', nullable: true }, bannerUrl: { type: 'string', nullable: true }, errorImageUrl: { type: 'string', nullable: true }, diff --git a/packages/backend/src/services/fetch-instance-metadata.ts b/packages/backend/src/services/fetch-instance-metadata.ts index d5294c5fe..029c388dc 100644 --- a/packages/backend/src/services/fetch-instance-metadata.ts +++ b/packages/backend/src/services/fetch-instance-metadata.ts @@ -1,5 +1,6 @@ import { DOMWindow, JSDOM } from 'jsdom'; import fetch from 'node-fetch'; +import tinycolor from 'tinycolor2'; import { getJson, getHtml, getAgentByUrl } from '@/misc/fetch.js'; import { Instance } from '@/models/entities/instance.js'; import { Instances } from '@/models/index.js'; @@ -208,16 +209,11 @@ async function fetchIconUrl(instance: Instance, doc: DOMWindow['document'] | nul } async function getThemeColor(doc: DOMWindow['document'] | null, manifest: Record | null): Promise { - if (doc) { - const themeColor = doc.querySelector('meta[name="theme-color"]')?.getAttribute('content'); + const themeColor = doc?.querySelector('meta[name="theme-color"]')?.getAttribute('content') || manifest?.theme_color; - if (themeColor) { - return themeColor; - } - } - - if (manifest) { - return manifest.theme_color; + if (themeColor) { + const color = new tinycolor(themeColor); + if (color.isValid()) return color.toHexString(); } return null;