mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
[upload] Auto-unlock storage in the upload modal for a kept-unlocked session (#51)
The upload modal only READ storage's unlock state (isUnlocked) on load/open/refresh
— it never auto-unlocked the way the profile Storage pane does. So after a refresh
with "keep unlocked" ticked, the upload modal still opened with storage Locked even
though the signer had been restored.
Add UploadModal.maybeAutoUnlockStorage(pubkey), called on load(), open(), and
refreshState(): if storage is locked but a decrypt-capable signer is already present
(e.g. a kept-unlocked nsec restored on refresh), unlock silently. Guarded two ways:
- only when the account ALREADY exists (new storageService.hasStoredAccount) so it
never auto-creates storage for magnet/URL-only uploaders, and
- only when the signer is already available, so it never triggers a fresh
permission prompt on modal open.
Tests: storageService.hasStoredAccount (false before setup, true after unlock,
never creates an account, survives lock). Lint + build green.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
cebd3dc1e7
commit
6bdf28fb30
3 files changed
+84
-1
No files matched your search
@@ -526,6 +526,23 @@ export class StorageService {
|
||||
return this.masterKeys.has(pubkey);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if an encrypted storage account already exists for this pubkey. Lets
|
||||
* callers auto-unlock existing storage WITHOUT unlock() creating a brand-new
|
||||
* account for users who never set storage up (e.g. magnet/URL-only uploaders).
|
||||
*/
|
||||
async hasStoredAccount(pubkey) {
|
||||
if (!pubkey) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
const account = await this._getAccount(pubkey);
|
||||
return Boolean(account && account.encryptedMasterKey);
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a connection configuration.
|
||||
* Requires unlock() to be called first.
|
||||
|
||||
@@ -192,6 +192,7 @@ export class UploadModal {
|
||||
// Initial State
|
||||
if (this.storageService) {
|
||||
const pubkey = this.getCurrentPubkey ? this.getCurrentPubkey() : null;
|
||||
await this.maybeAutoUnlockStorage(pubkey);
|
||||
this.isStorageUnlocked = pubkey ? this.storageService.isUnlocked(pubkey) : false;
|
||||
await this.loadFromStorage();
|
||||
}
|
||||
@@ -713,6 +714,7 @@ export class UploadModal {
|
||||
async refreshState() {
|
||||
if (this.storageService) {
|
||||
const pubkey = this.getCurrentPubkey ? this.getCurrentPubkey() : null;
|
||||
await this.maybeAutoUnlockStorage(pubkey);
|
||||
this.isStorageUnlocked = pubkey ? this.storageService.isUnlocked(pubkey) : false;
|
||||
this.updateLockUi();
|
||||
|
||||
@@ -720,6 +722,43 @@ export class UploadModal {
|
||||
}
|
||||
}
|
||||
|
||||
// Silently unlock existing storage when a decrypt-capable signer is already
|
||||
// available (e.g. a kept-unlocked nsec restored on refresh, TODO #51) so the
|
||||
// upload modal opens Unlocked instead of Locked. Guarded on an EXISTING account
|
||||
// so it never creates storage for magnet/URL-only uploaders, and on the signer
|
||||
// already being present so it never triggers a fresh permission prompt.
|
||||
async maybeAutoUnlockStorage(pubkey) {
|
||||
if (!this.storageService || !pubkey) {
|
||||
return;
|
||||
}
|
||||
if (this.storageService.isUnlocked(pubkey)) {
|
||||
return;
|
||||
}
|
||||
const signer = getActiveSigner();
|
||||
if (
|
||||
!signer ||
|
||||
(typeof signer.nip44Decrypt !== "function" &&
|
||||
typeof signer.nip04Decrypt !== "function")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const hasAccount =
|
||||
typeof this.storageService.hasStoredAccount === "function"
|
||||
? await this.storageService.hasStoredAccount(pubkey)
|
||||
: false;
|
||||
if (!hasAccount) {
|
||||
return;
|
||||
}
|
||||
await this.storageService.unlock(pubkey, { signer });
|
||||
} catch (error) {
|
||||
devLogger?.log?.(
|
||||
"[UploadModal] Auto-unlock storage skipped:",
|
||||
error?.message || error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async loadFromStorage() {
|
||||
if (!this.storageService) return;
|
||||
const pubkey = this.getCurrentPubkey ? this.getCurrentPubkey() : null;
|
||||
@@ -1222,9 +1261,11 @@ export class UploadModal {
|
||||
this.setGlobalModalState("upload", true);
|
||||
this.isVisible = true;
|
||||
|
||||
// Refresh lock state on open
|
||||
// Refresh lock state on open. Auto-unlock first so a kept-unlocked nsec
|
||||
// (restored on refresh) opens Unlocked instead of Locked (TODO #51).
|
||||
if (this.storageService) {
|
||||
const pubkey = this.getCurrentPubkey ? this.getCurrentPubkey() : null;
|
||||
await this.maybeAutoUnlockStorage(pubkey);
|
||||
this.isStorageUnlocked = pubkey ? this.storageService.isUnlocked(pubkey) : false;
|
||||
this.updateLockUi();
|
||||
// Always attempt load to refresh configuration status (even if locked)
|
||||
|
||||
@@ -277,4 +277,29 @@ describe("StorageService", () => {
|
||||
/Storage is locked/,
|
||||
);
|
||||
});
|
||||
|
||||
// hasStoredAccount lets the upload modal auto-unlock EXISTING storage on refresh
|
||||
// without unlock() creating a fresh account for users who never set it up (#51).
|
||||
test("hasStoredAccount() is false before setup, true after, and never creates an account", async () => {
|
||||
assert.strictEqual(
|
||||
await storageService.hasStoredAccount(pubkey),
|
||||
false,
|
||||
"no account before any unlock",
|
||||
);
|
||||
// A read-only existence check must not create an account.
|
||||
assert.strictEqual(await storageService.hasStoredAccount(pubkey), false);
|
||||
|
||||
await storageService.unlock(pubkey, { signer: mockSigner });
|
||||
assert.strictEqual(
|
||||
await storageService.hasStoredAccount(pubkey),
|
||||
true,
|
||||
"account exists after unlock persisted the encrypted master key",
|
||||
);
|
||||
|
||||
// Survives a lock (existence is on-disk, not the in-memory master key).
|
||||
storageService.lock(pubkey);
|
||||
assert.strictEqual(await storageService.hasStoredAccount(pubkey), true);
|
||||
|
||||
assert.strictEqual(await storageService.hasStoredAccount(""), false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user