Skip to content

Commit 443877c

Browse files
committed
chore: login to phoenix code wiring in initial
1 parent 64576e3 commit 443877c

File tree

11 files changed

+258
-64
lines changed

11 files changed

+258
-64
lines changed

docs/API-Reference/command/Commands.md

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -824,18 +824,6 @@ Sorts working set by file type
824824
## CMD\_WORKING\_SORT\_TOGGLE\_AUTO
825825
Toggles automatic working set sorting
826826

827-
**Kind**: global variable
828-
<a name="CMD_TOGGLE_SHOW_WORKING_SET"></a>
829-
830-
## CMD\_TOGGLE\_SHOW\_WORKING\_SET
831-
Toggles working set visibility
832-
833-
**Kind**: global variable
834-
<a name="CMD_TOGGLE_SHOW_FILE_TABS"></a>
835-
836-
## CMD\_TOGGLE\_SHOW\_FILE\_TABS
837-
Toggles file tabs visibility
838-
839827
**Kind**: global variable
840828
<a name="CMD_TOGGLE_SHOW_WORKING_SET"></a>
841829

src/brackets.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ define(function (require, exports, module) {
139139
require("widgets/InlineMenu");
140140
require("thirdparty/tinycolor");
141141
require("utils/LocalizationUtils");
142+
require("services/login");
142143

143144
// DEPRECATED: In future we want to remove the global CodeMirror, but for now we
144145
// expose our required CodeMirror globally so as to avoid breaking extensions in the

src/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"app_title": "Phoenix Code",
44
"app_name_about": "Phoenix Code",
55
"about_icon": "styles/images/phoenix-icon.svg",
6+
"account_url": "https://account.phcode.io/",
67
"how_to_use_url": "https://github.com/adobe/brackets/wiki/How-to-Use-Brackets",
78
"docs_url": "https://docs.phcode.dev/",
89
"support_url": "https://github.com/phcode-dev/phoenix/discussions",
@@ -59,4 +60,4 @@
5960
"url": "https://github.com/phcode-dev/phoenix/blob/master/LICENSE"
6061
}
6162
]
62-
}
63+
}

src/extensionsIntegrated/Phoenix/main.js

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
*
1919
*/
2020

21-
/*global Phoenix*/
2221
/*eslint no-console: 0*/
2322
/*eslint strict: ["error", "global"]*/
2423
/* jshint ignore:start */
@@ -32,26 +31,10 @@ define(function (require, exports, module) {
3231
Strings = require("strings"),
3332
Dialogs = require("widgets/Dialogs"),
3433
NotificationUI = require("widgets/NotificationUI"),
35-
DefaultDialogs = require("widgets/DefaultDialogs"),
36-
ProfileMenu = require("./profile-menu");
34+
DefaultDialogs = require("widgets/DefaultDialogs");
3735

3836
const PERSIST_STORAGE_DIALOG_DELAY_SECS = 60000;
39-
let $icon;
4037

41-
function _addToolbarIcon() {
42-
const helpButtonID = "user-profile-button";
43-
$icon = $("<a>")
44-
.attr({
45-
id: helpButtonID,
46-
href: "#",
47-
class: "user",
48-
title: Strings.CMD_USER_PROFILE
49-
})
50-
.appendTo($("#main-toolbar .bottom-buttons"));
51-
$icon.on('click', ()=>{
52-
ProfileMenu.init();
53-
});
54-
}
5538
function _showUnSupportedBrowserDialogue() {
5639
if(Phoenix.browser.isMobile || Phoenix.browser.isTablet){
5740
Dialogs.showModalDialog(
@@ -109,7 +92,6 @@ define(function (require, exports, module) {
10992
if(Phoenix.isSpecRunnerWindow){
11093
return;
11194
}
112-
_addToolbarIcon();
11395
serverSync.init();
11496
defaultProjects.init();
11597
newProject.init();

src/nls/root/strings.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -629,7 +629,7 @@ define({
629629
"CMD_AUTO_UPDATE": "Auto Update",
630630
"CMD_HOW_TO_USE_BRACKETS": "How to Use {APP_NAME}",
631631
"CMD_SUPPORT": "{APP_NAME} Support",
632-
"CMD_USER_PROFILE": "User Profile",
632+
"CMD_USER_PROFILE": "{APP_NAME} Account",
633633
"CMD_DOCS": "Help, Getting Started",
634634
"CMD_SUGGEST": "Suggest a Feature",
635635
"CMD_REPORT_ISSUE": "Report Issue",
@@ -1564,5 +1564,9 @@ define({
15641564
"GIT_TOAST_MESSAGE": "Click the Git panel icon to manage your repository. Easily commit, push, pull, and view your project history—all in one place.<br><a href='https://docs.phcode.dev/docs/Features/git'>Learn more about the Git panel →</a>",
15651565

15661566
// surveys
1567-
"SURVEY_TITLE_VOTE_FOR_FEATURES_YOU_WANT": "Vote for the features you want to see next!"
1567+
"SURVEY_TITLE_VOTE_FOR_FEATURES_YOU_WANT": "Vote for the features you want to see next!",
1568+
1569+
// login
1570+
"SIGNED_OUT": "You have been signed out.",
1571+
"SIGNED_OUT_MESSAGE": "You have been signed out of your {APP_NAME} account. Please sign in again to continue."
15681572
});

src/phoenix/trust_ring.js

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -178,17 +178,18 @@ function _selectKeys() {
178178
return generateRandomKeyAndIV();
179179
}
180180

181-
const PHCODE_API_KEY = "PHCODE_API_KEY";
181+
const CRED_KEY_API = "API_KEY";
182182
const { key, iv } = _selectKeys();
183183
// this key is set at boot time as a truct base for all the core components before any extensions are loaded.
184184
// just before extensions are loaded, this key is blanked. This can be used by core modules to talk with other
185185
// core modules securely without worrying about interception by extensions.
186186
// KernalModeTrust should only be available within all code that loads before the first default/any extension.
187187
window.KernalModeTrust = {
188+
CRED_KEY_API,
188189
aesKeys: { key, iv },
189-
setPhoenixAPIKey,
190-
getPhoenixAPIKey,
191-
removePhoenixAPIKey,
190+
setCredential,
191+
getCredential,
192+
removeCredential,
192193
AESDecryptString,
193194
generateRandomKeyAndIV,
194195
dismantleKeyring
@@ -198,29 +199,38 @@ if(Phoenix.isSpecRunnerWindow){
198199
}
199200
// key is 64 hex characters, iv is 24 hex characters
200201

201-
async function setPhoenixAPIKey(apiKey) {
202+
async function setCredential(credKey, secret) {
202203
if(!window.__TAURI__){
203204
throw new Error("Phoenix API key can only be set in tauri shell!");
204205
}
205-
return window.__TAURI__.tauri.invoke("store_credential", {scopeName: PHCODE_API_KEY, secretVal: apiKey});
206+
if(!credKey){
207+
throw new Error("credKey is required to set credential!");
208+
}
209+
return window.__TAURI__.tauri.invoke("store_credential", {scopeName: credKey, secretVal: secret});
206210
}
207211

208-
async function getPhoenixAPIKey() {
212+
async function getCredential(credKey) {
209213
if(!window.__TAURI__){
210214
throw new Error("Phoenix API key can only be get in tauri shell!");
211215
}
212-
const encryptedKey = await window.__TAURI__.tauri.invoke("get_credential", {scopeName: PHCODE_API_KEY});
216+
if(!credKey){
217+
throw new Error("credKey is required to get credential!");
218+
}
219+
const encryptedKey = await window.__TAURI__.tauri.invoke("get_credential", {scopeName: credKey});
213220
if(!encryptedKey){
214221
return null;
215222
}
216223
return AESDecryptString(encryptedKey, key, iv);
217224
}
218225

219-
async function removePhoenixAPIKey() {
226+
async function removeCredential(credKey) {
220227
if(!window.__TAURI__){
221228
throw new Error("Phoenix API key can only be set in tauri shell!");
222229
}
223-
return window.__TAURI__.tauri.invoke("delete_credential", {scopeName: PHCODE_API_KEY});
230+
if(!credKey){
231+
throw new Error("credKey is required to remove credential!");
232+
}
233+
return window.__TAURI__.tauri.invoke("delete_credential", {scopeName: credKey});
224234
}
225235

226236
let _dismatled = false;

src/extensionsIntegrated/Phoenix/html/login-dialog.html renamed to src/services/html/login-popup.html

File renamed without changes.
File renamed without changes.

src/services/login.js

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* GNU AGPL-3.0 License
3+
*
4+
* Copyright (c) 2021 - present core.ai . All rights reserved.
5+
*
6+
* This program is free software: you can redistribute it and/or modify it under
7+
* the terms of the GNU Affero General Public License as published by the Free
8+
* Software Foundation, either version 3 of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
11+
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12+
* See the GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License along
15+
* with this program. If not, see https://opensource.org/licenses/AGPL-3.0.
16+
*
17+
*/
18+
19+
20+
define(function (require, exports, module) {
21+
const EventDispatcher = require("utils/EventDispatcher"),
22+
PreferencesManager = require("preferences/PreferencesManager"),
23+
Dialogs = require("widgets/Dialogs"),
24+
DefaultDialogs = require("widgets/DefaultDialogs"),
25+
Strings = require("strings"),
26+
ProfileMenu = require("./profile-menu");
27+
28+
const KernalModeTrust = window.KernalModeTrust;
29+
if(!KernalModeTrust){
30+
// integrated extensions will have access to kernal mode, but not external extensions
31+
throw new Error("Login service should have access to KernalModeTrust. Cannot boot without trust ring");
32+
}
33+
const secureExports = {};
34+
KernalModeTrust.loginService = secureExports;
35+
// user profile is something like "apiKey": "uuid...", validationCode: "dfdf", "firstName":"Aa","lastName":"bb",
36+
// "email":"[email protected]", "customerID":"uuid...","loginTime":1750074393853,
37+
// "profileIcon":{"color":"#14b8a6","initials":"AB"}
38+
let userProfile = null;
39+
let isLoggedInUser = false;
40+
41+
// just used as trigger to notify different windows about user profile changes
42+
const PREF_USER_PROFILE_VERSION = "userProfileVersion";
43+
44+
45+
function isLoggedIn() {
46+
return isLoggedInUser;
47+
}
48+
49+
const ERR_RETRY_LATER = "retry_later";
50+
const ERR_INVALID = "invalid";
51+
52+
/**
53+
* Resolves the provided API key and verification code to user profile data
54+
*
55+
* @param {string} apiKey - The API key to be validated.
56+
* @param {string} validationCode - The verification code associated with the API key.
57+
* @return {Promise<Object>} A promise resolving to an object containing the user details if successful,
58+
* or an error object with the relevant error code (`ERR_RETRY_LATER` or `ERR_INVALID`) if the operation fails.
59+
* never rejects.
60+
*/
61+
async function _resolveAPIKey(apiKey, validationCode) {
62+
const resolveURL = `${Phoenix.config.account_url}resolveAppSessionID?appSessionID=${apiKey}&validationCode=${validationCode}`;
63+
if (!navigator.onLine) {
64+
return {err: ERR_RETRY_LATER};
65+
}
66+
try {
67+
const response = await fetch(resolveURL);
68+
if (response.status === 400 || response.status === 404) {
69+
// 404 api key not found and 400 Bad Request, eg: verification code mismatch
70+
return {err: ERR_INVALID};
71+
} else if (response.ok) {
72+
const userDetails = await response.json();
73+
userDetails.apiKey = apiKey;
74+
userDetails.validationCode = validationCode;
75+
return {userDetails};
76+
}
77+
// Other errors like 500 are retriable
78+
console.log('Other error:', response.status);
79+
return {err: ERR_RETRY_LATER};
80+
} catch (e) {
81+
console.error(e, "Failed to call resolve API endpoint", resolveURL);
82+
return {err: ERR_RETRY_LATER};
83+
}
84+
}
85+
86+
async function _verifyLogin() {
87+
const savedUserProfile = await KernalModeTrust.getCredential(KernalModeTrust.CRED_KEY_API);
88+
if(!savedUserProfile){
89+
console.log("No savedUserProfile found. Not logged in");
90+
ProfileMenu.setNotLoggedIn();
91+
return;
92+
}
93+
try {
94+
userProfile = JSON.parse(savedUserProfile);
95+
} catch (e) {
96+
console.error(e, "Failed to parse saved user profile credentials");// this should never happen
97+
ProfileMenu.setNotLoggedIn();
98+
return; // not logged in if parse fails
99+
}
100+
isLoggedInUser = true;
101+
// api key is present, verify if the key is valid. but just show user that we are logged in with
102+
// stored credentials.
103+
ProfileMenu.setLoggedIn(userProfile.profileIcon.initials, userProfile.profileIcon.color);
104+
const resolveResponse = await _resolveAPIKey(userProfile.apiKey, userProfile.validationCode);
105+
if(resolveResponse.userDetails) {
106+
// a valid user account is in place. update the stored credentials
107+
userProfile = resolveResponse.userDetails;
108+
ProfileMenu.setLoggedIn(userProfile.profileIcon.initials, userProfile.profileIcon.color);
109+
await KernalModeTrust.setCredential(KernalModeTrust.CRED_KEY_API, JSON.stringify(userProfile));
110+
return;
111+
}
112+
// some error happened.
113+
if(resolveResponse.err === ERR_INVALID) { // the api key is invalid, we need to logout and tell user
114+
ProfileMenu.setNotLoggedIn();
115+
Dialogs.showModalDialog(
116+
DefaultDialogs.DIALOG_ID_ERROR,
117+
Strings.SIGNED_OUT,
118+
Strings.SIGNED_OUT_MESSAGE
119+
);
120+
await KernalModeTrust.removeCredential(KernalModeTrust.CRED_KEY_API);
121+
}
122+
// maybe some intermittent network error, ERR_RETRY_LATER is here. do nothing
123+
}
124+
125+
function init() {
126+
ProfileMenu.init();
127+
if(!Phoenix.isNativeApp){
128+
console.warn("Login service is not supported in browser");
129+
return;
130+
}
131+
_verifyLogin().catch(console.error);// todo raise metrics
132+
const pref = PreferencesManager.stateManager.definePreference(PREF_USER_PROFILE_VERSION, 'string', '0')
133+
.watchExternalChanges();
134+
135+
}
136+
137+
init();
138+
139+
// no sensitive apis or events should be triggered from the public exports of this module as extensions
140+
// can read them. Always use KernalModeTrust.loginService for sensitive apis.
141+
EventDispatcher.makeEventDispatcher(exports);
142+
EventDispatcher.makeEventDispatcher(secureExports);
143+
144+
// kernal exports
145+
secureExports.isLoggedIn = isLoggedIn;
146+
// public exports
147+
exports.isLoggedIn = isLoggedIn;
148+
149+
});

0 commit comments

Comments
 (0)