fix(honeybar): Ursprung zur Laufzeit statt beim Bauen
Die Adresse im <script>-Tag stand fest in der index.html, waehrend die CSP aus HONEYBAR_ORIGIN kam. Wer die Leiste woanders betreibt, bekam genau den Widerspruch: die CSP erlaubte den eigenen Ursprung, geladen wurde der alte. Der erste Versuch, das ueber ein Bau-Argument zu loesen, half beim fertigen Abbild nicht — dort ist der Bau vorbei, und ein Umzug haette einen Neubau verlangt. Ursprung und Schluessel stehen jetzt in /api/v1/public/config, die Oberflaeche haengt das Skript selbst ein. Beides speist sich aus derselben Variablen wie die CSP, sie koennen also nicht auseinanderlaufen, und eine Aenderung wirkt beim Neustart. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6c7eb348b0
commit
57156a6394
11 files changed
+74
-67
No files matched your search
@@ -374,6 +374,12 @@ func main() {
|
||||
faviconURL = admin.AppIconURL(s["app_icon_storage_key"], "64")
|
||||
appleTouchURL = admin.AppIconURL(s["app_icon_storage_key"], "192")
|
||||
}
|
||||
// Ohne Ursprung gibt es keine Leiste — dann hat auch der Schluessel
|
||||
// nichts auf der Seite zu suchen.
|
||||
honeybarKey := ""
|
||||
if cfg.HoneybarOrigin != "" {
|
||||
honeybarKey = cfg.HoneybarKey
|
||||
}
|
||||
apps := cfg.ExternalApps
|
||||
if apps == nil {
|
||||
apps = []config.ExternalApp{} // nie null – das Frontend erwartet eine Liste
|
||||
@@ -398,6 +404,12 @@ func main() {
|
||||
// ist – sonst gibt der Endpunkt eine Einstellung preis, die
|
||||
// niemanden etwas angeht.
|
||||
"oidc_button_label": oidcLabel,
|
||||
// Die HoneyOS-Leiste laedt die Oberflaeche selbst nach. Zur
|
||||
// Laufzeit statt beim Bauen, damit ein fertiges Abbild ueber die
|
||||
// .env umgezogen werden kann — und damit derselbe Ursprung
|
||||
// gilt, den die CSP erlaubt.
|
||||
"honeybar_origin": cfg.HoneybarOrigin,
|
||||
"honeybar_key": honeybarKey,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -83,9 +83,21 @@ type Config struct {
|
||||
// (z. B. https://os.example.com). Er wird in die CSP aufgenommen — ohne das
|
||||
// blockt der Browser das Skript und dessen Anfragen.
|
||||
//
|
||||
// Muss zur Adresse im <script>-Tag der index.html passen. Leer heißt: keine
|
||||
// Leiste, und die CSP bleibt so eng wie bisher.
|
||||
// Derselbe Wert geht über /api/v1/public/config an die Oberfläche, die das
|
||||
// Skript daraufhin selbst nachlädt. Beides aus einer Quelle: so kann die
|
||||
// CSP nicht einen anderen Ursprung erlauben als den geladenen.
|
||||
//
|
||||
// Leer heißt: keine Leiste, und die CSP bleibt so eng wie bisher.
|
||||
HoneybarOrigin string
|
||||
// HoneybarKey ist der Ausweis, mit dem sich die Leiste bei HoneyOS die
|
||||
// App-Liste holt (dort HONEYOS_APPS_KEY). Nötig, wenn HoneyOS auf einer
|
||||
// anderen Site liegt als Aether: dann schickt der Browser die
|
||||
// HoneyOS-Sitzung nicht mit.
|
||||
//
|
||||
// Er steht anschließend im Quelltext der Seite — das ist unvermeidlich,
|
||||
// die Leiste läuft im Browser. Er gibt nur die App-Liste her, nichts
|
||||
// Schreibendes.
|
||||
HoneybarKey string
|
||||
// OfficeWopiURL ist die Adresse, unter der der **Office-Server** Aether
|
||||
// erreicht.
|
||||
//
|
||||
@@ -189,6 +201,7 @@ func Load() (*Config, error) {
|
||||
OfficeURL: strings.TrimRight(getEnv("OFFICE_URL", ""), "/"),
|
||||
OfficePublicURL: strings.TrimRight(getEnv("OFFICE_PUBLIC_URL", ""), "/"),
|
||||
HoneybarOrigin: strings.TrimRight(getEnv("HONEYBAR_ORIGIN", ""), "/"),
|
||||
HoneybarKey: getEnv("HONEYBAR_KEY", ""),
|
||||
OfficeWopiURL: strings.TrimRight(getEnv("OFFICE_WOPI_URL", ""), "/"),
|
||||
OfficeAllowedHosts: splitList(getEnv("OFFICE_ALLOWED_HOSTS", "")),
|
||||
OfficeInsecureTLS: getEnv("OFFICE_INSECURE_TLS", "false") == "true",
|
||||
|
||||
+11
-11
@@ -65,19 +65,19 @@ ADMIN_PASSWORD=changeme_strong_admin_password
|
||||
# OIDC_BUTTON_LABEL=Anmelden mit PocketID
|
||||
|
||||
# ── HoneyOS-Leiste ────────────────────────────────────────────────────────────
|
||||
# Ursprung, von dem die Leiste geladen wird. Landet an zwei Stellen: beim
|
||||
# Bauen im <script>-Tag der index.html, zur Laufzeit in der CSP
|
||||
# (script-src + connect-src). Leer = keine Leiste, CSP so eng wie ohne sie.
|
||||
# Ursprung, von dem die Leiste geladen wird. Wird an zwei Stellen gebraucht und
|
||||
# kommt deshalb aus einer Quelle: die CSP erlaubt ihn (script-src +
|
||||
# connect-src), und die Oberflaeche laedt das Skript von dort nach.
|
||||
#
|
||||
# Wird beim **Bauen** eingesetzt — nach einer Änderung also
|
||||
# `docker compose build app`, ein Neustart genügt nicht.
|
||||
HONEYBAR_ORIGIN=https://honeyos.example.com
|
||||
# Schlüssel, mit dem sich die Leiste die App-Liste holt. Nötig, weil HoneyOS auf
|
||||
# einer anderen Site liegt als Aether und der Browser die HoneyOS-Sitzung dann
|
||||
# nicht mitschickt. Muss zu HONEYOS_APPS_KEY drüben passen.
|
||||
# Wirkt beim **Neustart** — kein Neubau noetig.
|
||||
# Leer = keine Leiste, und die CSP bleibt so eng wie ohne sie.
|
||||
HONEYBAR_ORIGIN=https://os.example.com
|
||||
# Ausweis, mit dem sich die Leiste die App-Liste holt. Muss zu HONEYOS_APPS_KEY
|
||||
# drueben passen. Nur noetig, wenn HoneyOS auf einer anderen Site liegt als
|
||||
# Aether — dann schickt der Browser die HoneyOS-Sitzung nicht mit.
|
||||
#
|
||||
# Wird beim **Bauen** in die index.html eingesetzt — nach einer Änderung also
|
||||
# `docker compose build app`, ein Neustart genügt nicht.
|
||||
# Gleiche Site (os.beispiel.duckdns.org + cloud.beispiel.duckdns.org): kann leer
|
||||
# bleiben, die Sitzung reicht.
|
||||
HONEYBAR_KEY=
|
||||
|
||||
# ── Volltextsuche (OCR) ───────────────────────────────────────────────────────
|
||||
|
||||
@@ -4,13 +4,6 @@ WORKDIR /app/frontend
|
||||
COPY frontend/package*.json ./
|
||||
RUN npm ci
|
||||
COPY frontend/ ./
|
||||
# Ursprung und Schlüssel für die HoneyOS-Leiste. Vite setzt beides in die
|
||||
# index.html ein. Ohne Ursprung faellt der Tag weg (keine Leiste), ohne
|
||||
# Schlüssel bekommt die Leiste keine App-Liste — Aether bleibt unberührt.
|
||||
ARG HONEYBAR_ORIGIN=""
|
||||
ARG HONEYBAR_KEY=""
|
||||
ENV VITE_HONEYBAR_ORIGIN=$HONEYBAR_ORIGIN
|
||||
ENV VITE_HONEYBAR_KEY=$HONEYBAR_KEY
|
||||
RUN npm run build
|
||||
|
||||
# ── Stage 2: Backend build ────────────────────────────────────────────────────
|
||||
|
||||
@@ -16,10 +16,6 @@ services:
|
||||
# frontend/, nicht nur diesen Ordner.
|
||||
context: ..
|
||||
dockerfile: deploy/Dockerfile
|
||||
args:
|
||||
# Wird in die index.html eingebacken, siehe dort.
|
||||
HONEYBAR_ORIGIN: ${HONEYBAR_ORIGIN:-}
|
||||
HONEYBAR_KEY: ${HONEYBAR_KEY:-}
|
||||
image: gitea.honeyy.duckdns.org/docker/aether:latest
|
||||
restart: unless-stopped
|
||||
env_file: .env
|
||||
|
||||
@@ -19,27 +19,5 @@
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
|
||||
<!-- HoneyOS-Leiste: der Umschalter zwischen den eigenen Apps. Sie rendert
|
||||
in einem Shadow DOM, Aethers Stile berührt sie nicht.
|
||||
|
||||
Sie steht hier statt serverseitig eingespritzt, weil der Service Worker
|
||||
die index.html in seinen Vorrat legt (navigateFallback) — was zur
|
||||
Bauzeit nicht drinsteht, käme beim Browser nie an.
|
||||
|
||||
`defer`: ohne wartet der Seitenaufbau auf das Skript. Ist HoneyOS
|
||||
langsam oder aus, darf das Aether nicht ausbremsen.
|
||||
|
||||
Ursprung und Schlüssel kommen beide aus der .env (HONEYBAR_ORIGIN,
|
||||
HONEYBAR_KEY) und werden beim Bauen eingesetzt. Derselbe Ursprung
|
||||
landet zur Laufzeit in der CSP — so können die beiden nicht
|
||||
auseinanderlaufen. Ist er leer, entfernt vite.config.ts diesen Tag
|
||||
ganz; Aether läuft dann ohne Leiste weiter. HoneyOS liegt auf einer anderen Site als Aether
|
||||
(duckdns.org ist ein public suffix), deshalb schickt der Browser die
|
||||
HoneyOS-Sitzung nicht mit — ohne diesen zweiten Ausweis bekäme die
|
||||
Leiste nie eine App-Liste. Der Schlüssel steht bewusst nicht im
|
||||
Quelltext: er käme sonst ins Repo und liesse sich nur mit einer
|
||||
Code-Änderung wechseln. -->
|
||||
<script defer src="%VITE_HONEYBAR_ORIGIN%/honeybar/honeybar.js"
|
||||
data-app="aether" data-key="%VITE_HONEYBAR_KEY%"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -231,6 +231,11 @@ export interface PublicConfig {
|
||||
oidc_button_label?: string
|
||||
primary_color?: string
|
||||
accent_color?: string
|
||||
/** Ursprung der HoneyOS-Leiste (HONEYBAR_ORIGIN). Leer = keine Leiste. */
|
||||
honeybar_origin?: string
|
||||
/** Ausweis fuer deren App-Liste (HONEYBAR_KEY). Nur gesetzt, wenn es einen
|
||||
* Ursprung gibt. */
|
||||
honeybar_key?: string
|
||||
external_apps: ExternalApp[]
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* Die HoneyOS-Leiste nachladen.
|
||||
*
|
||||
* Zur Laufzeit statt beim Bauen: Der Ursprung steht in der .env des Servers,
|
||||
* ein fertiges Abbild laesst sich damit umziehen, ohne es neu zu bauen. Frueher
|
||||
* stand die Adresse fest in der index.html — wer die Leiste woanders betrieb,
|
||||
* bekam einen CSP-Verstoss, weil der erlaubte und der geladene Ursprung
|
||||
* auseinanderliefen. Beides kommt jetzt aus HONEYBAR_ORIGIN.
|
||||
*
|
||||
* Faellt der Server aus oder ist kein Ursprung gesetzt, passiert schlicht
|
||||
* nichts: Aether ist nicht auf die Leiste angewiesen.
|
||||
*/
|
||||
export function mountHoneybar(origin?: string, key?: string) {
|
||||
if (!origin) return
|
||||
// Der Aufruf haengt an der Konfiguration, und die wird auch neu geladen,
|
||||
// wenn der Admin etwas aendert. Zweimal einbinden hiesse zwei Leisten.
|
||||
if (document.querySelector('script[data-honeybar]')) return
|
||||
|
||||
const el = document.createElement('script')
|
||||
el.dataset.honeybar = ''
|
||||
el.defer = true
|
||||
el.src = origin.replace(/\/+$/, '') + '/honeybar/honeybar.js'
|
||||
// honeybar.js liest seine Einstellungen ueber document.currentScript –
|
||||
// das gilt auch fuer ein nachtraeglich eingehaengtes Skript.
|
||||
el.dataset.app = 'aether'
|
||||
if (key) el.dataset.key = key
|
||||
document.head.appendChild(el)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { mountHoneybar } from '../honeybar'
|
||||
import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react'
|
||||
import { publicApi } from '../api/client'
|
||||
import { applyTheme } from '../theme'
|
||||
@@ -103,6 +104,7 @@ export function AppSettingsProvider({ children }: { children: ReactNode }) {
|
||||
if (typeof cfg.max_versions_kept === 'number' && cfg.max_versions_kept > 0) {
|
||||
setMaxVersionsKept(cfg.max_versions_kept)
|
||||
}
|
||||
mountHoneybar(cfg.honeybar_origin, cfg.honeybar_key)
|
||||
applyTheme(cfg.primary_color, cfg.accent_color)
|
||||
applyThemeColor(cfg.primary_color)
|
||||
document.title = cfg.cloud_name || 'Aether'
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"root":["./src/App.tsx","./src/honeybar.ts","./src/main.tsx","./src/test-setup.ts","./src/theme.ts","./src/api/client.ts","./src/components/AppPasswords.tsx","./src/components/AppSwitcher.tsx","./src/components/Breadcrumb.tsx","./src/components/ConfirmDialog.tsx","./src/components/ContextMenu.tsx","./src/components/Dialog.tsx","./src/components/FileCover.tsx","./src/components/FileInfoDialog.tsx","./src/components/FilePreview.tsx","./src/components/FileViews.tsx","./src/components/MoveTo.tsx","./src/components/OfficeEditor.tsx","./src/components/PageHeader.tsx","./src/components/PdfViewer.tsx","./src/components/SearchBox.tsx","./src/components/ShareDialog.tsx","./src/components/ShareWithDialog.tsx","./src/components/Shell.tsx","./src/components/Sidebar.tsx","./src/components/Tabs.tsx","./src/components/TagChip.tsx","./src/components/TagDialog.tsx","./src/components/TagEditDialog.tsx","./src/components/UploadQueue.tsx","./src/components/UserMenu.tsx","./src/components/VersionsDialog.tsx","./src/components/WebDavGuide.tsx","./src/components/filetypes.ts","./src/components/icons.tsx","./src/hooks/useAppSettings.tsx","./src/hooks/useAuth.ts","./src/hooks/useInternalDrag.ts","./src/hooks/useNavHistory.ts","./src/hooks/useOverlayDismiss.ts","./src/pages/AdminPage.tsx","./src/pages/FileBrowser.tsx","./src/pages/LoginPage.tsx","./src/pages/ProfilePage.tsx","./src/pages/SharePage.tsx","./src/pages/SharedPage.tsx","./src/pages/SharedWithMePage.tsx","./src/pages/TagsPage.tsx","./src/pages/TrashPage.tsx","./src/pages/admin/AuditTab.tsx","./src/pages/admin/GroupsTab.tsx","./src/pages/admin/ThemingTab.tsx","./src/pages/admin/UsersTab.tsx","./src/pages/admin/ui.tsx"],"version":"6.0.3"}
|
||||
@@ -32,30 +32,9 @@ function pdfjsStandardFonts(): Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Den <script>-Tag der HoneyOS-Leiste entfernen, wenn kein Ursprung gesetzt ist.
|
||||
*
|
||||
* Ohne das bliebe nach der Ersetzung src="/honeybar/honeybar.js" stehen: der
|
||||
* Service Worker beantwortet die Navigation mit index.html, der Browser meldet
|
||||
* einen MIME-Fehler. Kein Ursprung heisst schlicht keine Leiste.
|
||||
*/
|
||||
function honeybar(): Plugin {
|
||||
return {
|
||||
name: 'aether-honeybar',
|
||||
transformIndexHtml: {
|
||||
order: 'pre',
|
||||
handler(html) {
|
||||
if (process.env.VITE_HONEYBAR_ORIGIN?.trim()) return html
|
||||
return html.replace(/\s*<script[^>]*honeybar\.js[^>]*><\/script>/, '')
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
pdfjsStandardFonts(),
|
||||
honeybar(),
|
||||
react(),
|
||||
tailwindcss(),
|
||||
VitePWA({
|
||||
|
||||
Reference in new issue
Block a user