mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-11-07 21:32:51 +00:00
uploads: locked-storage file picks unlock inline instead of dead-ending (#56)
All three paths (upload video pick, upload thumbnail pick, edit "Replace file") now route through the shared signer gate — silent kept-unlocked restore, else ONE passphrase prompt — then unlock storage and continue the same pick. Cancel/bad-passphrase abort quietly; a refused thumbnail pick restores the picker. Unlock glue extracted to uploadModalStorageUnlock.js (file-size budget); ensureSigner threaded via initUploadModal/initEditModal. Tests: tests/upload-edit-storage-unlock-gate.test.mjs (12). Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
1 parent
7ead34378e
commit
e32298cc89
7 files changed
+496
-43
No files matched your search
@@ -0,0 +1,106 @@
|
||||
// js/ui/components/uploadModalStorageUnlock.js
|
||||
//
|
||||
// Storage-unlock glue for the Upload modal (#36 / #56), kept out of
|
||||
// UploadModal.js to stay within its file-size budget. Every function operates
|
||||
// on the UploadModal instance (`modal`); UploadModal exposes them as thin
|
||||
// prototype delegates so callers and tests keep using the instance methods.
|
||||
|
||||
import { devLogger, userLogger } from "../../utils/logger.js";
|
||||
|
||||
// #56: the file pickers unlock storage inline instead of dead-ending. The
|
||||
// shared signer gate (app.ensureEncryptionCapableSigner) restores a cached
|
||||
// keep-unlocked key silently or shows ONE passphrase prompt; handleUnlock
|
||||
// then unlocks storage with the restored signer (extension permissions +
|
||||
// credential reload included). Returns true when the upload can proceed.
|
||||
// Cancel / bad-passphrase abort quietly (the gate surfaces its own toast for
|
||||
// a wrong passphrase); other gate failures fall through to handleUnlock so
|
||||
// its existing messaging (saved-key flow / "no signer") still applies.
|
||||
export async function ensureStorageUnlockedForUpload(modal) {
|
||||
if (!modal.storageService) return false;
|
||||
const pubkey = modal.getCurrentPubkey ? modal.getCurrentPubkey() : null;
|
||||
if (!pubkey) return false;
|
||||
|
||||
if (!modal.storageService.isUnlocked(pubkey)) {
|
||||
if (typeof modal.ensureSigner === "function") {
|
||||
let gate = null;
|
||||
try {
|
||||
gate = await modal.ensureSigner({
|
||||
pubkey,
|
||||
need: "encrypt",
|
||||
promptMessage:
|
||||
"Re-enter your PIN / passphrase to unlock storage for this upload.",
|
||||
});
|
||||
} catch (error) {
|
||||
devLogger?.warn?.("[UploadModal] Signer gate failed:", error);
|
||||
}
|
||||
if (
|
||||
gate &&
|
||||
gate.ok !== true &&
|
||||
(gate.reason === "cancelled" || gate.reason === "bad-passphrase")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
await modal.handleUnlock();
|
||||
}
|
||||
|
||||
const unlocked = modal.storageService.isUnlocked(pubkey);
|
||||
if (unlocked && (!modal.isStorageUnlocked || !modal.activeCredentials)) {
|
||||
modal.isStorageUnlocked = true;
|
||||
modal.updateLockUi();
|
||||
await modal.loadFromStorage();
|
||||
}
|
||||
modal.isStorageUnlocked = unlocked;
|
||||
return unlocked;
|
||||
}
|
||||
|
||||
// Undo the picker-side effects of a thumbnail selection that couldn't upload
|
||||
// (placeholder shows the picked filename and the URL input is disabled).
|
||||
export function resetThumbnailPicker(modal) {
|
||||
if (modal.inputs?.thumbnailFile) {
|
||||
modal.inputs.thumbnailFile.value = "";
|
||||
}
|
||||
const thumbnail = modal.inputs?.thumbnail;
|
||||
if (thumbnail) {
|
||||
thumbnail.placeholder = "https://example.com/thumbnail.jpg";
|
||||
thumbnail.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if a locked persisted-nsec session was detected (for the active
|
||||
// account) and the re-unlock (passphrase) flow was opened, so the caller can
|
||||
// stop instead of dead-ending with "No signer available".
|
||||
export function promptStoredNsecUnlock(modal, pubkey) {
|
||||
const client = modal.authService?.nostrClient;
|
||||
if (!client || typeof client.getStoredSessionActorMetadata !== "function") {
|
||||
return false;
|
||||
}
|
||||
let meta = null;
|
||||
try {
|
||||
meta = client.getStoredSessionActorMetadata();
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
if (!meta || meta.hasEncryptedKey !== true || meta.source !== "nsec") {
|
||||
return false;
|
||||
}
|
||||
const metaPubkey =
|
||||
typeof meta.pubkey === "string" ? meta.pubkey.trim().toLowerCase() : "";
|
||||
const activePubkey =
|
||||
typeof pubkey === "string" ? pubkey.trim().toLowerCase() : "";
|
||||
if (activePubkey && metaPubkey && metaPubkey !== activePubkey) {
|
||||
return false;
|
||||
}
|
||||
if (typeof modal.onRequestUnlock !== "function") {
|
||||
return false;
|
||||
}
|
||||
modal.showError(
|
||||
"Your saved key is locked after reloading. Re-enter your passphrase to unlock it, then try the upload again.",
|
||||
);
|
||||
try {
|
||||
modal.onRequestUnlock();
|
||||
} catch (err) {
|
||||
userLogger.warn("[UploadModal] Failed to open saved-key unlock flow:", err);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Reference in new issue
Block a user