Closed kavimuru closed 2 years ago
Triggered auto assignment to @marcochavezf (Engineering
), see https://stackoverflow.com/c/expensify/questions/4319 for more details.
I also noticed the password manager is not triggered when I enter the email in the desktop app and here's a discussion about a possible solution to enable autofill.
I think it's worth to make this issue External
to see if we get a proposal to enable autofill on desktop. But feel free to close it if there's no possible solution.
Triggered auto assignment to @michaelhaxhiu (External
), see https://stackoverflow.com/c/expensify/questions/8582 for more details.
Posted to upwork - https://www.upwork.com/jobs/~01a9e983bc39d24141
Triggered auto assignment to Contributor-plus team member for initial proposal review - @parasharrajat (Exported
)
Triggered auto assignment to @iwiznia (Exported
), see https://stackoverflow.com/c/expensify/questions/7972 for more details.
The electron does not support autofill natively. This is a userland feature and saving username and passed is a security concern.
The electron does not support autofill natively. This is a userland feature and saving username and passed is a security concern.
Ah yeah, definitively saving usernames and passwords in the electron app is not a good idea, but shouldn't password managers (like 1Password) detect the username/email field to autofill it automatically in macOS apps?
Yes! the motive of this issue should be to set up the app to support native autofill services.
Still waiting for proposals. I think we need to increase price, no?
Yep, true. I got the reminder to double price on Friday last week but didn't have time to action it. Doubling now.
Not overdue as I just doubled the price
Doubling.
Doubling price again
Doubling
Not overdue
Doubling
Is this issue still open to proposals? I might have a proposal. Asking because "Exported" label was removed.
Yes, it is open
@michaelhaxhiu let's pin this issue and post it in slack to bring to attention.
No one interested to catch this whale :scream:
@parasharrajat, i have been researching on this issue. I would post a proposal soon most prolly today or tomorrow .
Good call, I just pinned it. I'm going to double the value too, as it's been more than 7 days.
Electron doesn't provide an interface for autofill because according to one of its main developers:
Electron doesn't ship the autofill feature of Chrome browser because only very few cases need it
Therefore, this feature has to be implemented on our side.
We have two options:
datalist
along with node-keytar for managing and storing passwords using the system keychain.In my case, I would rather go with the second one because this way we would be using the system keychain and therefore we would not have to manage and store passwords.
Here is a tutorial about node-keytar
that breaks it down and explain some things in detail.
Also, I had found a repository that implemented autofill for Electron using node-keytar
but I can't find it now. I'll keep trying to find it and I'll add it as reference to my proposal.
I'll be listening to any questions and clarifying any details about my proposal.
We are not up for the first approach. This could create serious security concerns. Let's hear about the second approach.
you can go as much as detailed as you like or choose to use an external document if the proposal does not fit into the post.
To save the credentials, I propose to utilize Electron's safeStorage
module to save the password or payment information. This module protects data stored on disk from being accessed by other applications or users with full disk access. Note that on Mac, access to the system Keychain is required (the same applies for Linux and Windows).
Create UI components for [save password] and [auto fill password]. Store encrypted information in AsyncStorage via Onyx.
(first string in the terminal above is encrypted console.log, second is decrypted)
safeStorage.isEncryptionAvailable()
safeStorage.encryptString(plainText)
{key: 'credit-card', title: 'VISA **72', encrypted: '=123aosidj123'}
{username, password}
{number, name, month, year, cvv}
keychain → safestorage → onyx
interactions{username, password}
=123aosidj123
{login, password}
formatsecureTextEntry
and autoCompleteType
props process.env.ELECTRON_ENV
// BaseTextInput [pseudo-code]
componentDidMount() {
if (
isElectronEnvironment && // only subscribe within electron environment
this.props.autoCompleteType === 'password' // only subscribe on password or credit card information
) {
Autofill.subscribeCredentialSelect(this.props.name, this.onAutofill) // this event will be triggered when user selects credential from UI Component
}
}
componentWillUnmount() {
Autofill.unsubscribeCredentialSelect(this.props.name, this.onAutofill) // unsubscribe when component is unmounted
}
onAutofill(response) {
this.input.value = response // override with autofill response
}
onFocus() {
if (
isElectronEnvironment && // same conditions as in componentDidMount
!this.input.value.length && // could be modified, but I assume user wants to auto-fill before typing
this.props.autoCompleteType === 'password'
) {
Autofill.showPopup(this.props.autoCompleteType) // Shows autofill popup with available credentials, such as username or a credit card
}
}
render () {
return <TextInput {...props} />
}
let secureCredentials = [];
loadCredentials() {
Onyx.connect({
key: ONYXKEYS.SECURE_CREDENTIALS,
callback: credentials => secureCredentials = credentials,
});
}
getCredentialsOverview(credentialType = 'credit-card') {
let credentials = secure.filter(item => item.type === credit-card) // filter by credential type e.g. so we don't return username
return credentials // encrypted values to show in UI e.g. 'VISA **72'
}
getCredential(credentialKey) {
let credential = secure.find(item => item.key === credentialKey) // find credential to be decrypted
safeStorage.decryptString(credential)
}
saveCredential(string) {
let credential = safeStorage.encryptString(string)
Onyx.merge(ONYXKEYS.SECURE_CREDENTIALS, [credential])
}
src/components/Popover.js
createPortal(
<Modal
type={CONST.MODAL.MODAL_TYPE.SECURE_AUTOFILL}
popoverAnchorPosition={props.anchorPosition}
shouldCloseOnOutsideClick
/>,
document.body, // could be rendered as a global element, or straight into input field
);
node
processes or chromium
instances can access @parasharrajat can you please review @azimgd 's proposal above?
Thanks for the proposal. Sounds like a plan but I will have to do some research on this to understand.
Intitial Questions:
I propose to utilize Electron's safeStorage module to save the password or payment information.
But it looks are you are only using this module to encrypt. Then storing things on Onyx. Correct?
Do you have any example of a desktop app where autofill is working?
https://user-images.githubusercontent.com/4882133/195590342-655d256e-4ee5-46c8-a135-9b5b3fc15a2a.mov
But it looks are you are only using this module to encrypt. Then storing things on Onyx. Correct?
Yes. Safe storage uses keychain to store an app specific cypher which is later utilized for any consecutive encryptString
and decryptString
method calls.
Storing encrypted payload as ArrayBuffer within IndexedDB (which is supported by Onyx via LocalForage). Note that decrypting requires correct cypher text stored in Keychain.
Here is an error I get when I try to decrypt a string with corrupt cypher (changed the cypher manually using Keychain Access).
Error: Error while decrypting the ciphertext provided to safeStorage.decryptString.
OS Prompt which requests access to Keychain
Note: communication between node → react is done using ipcRenderer
. sendSync
blocks the node thread until callback is resolved.
// libs/SecureCredentials.js
import Onyx from 'react-native-onyx';
import ONYXKEYS from '../ONYXKEYS';
import ELECTRON_EVENTS from '../../desktop/ELECTRON_EVENTS';
let secureCredentials;
Onyx.connect({
key: ONYXKEYS.SECURE_CREDENTIALS,
waitForCollectionCallback: true,
callback: val => secureCredentials = val,
});
function storeEncryptedCredentials() {
const encrypted = window.electron.sendSync(ELECTRON_EVENTS.REQUEST_SECURE_CREDENTIALS, JSON.stringify({authorization: {username: 'azim', password: '123'}}));
// eslint-disable-next-line rulesdir/prefer-actions-set-data
Onyx.merge(ONYXKEYS.SECURE_CREDENTIALS, encrypted);
}
function fetchDecryptedCredentials() {
const decrypted = window.electron.sendSync(ELECTRON_EVENTS.PERSIST_SECURE_CREDENTIALS, secureCredentials);
return decrypted;
}
export {
storeEncryptedCredentials,
fetchDecryptedCredentials,
};
// desktop/main.js
ipcMain.on(ELECTRON_EVENTS.REQUEST_SECURE_CREDENTIALS, (event, credential) => {
try {
// eslint-disable-next-line no-param-reassign
event.returnValue = safeStorage.encryptString(credential).toJSON();
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
// eslint-disable-next-line no-param-reassign
event.returnValue = {};
}
});
ipcMain.on(ELECTRON_EVENTS.PERSIST_SECURE_CREDENTIALS, (event, credential) => {
try {
// eslint-disable-next-line no-param-reassign
event.returnValue = safeStorage.decryptString(Buffer.from(credential));
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
// eslint-disable-next-line no-param-reassign
event.returnValue = {};
}
});
Is this issue still open?
@therealJimoh Yes
@parasharrajat any questions on the proposal above ?
Haven't got the time to look into it? I need to review the overall approach to storing data in Onyx. This requires an internal discussion. I will try to summarize it on slack.
As I mentioned before, I am considering using node-keytar
.
Some of the advantages:
const keytar = require('keytar')
// add credentials
keytar.addPassword('ExpensifyApp', 'AccountName', 'secret');
// get all stored credentials to then use for suggestions
let credentials = keytar.findCredentials('ExpensifyApp');
- node-keytar will store each encrypted payload as a separate entry, which results in prompting OS authorization multiple times. safeStorage will only store a secret key which is used to decrypt secret payload.
They can certainly be separate entries, but the user will still be prompted OS authorization only for the first time.
- node-keytar is not a storage service, but rather an api for OS specific keychain. Meaning that multiple node processes or chromium instances can access secure keychain entries.
While it's true that node-keytar is an API for system keychain, it's not true that any node processes will be able to access Expensify entries (or at least not in our case). The reason being that after we package our Electron app, the app name would be Expensify meaning that only Expensify process would be able to access it.
- node-keytar requires extra maintenance, specifically extra build steps per platform.
It's a node module. So we would just have to install, import, and use it.
I will try to summarize it on slack.
@parasharrajat when do you do so, will you drop the link in here (for internal or #expensify-open-source post), thanks.
Would be good to get a small update here before EOW if possible 🙏
I will share an update tomorrow. Haven't forgotten it. Basically, it will kick off in a few hours.
Autofill
is a component in the chromium project. Probably the most expensive way is that we enable it and modify its storage logic and compile our own electron. The first build took me several hours. (This way is not recommended 😂)
I think it's better to create a separate library to do it and modify our existing components as little as possible.
We can learn the logic of chromium, just use js to render the popup and store password in a system password manager. (for example, use node-keytar
, It doesn't prompt for OS authorization multiple times)
IMO, it's better to keep interactive habits similar to the browser:
Here are three demo videos, and I will update this proposal with more details in the next few days :
https://user-images.githubusercontent.com/8579651/196975364-51ba11b0-661a-4814-95df-f34dee96d399.mp4
https://user-images.githubusercontent.com/8579651/196975417-76fcf36c-02b2-4345-ac8d-cdc14c9faa33.mp4
https://user-images.githubusercontent.com/8579651/196975467-b8dbe35b-2d09-4493-bf78-f6f17d64774d.mp4
I create a new electron-autofill
library, it will be published to npm after development.
All our app needs to do is install and use it, minor modifications required, such as this:
- <View style={[this.props.style]}>
+ <View style={[this.props.style]} accessibilityRole={ComponentUtils.ACCESSIBILITY_ROLE_FORM}>
It will add some event listeners on window
or div#root
(click for autofill, input for filtering)
When we click on an input that needs to be autofilled, it analyzes the input's attributes, even its label, to decide which type of pop-up box to open, such as password type or card type , address can also be supported. It also recognizes other input elements within the same form scope (The principle is complex and is still being perfected).
It will update the value of these inputs after we select a candidate. (maintain compatibility with react
)
After the form is submitted, it saves the relevant information. Password information will be saved in the system's keychain. The storage location of the card information can be discussed again, which can be localStorage
, Onyx
, or even keychain. (IMO, localStorage
or Onyx
are both ok)
I think we shouldn't use safeStorage and store user password in front-end application. The cipher text can be decrypted, especially if the decrypted key is the same one shared by other electron applications. We can't modify this entry name by program unless we build electron from source code. It is a constant value in the chromium source code.
I guess these apps may have modified the entry name in their own chromium source code. 😂
Looking now at all proposals.
Looked all proposals. Going though node-keytar
and safestorage
for now.
@tomivs and @ntdiary your proposal are incomplete. Please provide more details when possible. Proposals should explain architecture of your code along with implementation in our app.
Note: A universal solution which can be applied to normally any autofillable form will be better. For now, this issue focuses on Add debit card and login forms
.
I have updated some details in the Some details
section, I'll update and discuss as necessary.
A universal solution which can be applied to normally any autofillable form will be better.
I hope to create a library (electron-autofill
) for most electron apps that need it, and of course our app will be supported first. 😄
https://user-images.githubusercontent.com/4882133/197191516-b5acb91d-292d-494c-ae40-f1de0cdbc326.mov
zIndex
inconsistencies make some element overflowhttps://github.com/azimgd/expensify-app/pull/1/files
A universal solution which can be applied to normally any autofillable form will be better
This approach is quite universal as I'm attaching (for demo purposes only) Dropdown component to src/component/RNTextInput
. This could easily be decoupled further as it isn't dependent on anything else but TextInput
and Onyx
for now. Having a separate npm package is possible as well with this approach.
https://github.com/atom/node-keytar/issues/88
the fact that when this is used in a Node process to store secrets any other Node process can trivially access those secrets is something that should be deeply troubling to project owners.
as it's clear that I'm not the only one confused by this - and people are most likely using this library blindly, and haven't gone to the effort that I did of creating another Electron app to test that the other app can't access the credentials - then I would strongly recommend updating the readme with a brief explanation of when this library should and shouldn't be used. Otherwise, it's doing more harm than good.
https://github.com/atom/node-keytar/issues/50
As I understand it, keytar stores credentials in the OS X keychain. Keychain automatically allows "the service which created the entry" to access or change the password. Testing this, however, it seems Keychain simply registers node as the service — meaning that any node process can arbitrarily look up any passwords set by keytar, with no confirmation by the user.
https://github.com/atom/node-keytar/issues/359 https://github.com/atom/node-keytar/issues/406 https://github.com/atom/node-keytar/issues/245 https://github.com/atom/node-keytar/issues/50
I've read it all but I am still confused. Can someone make a conclusion, please?
Interesting. I also take this very seriously. I read the source code of both and tested some use cases, rather than just blindly choosing A or B based on the README or doc. I think I can do more testing for this and help come to a conclusion.
@iwiznia, @michaelhaxhiu, @parasharrajat Whoops! This issue is 2 days overdue. Let's get this updated quick!
@iwiznia, @michaelhaxhiu, @parasharrajat Whoops! This issue is 2 days overdue. Let's get this updated quick!
node-keytar
and safeStorage
https://github.com/ntdiary/example, There are two electron project in this repo:
node-keytar
or safeStorage + indexedDB
.demo
with npm start
or just double-click executable app.attacker
app.demo
app, fill in the password and save.attacker
app, click steal
button. See if it can steal the password quietly.https://user-images.githubusercontent.com/8579651/197464320-c88c2268-39ef-4fe6-9c2b-facc95da0650.mp4
safeStorage
: yesnode-keytar
: yeshttps://user-images.githubusercontent.com/8579651/197464338-7ade32ce-6f4c-44e0-a95e-87182d1d2d14.mp4
safeStorage
: no. Authorization required.node-keytar
: no. Authorization required.https://user-images.githubusercontent.com/8579651/197464352-cbb39daa-8857-4ca3-b1b7-bdbfc2408c95.mp4
safeStorage
: yesnode-keytar
: yeshttps://user-images.githubusercontent.com/8579651/197464402-c089ae2f-b642-404f-b2e7-c608d13dc5fc.mp4
This repo doesn't yet support Linux, although it's easy to do. Now you can manually copy the key first if you want to test.
safeStorage
: yesnode-keytar
: yesQ: Why do this test? I hope this test and post can help us better understand the security of these two modules.
Q: Why I didn't recommed safeStorage
before?
Actually, both modules are my alternatives. safeStorage
had an initialization bug that happened to come across in my previous tests, which made me think it was using the key of Chromium Safe Storage
and might conflict with other apps. But it has been fixed. the worry is gone.
Q: So is safeStorage
safer?
According to the test, I'm afraid not. node-keytar
is just the interface of the system keychain. And safeStorage
also use system keychain to store encryption key (It's different in Windows, where the key is stored in the Local State
file).
This problem requires OS support in the first place. I think I'd be happy to help do something as well if needed by then.
related:
As for the build issues in the above post. I think only https://github.com/atom/node-keytar/issues/406 is a bit difficult for me, since I don't have an M1 Mac yet. 😂
Just curious, do we need to explain the risk somewhere when we try to provide this autofill
service?
Looking forward to hearing more opinions. 🙂
Finally, I will provide more details and demo code about architecture logic in this week.
Update: Reviewing Proposals...
It's been almost 2 weeks without an update, so I would really appreciate if we can get any comment.
There are holidays in India. I will try to share an update but expect some delay.
Thank you for an update, do you have any estimates on when this could be reviewed ?
If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!
Issue was found when executing #9294
Action Performed:
Expected Result:
Autofill should works during login Autofill should works while adding new Debit card
Actual Result:
No autofill for fields at login page No autofill for all fields while adding new Debit card
Workaround:
unknown
Platform:
Where is this issue occurring?
Version Number: Version 1.1.86-1
Reproducible in staging?: y
Reproducible in production?: y
Email or phone of affected tester (no customers):
Logs: https://stackoverflow.com/c/expensify/questions/4856
Notes/Photos/Videos:
https://user-images.githubusercontent.com/43996225/181026173-67396956-4e27-4886-9d7b-047dd470e730.mp4
Upwork job URL: https://www.upwork.com/jobs/~01a9e983bc39d24141
Issue reported by: Applause internal team
Slack conversation:
View all open jobs on GitHub