diff --git a/authentik/stages/email/utils.py b/authentik/stages/email/utils.py index cc69d142a2..98dba7d35a 100644 --- a/authentik/stages/email/utils.py +++ b/authentik/stages/email/utils.py @@ -4,6 +4,7 @@ from email.mime.image import MIMEImage from functools import lru_cache from pathlib import Path +from django.conf import settings from django.core.mail import EmailMultiAlternatives from django.core.mail.message import sanitize_address from django.template.exceptions import TemplateDoesNotExist @@ -14,9 +15,10 @@ from django.utils import translation @lru_cache def logo_data() -> MIMEImage: """Get logo as MIME Image for emails""" - path = Path("web/icons/icon_left_brand.png") - if not path.exists(): - path = Path("web/dist/assets/icons/icon_left_brand.png") + path = Path("web/dist/assets/icons/icon_left_brand.png") + # When running tests, assets might not exist, so fallback to a different icon + if settings.TEST: + path = Path("web/authentik/sources/saml.png") with open(path, "rb") as _logo_file: logo = MIMEImage(_logo_file.read()) logo.add_header("Content-ID", "") diff --git a/web/.storybook/main.js b/web/.storybook/main.js index b7789ecc8b..1421011be0 100644 --- a/web/.storybook/main.js +++ b/web/.storybook/main.js @@ -3,6 +3,8 @@ * @import { StorybookConfig } from "@storybook/web-components-vite"; */ +import { copyAssets } from "../scripts/build-assets.mjs"; + /** * @param {TemplateStringsArray} strings * @param {...any} values @@ -10,15 +12,14 @@ */ const html = (strings, ...values) => String.raw({ raw: strings }, ...values); +await copyAssets(); + /** * @satisfies {StorybookConfig} */ const config = { stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], - staticDirs: [ - { from: "../icons", to: "/static/dist/assets/icons" }, - { from: "../authentik", to: "/static/authentik" }, - ], + staticDirs: [{ from: "../dist/assets", to: "/static/dist/assets" }], addons: [ // --- "@storybook/addon-links", diff --git a/web/icons/brand.png b/web/icons/brand.png deleted file mode 100644 index e323066d6c..0000000000 Binary files a/web/icons/brand.png and /dev/null differ diff --git a/web/icons/brand.svg b/web/icons/brand.svg deleted file mode 100644 index 9492751bfb..0000000000 --- a/web/icons/brand.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/icons/icon.png b/web/icons/icon.png deleted file mode 100644 index 8165cae90b..0000000000 Binary files a/web/icons/icon.png and /dev/null differ diff --git a/web/icons/icon.svg b/web/icons/icon.svg deleted file mode 100644 index 00b685c98b..0000000000 --- a/web/icons/icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/icons/icon_left_brand.png b/web/icons/icon_left_brand.png deleted file mode 100644 index b9bd833a32..0000000000 Binary files a/web/icons/icon_left_brand.png and /dev/null differ diff --git a/web/icons/icon_left_brand.svg b/web/icons/icon_left_brand.svg deleted file mode 100644 index 189501f137..0000000000 --- a/web/icons/icon_left_brand.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/icons/icon_pride_lgbt.png b/web/icons/icon_pride_lgbt.png deleted file mode 100644 index 256316c4ee..0000000000 Binary files a/web/icons/icon_pride_lgbt.png and /dev/null differ diff --git a/web/icons/icon_pride_trans.png b/web/icons/icon_pride_trans.png deleted file mode 100644 index 4e03817345..0000000000 Binary files a/web/icons/icon_pride_trans.png and /dev/null differ diff --git a/web/icons/icon_top_brand.png b/web/icons/icon_top_brand.png deleted file mode 100644 index 3e55ae3399..0000000000 Binary files a/web/icons/icon_top_brand.png and /dev/null differ diff --git a/web/icons/icon_top_brand.svg b/web/icons/icon_top_brand.svg deleted file mode 100644 index 4376998381..0000000000 --- a/web/icons/icon_top_brand.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/package-lock.json b/web/package-lock.json index c7a17db70a..af0305a748 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -24,6 +24,7 @@ "@formatjs/intl-listformat": "^8.3.5", "@fortawesome/fontawesome-free": "^7.2.0", "@goauthentik/api": "0.0.0", + "@goauthentik/brand-assets": "^2.0.0", "@goauthentik/core": "^1.0.0", "@goauthentik/esbuild-plugin-live-reload": "^2.0.1", "@goauthentik/eslint-config": "^1.3.0", @@ -1551,6 +1552,12 @@ "resolved": "packages/client-ts", "link": true }, + "node_modules/@goauthentik/brand-assets": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@goauthentik/brand-assets/-/brand-assets-2.0.0.tgz", + "integrity": "sha512-yRJrV+KuGrz7MNcRzAkZa4e7LuciuFZBVSyPFRd/EndxgiqcFuFHyn+6tEurKNmianBNURhe2qm5ytoLFgEWFQ==", + "license": "UNLICENSED" + }, "node_modules/@goauthentik/core": { "resolved": "packages/core", "link": true diff --git a/web/package.json b/web/package.json index c5e8fcb6fa..ad105115cc 100644 --- a/web/package.json +++ b/web/package.json @@ -99,6 +99,7 @@ "@formatjs/intl-listformat": "^8.3.5", "@fortawesome/fontawesome-free": "^7.2.0", "@goauthentik/api": "0.0.0", + "@goauthentik/brand-assets": "^2.0.0", "@goauthentik/core": "^1.0.0", "@goauthentik/esbuild-plugin-live-reload": "^2.0.1", "@goauthentik/eslint-config": "^1.3.0", diff --git a/web/scripts/build-assets.mjs b/web/scripts/build-assets.mjs new file mode 100644 index 0000000000..024046c67f --- /dev/null +++ b/web/scripts/build-assets.mjs @@ -0,0 +1,75 @@ +import "@goauthentik/core/environment/load/node"; + +import * as fs from "node:fs/promises"; +import { createRequire } from "node:module"; +import * as path from "node:path"; + +import { ConsoleLogger } from "#logger/node"; +import { DistDirectory, EntryPoint, PackageRoot } from "#paths/node"; + +const require = createRequire(import.meta.url); +const logger = ConsoleLogger.child({ name: "Assets" }); + +/** + * @typedef {[from: string, to: string]} SourceDestinationPair + */ + +/** + * @type {SourceDestinationPair[]} + */ +const assets = [ + [ + path.join(path.dirname(EntryPoint.StandaloneLoading.in), "startup"), + path.dirname(EntryPoint.StandaloneLoading.out), + ], + [path.resolve(PackageRoot, "src", "assets", "images"), "./assets/images"], + [require.resolve("@goauthentik/brand-assets/brand.png"), "./assets/icons/brand.png"], + [require.resolve("@goauthentik/brand-assets/brand.svg"), "./assets/icons/brand.svg"], + [require.resolve("@goauthentik/brand-assets/icon.png"), "./assets/icons/icon.png"], + [require.resolve("@goauthentik/brand-assets/icon.svg"), "./assets/icons/icon.svg"], + [ + require.resolve("@goauthentik/brand-assets/icon_left_brand.png"), + "./assets/icons/icon_left_brand.png", + ], + [ + require.resolve("@goauthentik/brand-assets/icon_left_brand.svg"), + "./assets/icons/icon_left_brand.svg", + ], + [ + require.resolve("@goauthentik/brand-assets/icon_pride_lgbt.png"), + "./assets/icons/icon_pride_lgbt.png", + ], + [ + require.resolve("@goauthentik/brand-assets/icon_pride_trans.png"), + "./assets/icons/icon_pride_trans.png", + ], + [ + require.resolve("@goauthentik/brand-assets/icon_top_brand.png"), + "./assets/icons/icon_top_brand.png", + ], + [ + require.resolve("@goauthentik/brand-assets/icon_top_brand.svg"), + "./assets/icons/icon_top_brand.svg", + ], +]; + +export async function copyAssets() { + /** + * @param {SourceDestinationPair} pair + */ + const copy = ([from, to]) => { + const resolvedDestination = path.resolve(DistDirectory, to); + + logger.debug(`📋 Copying assets from ${from} to ${to}`); + + return fs + .cp(from, resolvedDestination, { + recursive: true, + }) + .catch((error) => { + logger.error(`Failed to copy assets from ${from} to ${to}: ${error}`); + }); + }; + + return await Promise.all(assets.map(copy)); +} diff --git a/web/scripts/build-web.mjs b/web/scripts/build-web.mjs index b43e4b40ba..117c3735db 100644 --- a/web/scripts/build-web.mjs +++ b/web/scripts/build-web.mjs @@ -7,6 +7,8 @@ import "@goauthentik/core/environment/load/node"; import * as fs from "node:fs/promises"; import * as path from "node:path"; +import { copyAssets } from "./build-assets.mjs"; + /** * @file ESBuild script for building the authentik web UI. * @@ -35,22 +37,6 @@ const publicBundledDefinitions = Object.fromEntries( ); logger.info(publicBundledDefinitions, "Bundle definitions"); -/** - * @typedef {[from: string, to: string]} SourceDestinationPair - */ - -/** - * @type {SourceDestinationPair[]} - */ -const assets = [ - [ - path.join(path.dirname(EntryPoint.StandaloneLoading.in), "startup"), - path.dirname(EntryPoint.StandaloneLoading.out), - ], - [path.resolve(PackageRoot, "src", "assets", "images"), "./assets/images"], - [path.resolve(PackageRoot, "icons"), "./assets/icons"], -]; - const entryPointNames = Object.keys(EntryPoint); const entryPoints = Object.values(EntryPoint); const entryPointsDescription = entryPointNames.join("\n\t"); @@ -68,29 +54,7 @@ const BASE_ESBUILD_PLUGINS = [ */ const errors = []; - /** - * @param {SourceDestinationPair} pair - */ - const copy = ([from, to]) => { - const resolvedDestination = path.resolve(DistDirectory, to); - - logger.debug(`📋 Copying assets from ${from} to ${to}`); - - return fs - .cp(from, resolvedDestination, { - recursive: true, - }) - .catch((error) => { - errors.push({ - text: `Failed to copy assets from ${from} to ${to}: ${error}`, - location: { - file: from, - }, - }); - }); - }; - - await Promise.all(assets.map(copy)); + await copyAssets(); return { errors }; });