From d04e64d713e2e089a7b97b8a68601ee032d252f6 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Fri, 16 Jan 2026 04:06:49 +0000 Subject: [PATCH] feat: ensure storage keys clear on auth change and UI reflects provider - Added `bitvid:auth-changed` listener in `js/app.js` to trigger `storageService.lock()` and refresh `UploadModal`. - Implemented `refreshState()` in `js/ui/components/UploadModal.js` to manage secure storage unlocking, sensitive data clearing, and dynamic provider metadata loading. - Updated `js/ui/profileModalController.js` to prioritize default storage connections and display the provider name (e.g., "Unlocked (R2)") in the status text. - Ensured sensitive configuration data is nulled out when storage is locked. --- js/app.js | 15 +++++++ js/ui/components/UploadModal.js | 79 ++++++++++++++++++++++++--------- js/ui/profileModalController.js | 23 ++++++---- 3 files changed, 88 insertions(+), 29 deletions(-) diff --git a/js/app.js b/js/app.js index 73bcc993..6328dfd5 100644 --- a/js/app.js +++ b/js/app.js @@ -719,6 +719,21 @@ class Application { this.syncAuthUiState(); + if (typeof window !== "undefined") { + window.addEventListener("bitvid:auth-changed", (event) => { + const detail = event.detail || {}; + const previousPubkey = detail.previousPubkey; + + if (previousPubkey && typeof storageService !== "undefined" && typeof storageService.lock === "function") { + storageService.lock(previousPubkey); + } + + if (this.uploadModal && typeof this.uploadModal.refreshState === "function") { + this.uploadModal.refreshState(); + } + }); + } + const savedPubKey = this.activeProfilePubkey; if (savedPubKey) { // Auto-login if a pubkey was saved diff --git a/js/ui/components/UploadModal.js b/js/ui/components/UploadModal.js index b05d4ee7..645d4aee 100644 --- a/js/ui/components/UploadModal.js +++ b/js/ui/components/UploadModal.js @@ -455,6 +455,27 @@ export class UploadModal { // --- R2 Integration --- + async refreshState() { + if (this.storageService) { + const pubkey = this.getCurrentPubkey ? this.getCurrentPubkey() : null; + this.isStorageUnlocked = pubkey ? this.storageService.isUnlocked(pubkey) : false; + this.updateLockUi(); + + if (this.isStorageUnlocked) { + await this.loadFromStorage(); + } else { + // Locked: Clear sensitive data from UI and memory + this.cloudflareSettings = null; + this.fillSettingsForm({}); + this.updateStorageStatus(false); + this.activeConnectionId = null; + } + } else if (this.r2Service && typeof this.r2Service.loadSettings === "function") { + await this.loadR2Settings(); + } + } + + // Legacy fallback loader (renamed for clarity if desired, but keeping signature safe) async loadR2Settings() { // Fallback legacy load if storage service not used/available if (!this.r2Service?.loadSettings) return; @@ -470,26 +491,39 @@ export class UploadModal { const pubkey = this.getCurrentPubkey ? this.getCurrentPubkey() : null; if (!pubkey) return; - const connections = await this.storageService.listConnections(pubkey); - const r2Conn = connections.find(c => c.provider === PROVIDERS.R2 || c.provider === "cloudflare_r2"); + try { + const connections = await this.storageService.listConnections(pubkey); + // Prefer default connection, fallback to R2 + const defaultConn = connections.find(c => c.meta?.defaultForUploads); + const targetConn = defaultConn || connections.find(c => c.provider === PROVIDERS.R2 || c.provider === "cloudflare_r2"); - if (r2Conn) { - try { - const details = await this.storageService.getConnection(pubkey, r2Conn.id); - if (details) { - this.cloudflareSettings = { - accountId: details.accountId, - accessKeyId: details.accessKeyId, - secretAccessKey: details.secretAccessKey, - baseDomain: details.meta?.baseDomain || "", - }; - this.fillSettingsForm(this.cloudflareSettings); - this.updateStorageStatus(true); - this.activeConnectionId = r2Conn.id; - } - } catch (err) { - console.error("Failed to load connection", err); - } + if (targetConn) { + const details = await this.storageService.getConnection(pubkey, targetConn.id); + if (details) { + // Map generic S3 or R2 details to settings + // R2 settings struct: accountId, accessKeyId, secretAccessKey, baseDomain + this.cloudflareSettings = { + accountId: details.accountId, // Might be undefined for generic S3 + accessKeyId: details.accessKeyId, + secretAccessKey: details.secretAccessKey, + baseDomain: details.meta?.baseDomain || "", + // Add generic endpoint for internal use if needed + endpoint: details.endpoint, + region: details.region, + bucket: details.bucket + }; + this.fillSettingsForm(this.cloudflareSettings); + + const providerLabel = targetConn.provider === PROVIDERS.R2 || targetConn.provider === "cloudflare_r2" + ? "R2" + : (targetConn.meta?.label || "S3"); + this.updateStorageStatus(true, providerLabel); + + this.activeConnectionId = targetConn.id; + } + } + } catch (err) { + console.error("Failed to load connection", err); } } @@ -541,6 +575,7 @@ export class UploadModal { if (this.statusText.storage) { if (locked) { this.statusText.storage.classList.add("hidden"); + this.statusText.storage.textContent = ""; } else { this.statusText.storage.classList.remove("hidden"); } @@ -687,9 +722,11 @@ export class UploadModal { } } - updateStorageStatus(isValid) { + updateStorageStatus(isValid, providerLabel) { if (this.statusText.storage) { - this.statusText.storage.textContent = isValid ? "Ready" : "Missing Credentials"; + const baseText = isValid ? "Ready" : "Missing Credentials"; + const label = providerLabel ? ` (${providerLabel})` : ""; + this.statusText.storage.textContent = baseText + label; this.statusText.storage.className = isValid ? "text-xs text-accent" : "text-xs text-critical"; } } diff --git a/js/ui/profileModalController.js b/js/ui/profileModalController.js index e53309e4..0cc0fa16 100644 --- a/js/ui/profileModalController.js +++ b/js/ui/profileModalController.js @@ -5921,13 +5921,18 @@ export class ProfileModalController { // For now, we assume managing a 'default' connection or the first one found. try { const connections = await storageService.listConnections(pubkey); - const targetId = "default"; // Simplified for now - const hasDefault = connections.find(c => c.id === targetId); + // Prioritize default for uploads + const defaultConn = connections.find(c => c.meta?.defaultForUploads); + const targetConn = defaultConn || connections[0]; - if (hasDefault) { - const conn = await storageService.getConnection(pubkey, targetId); + if (targetConn) { + const conn = await storageService.getConnection(pubkey, targetConn.id); if (conn) { this.fillStorageForm(conn); + if (this.storageStatusText) { + const label = conn.meta?.label || conn.provider || "S3"; + this.storageStatusText.textContent = `Unlocked (${label})`; + } } } } catch (error) { @@ -5943,13 +5948,15 @@ export class ProfileModalController { fillStorageForm(conn) { if (!conn) return; - const { provider, accessKeyId, secretAccessKey } = conn; + const { provider, accessKeyId, secretAccessKey, accountId: payloadAccountId, endpoint: payloadEndpoint } = conn; const { endpoint, region, bucket, prefix, defaultForUploads, accountId } = conn.meta || {}; if (this.storageProviderInput) this.storageProviderInput.value = provider || "cloudflare_r2"; - // For R2, the user enters accountId in the 'endpoint' field (repurposed by UI logic). - // So we prefer accountId if available, otherwise endpoint. - if (this.storageEndpointInput) this.storageEndpointInput.value = endpoint || accountId || ""; + + // For R2, accountId is critical. + const resolvedEndpoint = endpoint || accountId || payloadAccountId || payloadEndpoint || ""; + + if (this.storageEndpointInput) this.storageEndpointInput.value = resolvedEndpoint; if (this.storageRegionInput) this.storageRegionInput.value = region || "auto"; if (this.storageAccessKeyInput) this.storageAccessKeyInput.value = accessKeyId || ""; if (this.storageSecretKeyInput) this.storageSecretKeyInput.value = secretAccessKey || "";