Skip to content

Commit 73707c5

Browse files
committed
fix: add Reinstall Credentials debug command and auto port credentials on mac version change
1 parent 1212843 commit 73707c5

File tree

4 files changed

+105
-2
lines changed

4 files changed

+105
-2
lines changed

src/command/Commands.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,9 @@ define(function (require, exports, module) {
504504
/** Shows the sidebar */
505505
exports.SHOW_SIDEBAR = "view.showSidebar"; // SidebarView.js show()
506506

507+
/** Reinstalls credentials in keychain */
508+
exports.REINSTALL_CREDS = "debug.reinstallCreds"; // login-service.js handleReinstallCreds()
509+
507510
// commands
508511
/** Initializes a new git repository */
509512
exports.CMD_GIT_INIT = "git-init";

src/extensions/default/DebugCommands/main.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,10 @@ define(function (require, exports, module) {
820820
}
821821
// this command is defined in core, but exposed only in Debug menu for now
822822
debugMenu.addMenuItem(Commands.FILE_OPEN_KEYMAP, null);
823+
// Reinstall credentials menu item (native apps only)
824+
if(Phoenix.isNativeApp) {
825+
debugMenu.addMenuItem(Commands.REINSTALL_CREDS, null);
826+
}
823827
const diagnosticsSubmenu = debugMenu.addSubMenu(Strings.CMD_DIAGNOSTIC_TOOLS, DIAGNOSTICS_SUBMENU);
824828
diagnosticsSubmenu.addMenuItem(DEBUG_RUN_UNIT_TESTS);
825829
CommandManager.register(Strings.CMD_BUILD_TESTS, DEBUG_BUILD_TESTS, TestBuilder.toggleTestBuilder);

src/phoenix/trust_ring.js

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ function _selectKeys() {
181181
const CRED_KEY_API = Phoenix.isTestWindow ? "API_KEY_TEST" : "API_KEY";
182182
const CRED_KEY_PROMO = Phoenix.isTestWindow ? "PROMO_GRANT_KEY_TEST" : "PROMO_GRANT_KEY";
183183
const SIGNATURE_SALT_KEY = Phoenix.isTestWindow ? "SIGNATURE_SALT_KEY_TEST" : "SIGNATURE_SALT_KEY";
184+
const VERSION_PORTER_KEY = Phoenix.isTestWindow ? "VERSION_PORTER_TEST" : "VERSION_PORTER";
184185
const { key, iv } = _selectKeys();
185186

186187
async function setCredential(credKey, secret) {
@@ -243,6 +244,80 @@ export async function initTrustRing() {
243244
// this will only work once in a window unless dismantleKeyring is called. So this is safe as
244245
// a public export as essentially this is a fn that only works in the boot and shutdown phase.
245246
await window.__TAURI__.tauri.invoke("trust_window_aes_key", {key, iv});
247+
248+
_portCredentials();
249+
}
250+
async function reinstallCreds() {
251+
if(!window.__TAURI__){
252+
throw new Error("reinstallCreds can only be called in tauri shell!");
253+
}
254+
// Read current credential values
255+
const apiKey = await getCredential(CRED_KEY_API);
256+
const promoKey = await getCredential(CRED_KEY_PROMO);
257+
const saltKey = await getCredential(SIGNATURE_SALT_KEY);
258+
259+
// Remove credentials from keychain
260+
if(apiKey) {
261+
await removeCredential(CRED_KEY_API);
262+
}
263+
if(promoKey) {
264+
await removeCredential(CRED_KEY_PROMO);
265+
}
266+
if(saltKey) {
267+
await removeCredential(SIGNATURE_SALT_KEY);
268+
}
269+
270+
// Re-set credentials to refresh keychain access
271+
if(apiKey) {
272+
await setCredential(CRED_KEY_API, apiKey);
273+
}
274+
if(promoKey) {
275+
await setCredential(CRED_KEY_PROMO, promoKey);
276+
}
277+
if(saltKey) {
278+
await setCredential(SIGNATURE_SALT_KEY, saltKey);
279+
}
280+
281+
const currentVersion = Phoenix.metadata.version;
282+
await setCredential(VERSION_PORTER_KEY, currentVersion);
283+
}
284+
285+
/**
286+
* Handles keychain credential portability across app versions on macOS.
287+
* not a problem in windows/linux.
288+
*
289+
* On macOS, the system keychain ties stored credentials to the app’s code signature.
290+
* If the signature changes (for example: running a debug build, unsigned dev build,
291+
* or re-signed binary), macOS will repeatedly prompt the user for their password
292+
* every time credentials are accessed. This does not usually happen in official
293+
* signed release builds, but it can be disruptive during development.
294+
*
295+
* To reduce this annoyance, we track the app version in the keychain. If the
296+
* stored version and the current version don’t match, we reinstall credentials
297+
* under the new signature so that future keychain access works without constant
298+
* prompts.
299+
*/
300+
async function _portCredentials() {
301+
if(!Phoenix.isNativeApp || Phoenix.platform === "win" || Phoenix.platform === "linux") {
302+
return;
303+
}
304+
try {
305+
const storedVersion = await getCredential(VERSION_PORTER_KEY);
306+
const currentVersion = Phoenix.metadata.version;
307+
308+
if (!storedVersion && currentVersion) {
309+
// First boot or version key doesn't exist, set it
310+
await setCredential(VERSION_PORTER_KEY, currentVersion);
311+
} else if (storedVersion && currentVersion && storedVersion !== currentVersion) {
312+
// Version changed, reinstall credentials
313+
console.log(`Version changed from ${storedVersion} to ${currentVersion}, reinstalling credentials`);
314+
// Update stored version first to prevent races with multi phoenix windows
315+
await setCredential(VERSION_PORTER_KEY, currentVersion);
316+
await reinstallCreds();
317+
}
318+
} catch (error) {
319+
console.error("Error during version-based credential check:", error);
320+
}
246321
}
247322

248323
/**
@@ -293,7 +368,8 @@ window.KernalModeTrust = {
293368
generateRandomKeyAndIV,
294369
dismantleKeyring,
295370
generateDataSignature,
296-
validateDataSignature
371+
validateDataSignature,
372+
reinstallCreds
297373
};
298374
if(Phoenix.isSpecRunnerWindow){
299375
window.specRunnerTestKernalModeTrust = window.KernalModeTrust;

src/services/login-service.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ define(function (require, exports, module) {
3030
require("./promotions");
3131
require("./login-utils");
3232
const NodeUtils = require("utils/NodeUtils"),
33-
PreferencesManager = require("preferences/PreferencesManager");
33+
PreferencesManager = require("preferences/PreferencesManager"),
34+
Commands = require("command/Commands"),
35+
CommandManager = require("command/CommandManager");
3436
const EntitlementsDirectImport = require("./EntitlementsManager"); // this adds Entitlements to KernalModeTrust
3537

3638
const Metrics = require("utils/Metrics"),
@@ -689,13 +691,31 @@ define(function (require, exports, module) {
689691
LoginService.getDeviceID = getDeviceID;
690692
LoginService.EVENT_ENTITLEMENTS_CHANGED = EVENT_ENTITLEMENTS_CHANGED;
691693

694+
async function handleReinstallCreds() {
695+
if(!Phoenix.isNativeApp) {
696+
throw new Error("Reinstall credentials is only available in native apps");
697+
}
698+
try {
699+
await KernalModeTrust.reinstallCreds();
700+
console.log("Credentials reinstalled successfully");
701+
} catch (error) {
702+
console.error("Error reinstalling credentials:", error);
703+
throw error;
704+
}
705+
}
706+
692707
let inited = false;
693708
function init() {
694709
if(inited){
695710
return;
696711
}
697712
inited = true;
698713
EntitlementsDirectImport.init();
714+
715+
// Register reinstall credentials command for native apps only
716+
if(Phoenix.isNativeApp) {
717+
CommandManager.register("Reinstall Credentials", Commands.REINSTALL_CREDS, handleReinstallCreds);
718+
}
699719
}
700720
// Test-only exports for integration testing
701721
if (Phoenix.isTestWindow) {

0 commit comments

Comments
 (0)