fix: Client-Upload akzeptiert SECRET_KEY oder JWT_SECRET_KEY + Download in Settings

Upload-Auth:
- Akzeptiert jetzt sowohl SECRET_KEY als auch JWT_SECRET_KEY
  (BUILD_UPLOAD_TOKEN in Entwicklungs-.env kann einer von beiden sein)

Settings-View:
- Zeigt verfuegbare Desktop/Mobile Clients zum Download an
  (nur wenn mindestens ein Client vorhanden)
- Pro Client: Name, Dateiname, Download-Button

.env.example:
- Klarere Kommentare: "SECRET_KEY oder JWT_SECRET_KEY des Zielservers"

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker 2026-04-11 23:58:54 +02:00
parent 9391a58683
commit 29cc00e284
3 changed files with 52 additions and 10 deletions

View File

@ -37,11 +37,12 @@ MAX_UPLOAD_SIZE_MB=500
ONLYOFFICE_URL=
# =============================================
# Client-Build Upload (NUR auf der Entwicklungsmaschine!)
# Diese Werte gehoeren NICHT auf den Produktionsserver,
# sondern in die .env der Maschine auf der ./build.sh laeuft.
# Client-Build Upload (NUR auf der ENTWICKLUNGSMASCHINE!)
# NICHT auf dem Produktionsserver setzen!
# Diese Werte braucht nur die Maschine auf der ./build.sh laeuft.
# =============================================
# Oeffentliche URL der Cloud-Instanz wohin die Builds hochgeladen werden
# URL der Cloud-Instanz wohin die Builds hochgeladen werden
CLOUD_URL=https://cloud.example.com
# SECRET_KEY des Zielservers (identisch mit SECRET_KEY oben auf dem Server)
# SECRET_KEY oder JWT_SECRET_KEY des Zielservers
# (den gleichen Wert hier reinkopieren der auf dem Server steht)
BUILD_UPLOAD_TOKEN=

View File

@ -26,11 +26,13 @@ def _clients_dir():
def _verify_build_token():
"""Verify the build upload token from header or query param."""
expected = os.environ.get('SECRET_KEY', '')
if not expected:
return False
token = request.headers.get('X-Build-Token', '') or request.args.get('build_token', '')
return token == expected
if not token:
return False
# Accept SECRET_KEY or JWT_SECRET_KEY
secret = os.environ.get('SECRET_KEY', '')
jwt_secret = os.environ.get('JWT_SECRET_KEY', '')
return token == secret or token == jwt_secret
# --- Public: list available clients ---

View File

@ -45,6 +45,24 @@
</form>
</div>
<!-- Client Downloads -->
<div v-if="availableClients.length" class="settings-section">
<h3>Desktop & Mobile Clients</h3>
<div class="client-list">
<div v-for="client in availableClients" :key="client.platform" class="client-item">
<div class="client-info">
<i :class="'pi ' + (client.platform === 'linux' || client.platform === 'windows' || client.platform === 'mac' ? 'pi-desktop' : 'pi-mobile')"></i>
<div>
<strong>{{ client.name }}</strong>
<span class="client-meta">{{ client.filename }}</span>
</div>
</div>
<Button icon="pi pi-download" :label="'Download'" size="small" outlined
@click="downloadClient(client)" />
</div>
</div>
</div>
<!-- Email Accounts -->
<div class="settings-section">
<div class="section-header">
@ -167,6 +185,13 @@ import InputSwitch from 'primevue/inputswitch'
const auth = useAuthStore()
const toast = useToast()
// Client downloads
const availableClients = ref([])
function downloadClient(client) {
window.location.href = `/api/clients/${client.platform}/download`
}
// --- Password change ---
const currentPassword = ref('')
const newPassword = ref('')
@ -307,7 +332,13 @@ async function doDeleteAccount() {
}
}
onMounted(loadAccounts)
onMounted(async () => {
loadAccounts()
try {
const res = await apiClient.get('/clients')
availableClients.value = res.data.clients
} catch { availableClients.value = [] }
})
</script>
<style scoped>
@ -346,4 +377,12 @@ onMounted(loadAccounts)
.field label { display: block; margin-bottom: 0.5rem; font-weight: 500; font-size: 0.875rem; }
.field-row { display: flex; gap: 0.75rem; align-items: flex-end; }
.flex-grow { flex: 1; }
.client-list { display: flex; flex-direction: column; gap: 0.5rem; }
.client-item {
display: flex; align-items: center; justify-content: space-between;
padding: 0.75rem; border: 1px solid var(--p-surface-200); border-radius: 8px;
}
.client-info { display: flex; align-items: center; gap: 0.75rem; }
.client-info i { font-size: 1.25rem; color: var(--p-primary-color); }
.client-meta { display: block; font-size: 0.8rem; color: var(--p-text-muted-color); }
</style>