@@ -26,11 +26,14 @@ async function checkKeypairStatus() {
2626 const response = await chrome . runtime . sendMessage ( { type : 'GET_KEYPAIR_STATUS' } ) ;
2727 if ( DEBUG ) console . log ( '[Podkey Popup] Keypair status response:' , response ) ;
2828
29- if ( response . exists ) {
30- console . log ( '[Podkey Popup] Keypair exists, showing main screen' ) ;
29+ // Three states: 'unlocked' -> main, 'locked' -> unlock, 'none' -> setup.
30+ // (`state` falls back to exists for forward/back compatibility.)
31+ const state = response . state || ( response . exists ? 'unlocked' : 'none' ) ;
32+ if ( state === 'unlocked' ) {
3133 showMainScreen ( response ) ;
34+ } else if ( state === 'locked' ) {
35+ showUnlockScreen ( response ) ;
3236 } else {
33- console . log ( '[Podkey Popup] No keypair, showing setup screen' ) ;
3437 showSetupScreen ( ) ;
3538 }
3639 } catch ( error ) {
@@ -52,6 +55,33 @@ function showSetupScreen() {
5255 console . log ( '[Podkey Popup] Setup screen display set to block' ) ;
5356}
5457
58+ /**
59+ * Show generate screen (set an encryption passphrase for a new key)
60+ */
61+ function showGenerateScreen ( ) {
62+ hideAllScreens ( ) ;
63+ document . getElementById ( 'generatePassphrase' ) . value = '' ;
64+ document . getElementById ( 'generatePassphraseConfirm' ) . value = '' ;
65+ document . getElementById ( 'generateScreen' ) . style . display = 'block' ;
66+ currentScreen = 'generate' ;
67+ document . getElementById ( 'generatePassphrase' ) . focus ( ) ;
68+ }
69+
70+ /**
71+ * Show unlock screen (encrypted vault present but locked)
72+ */
73+ function showUnlockScreen ( status ) {
74+ hideAllScreens ( ) ;
75+ const pk = status && status . publicKey ;
76+ document . getElementById ( 'unlockIdentity' ) . textContent = pk
77+ ? `Unlock ${ pk . slice ( 0 , 8 ) } …${ pk . slice ( - 4 ) } `
78+ : 'Enter your passphrase to unlock your key' ;
79+ document . getElementById ( 'unlockPassphrase' ) . value = '' ;
80+ document . getElementById ( 'unlockScreen' ) . style . display = 'block' ;
81+ currentScreen = 'unlock' ;
82+ document . getElementById ( 'unlockPassphrase' ) . focus ( ) ;
83+ }
84+
5585/**
5686 * Show import screen
5787 */
@@ -97,9 +127,20 @@ function hideAllScreens() {
97127function setupEventListeners ( ) {
98128 console . log ( '[Podkey Popup] Setting up event listeners' ) ;
99129 // Setup screen
100- document . getElementById ( 'generateBtn' ) . addEventListener ( 'click' , handleGenerate ) ;
130+ document . getElementById ( 'generateBtn' ) . addEventListener ( 'click' , ( ) => showGenerateScreen ( ) ) ;
101131 document . getElementById ( 'importBtn' ) . addEventListener ( 'click' , ( ) => showImportScreen ( ) ) ;
102132
133+ // Generate screen
134+ document . getElementById ( 'generateConfirmBtn' ) . addEventListener ( 'click' , handleGenerate ) ;
135+ document . getElementById ( 'generateCancelBtn' ) . addEventListener ( 'click' , ( ) => showSetupScreen ( ) ) ;
136+
137+ // Unlock screen
138+ document . getElementById ( 'unlockBtn' ) . addEventListener ( 'click' , handleUnlock ) ;
139+ document . getElementById ( 'unlockPassphrase' ) . addEventListener ( 'keydown' , ( e ) => {
140+ if ( e . key === 'Enter' ) handleUnlock ( ) ;
141+ } ) ;
142+ document . getElementById ( 'forgetKeyBtn' ) . addEventListener ( 'click' , handleForgetKey ) ;
143+
103144 // Import screen
104145 document . getElementById ( 'importConfirmBtn' ) . addEventListener ( 'click' , handleImport ) ;
105146 document . getElementById ( 'importCancelBtn' ) . addEventListener ( 'click' , ( ) => showSetupScreen ( ) ) ;
@@ -108,36 +149,117 @@ function setupEventListeners() {
108149 document . getElementById ( 'copyBtn' ) . addEventListener ( 'click' , handleCopy ) ;
109150 document . getElementById ( 'autoSignToggle' ) . addEventListener ( 'change' , handleAutoSignToggle ) ;
110151 document . getElementById ( 'exportBtn' ) . addEventListener ( 'click' , handleExport ) ;
152+ document . getElementById ( 'lockBtn' ) . addEventListener ( 'click' , handleLock ) ;
111153}
112154
113155/**
114- * Handle generate new keypair
156+ * Validate a passphrase + confirmation pair. Returns the passphrase or null
157+ * (after alerting) when invalid.
158+ */
159+ function readPassphrase ( passId , confirmId ) {
160+ const pass = document . getElementById ( passId ) . value ;
161+ const confirm = document . getElementById ( confirmId ) . value ;
162+ if ( pass . length < 8 ) {
163+ alert ( 'Passphrase must be at least 8 characters.' ) ;
164+ return null ;
165+ }
166+ if ( pass !== confirm ) {
167+ alert ( 'Passphrases do not match.' ) ;
168+ return null ;
169+ }
170+ return pass ;
171+ }
172+
173+ /**
174+ * Handle generate new keypair (encrypts it under the chosen passphrase)
115175 */
116176async function handleGenerate ( ) {
117- const btn = document . getElementById ( 'generateBtn' ) ;
118- const labelEl = btn . querySelector ( '.btn-label' ) ;
119- const original = labelEl ? labelEl . textContent : btn . textContent ;
177+ const passphrase = readPassphrase ( 'generatePassphrase' , 'generatePassphraseConfirm' ) ;
178+ if ( ! passphrase ) return ;
179+
180+ const btn = document . getElementById ( 'generateConfirmBtn' ) ;
181+ const original = btn . textContent ;
120182 try {
121- if ( labelEl ) labelEl . textContent = 'Generating…' ;
183+ btn . textContent = 'Generating…' ;
122184 btn . disabled = true ;
123185
124- const response = await chrome . runtime . sendMessage ( { type : 'GENERATE_KEYPAIR' } ) ;
186+ const response = await chrome . runtime . sendMessage ( { type : 'GENERATE_KEYPAIR' , passphrase } ) ;
125187
126188 if ( DEBUG ) console . log ( '[Podkey] Keypair generated:' , response . publicKey ) ;
127189
128- // Show main screen
129190 await showMainScreen ( {
130191 exists : true ,
131192 publicKey : response . publicKey ,
132193 did : response . did
133194 } ) ;
134195 } catch ( error ) {
135196 alert ( 'Error generating keypair: ' + error . message ) ;
136- if ( labelEl ) labelEl . textContent = original ;
197+ } finally {
198+ btn . textContent = original ;
137199 btn . disabled = false ;
138200 }
139201}
140202
203+ /**
204+ * Handle unlock: decrypt the vault into the session with the passphrase
205+ */
206+ async function handleUnlock ( ) {
207+ const passphrase = document . getElementById ( 'unlockPassphrase' ) . value ;
208+ if ( ! passphrase ) {
209+ alert ( 'Please enter your passphrase.' ) ;
210+ return ;
211+ }
212+
213+ const btn = document . getElementById ( 'unlockBtn' ) ;
214+ const original = btn . textContent ;
215+ try {
216+ btn . textContent = 'Unlocking…' ;
217+ btn . disabled = true ;
218+
219+ const response = await chrome . runtime . sendMessage ( { type : 'UNLOCK_VAULT' , passphrase } ) ;
220+
221+ document . getElementById ( 'unlockPassphrase' ) . value = '' ;
222+ await showMainScreen ( {
223+ exists : true ,
224+ publicKey : response . publicKey ,
225+ did : response . did
226+ } ) ;
227+ } catch ( error ) {
228+ // 'Incorrect passphrase' from the background — keep it on-screen to retry.
229+ alert ( error . message || 'Unlock failed.' ) ;
230+ } finally {
231+ btn . textContent = original ;
232+ btn . disabled = false ;
233+ }
234+ }
235+
236+ /**
237+ * Handle lock: drop the in-memory key (vault stays encrypted on disk)
238+ */
239+ async function handleLock ( ) {
240+ await chrome . runtime . sendMessage ( { type : 'LOCK_VAULT' } ) ;
241+ const status = await chrome . runtime . sendMessage ( { type : 'GET_KEYPAIR_STATUS' } ) ;
242+ showUnlockScreen ( status ) ;
243+ }
244+
245+ /**
246+ * Handle "forget key & start over": wipe the encrypted vault and identity so
247+ * the user can generate or import a different key. Irreversible.
248+ */
249+ async function handleForgetKey ( event ) {
250+ if ( event ) event . preventDefault ( ) ;
251+ const confirmed = confirm (
252+ '⚠️ This deletes the encrypted key stored on this device.\n\n' +
253+ 'If you have not backed up your private key, you will lose access to this ' +
254+ 'identity permanently.\n\nContinue?'
255+ ) ;
256+ if ( ! confirmed ) return ;
257+
258+ await chrome . storage . session . remove ( [ 'podkey_private_key' ] ) ;
259+ await chrome . storage . local . remove ( [ 'podkey_vault' , 'podkey_public_key' ] ) ;
260+ showSetupScreen ( ) ;
261+ }
262+
141263/**
142264 * Handle import keypair
143265 */
@@ -150,19 +272,25 @@ async function handleImport() {
150272 return ;
151273 }
152274
275+ const passphrase = readPassphrase ( 'importPassphrase' , 'importPassphraseConfirm' ) ;
276+ if ( ! passphrase ) return ;
277+
153278 const btn = document . getElementById ( 'importConfirmBtn' ) ;
154279 btn . textContent = 'Importing...' ;
155280 btn . disabled = true ;
156281
157282 const response = await chrome . runtime . sendMessage ( {
158283 type : 'IMPORT_KEYPAIR' ,
159- privateKey
284+ privateKey,
285+ passphrase
160286 } ) ;
161287
162288 if ( DEBUG ) console . log ( '[Podkey] Keypair imported:' , response . publicKey ) ;
163289
164- // Clear input
290+ // Clear inputs (private key + passphrases)
165291 document . getElementById ( 'privateKeyInput' ) . value = '' ;
292+ document . getElementById ( 'importPassphrase' ) . value = '' ;
293+ document . getElementById ( 'importPassphraseConfirm' ) . value = '' ;
166294
167295 // Show main screen
168296 await showMainScreen ( {
@@ -229,15 +357,15 @@ async function handleExport() {
229357 if ( ! confirmed ) return ;
230358
231359 try {
232- // Private key is now stored in session storage (in-memory only).
233- // Try session storage first, fall back to local for legacy installs .
360+ // The unlocked private key lives in session storage. If the vault is locked
361+ // (e.g. after a browser restart) there is nothing to export until unlocked .
234362 let { podkey_private_key : privateKey } = await chrome . storage . session . get ( [ 'podkey_private_key' ] ) ;
235363 if ( ! privateKey ) {
236364 ( { podkey_private_key : privateKey } = await chrome . storage . local . get ( [ 'podkey_private_key' ] ) ) ;
237365 }
238366
239367 if ( ! privateKey ) {
240- alert ( 'No private key found. The key may have been cleared when the browser restarted. Please re-import your key .' ) ;
368+ alert ( 'Podkey is locked. Unlock with your passphrase first, then export .' ) ;
241369 return ;
242370 }
243371
0 commit comments