Skip to content

Commit 684b9e1

Browse files
macarthurordplewis
andcommitted
Encrypted Current User in browser (#1036)
* Encrypted user (currentUser) -still need jest test * all 'npm test' tests passed successfully * secret variable, tests improvement, only browser * fix conflicts * improve test and cleanup * improve coverage Co-authored-by: Diamond Lewis <[email protected]>
1 parent fc840f6 commit 684b9e1

File tree

9 files changed

+263
-11
lines changed

9 files changed

+263
-11
lines changed

integration/test/ParseUserTest.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -936,6 +936,32 @@ describe('Parse User', () => {
936936
expect(user.get('authData').facebook.id).toBe('test');
937937
});
938938

939+
it('can encrypt user', async () => {
940+
Parse.User.enableUnsafeCurrentUser();
941+
Parse.enableEncryptedUser();
942+
Parse.secret = 'My Secret Key';
943+
const user = new Parse.User();
944+
user.setUsername('usernameENC');
945+
user.setPassword('passwordENC');
946+
await user.signUp();
947+
948+
const path = Parse.Storage.generatePath('currentUser');
949+
const encryptedUser = Parse.Storage.getItem(path);
950+
951+
const crypto = Parse.CoreManager.getCryptoController();
952+
const decryptedUser = crypto.decrypt(encryptedUser, Parse.CoreManager.get('ENCRYPTED_KEY'));
953+
expect(JSON.parse(decryptedUser).objectId).toBe(user.id);
954+
955+
const currentUser = Parse.User.current();
956+
expect(currentUser).toEqual(user);
957+
958+
const currentUserAsync = await Parse.User.currentAsync();
959+
expect(currentUserAsync).toEqual(user);
960+
await Parse.User.logOut();
961+
Parse.CoreManager.set('ENCRYPTED_USER', false);
962+
Parse.CoreManager.set('ENCRYPTED_KEY', null);
963+
});
964+
939965
it('fix GHSA-wvh7-5p38-2qfc', async () => {
940966
Parse.User.enableUnsafeCurrentUser();
941967
const user = new Parse.User();

package-lock.json

Lines changed: 13 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"dependencies": {
3232
"@babel/runtime": "7.7.7",
3333
"@babel/runtime-corejs3": "7.7.7",
34+
"crypto-js": "3.1.9-1",
3435
"uuid": "3.3.3",
3536
"ws": "7.2.1",
3637
"xmlhttprequest": "1.8.0"

src/CoreManager.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ type ConfigController = {
3333
get: () => Promise;
3434
save: (attrs: { [key: string]: any }) => Promise;
3535
};
36+
type CryptoController = {
37+
encrypt: (obj: any, secretKey: string) => string;
38+
decrypt: (encryptedText: string, secretKey: any) => string;
39+
};
3640
type FileController = {
3741
saveFile: (name: string, source: FileSource, options: FullOptions) => Promise;
3842
saveBase64: (name: string, source: FileSource, options: FullOptions) => Promise;
@@ -176,13 +180,15 @@ const config: Config & { [key: string]: mixed } = {
176180
SERVER_AUTH_TYPE: null,
177181
SERVER_AUTH_TOKEN: null,
178182
LIVEQUERY_SERVER_URL: null,
183+
ENCRYPTED_KEY: null,
179184
VERSION: 'js' + require('../package.json').version,
180185
APPLICATION_ID: null,
181186
JAVASCRIPT_KEY: null,
182187
MASTER_KEY: null,
183188
USE_MASTER_KEY: false,
184189
PERFORM_USER_REWRITE: true,
185-
FORCE_REVOCABLE_SESSION: false
190+
FORCE_REVOCABLE_SESSION: false,
191+
ENCRYPTED_USER: false
186192
};
187193

188194
function requireMethods(name: string, methods: Array<string>, controller: any) {
@@ -234,6 +240,15 @@ module.exports = {
234240
return config['ConfigController'];
235241
},
236242

243+
setCryptoController(controller: CryptoController) {
244+
requireMethods('CryptoController', ['encrypt', 'decrypt'], controller);
245+
config['CryptoController'] = controller;
246+
},
247+
248+
getCryptoController(): CryptoController {
249+
return config['CryptoController'];
250+
},
251+
237252
setFileController(controller: FileController) {
238253
requireMethods('FileController', ['saveFile', 'saveBase64'], controller);
239254
config['FileController'] = controller;

src/CryptoController.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import AES from 'crypto-js/aes';
2+
import ENC from 'crypto-js/enc-utf8';
3+
4+
const CryptoController = {
5+
encrypt(obj: any, secretKey: string): ?string {
6+
const encrypted = AES.encrypt(JSON.stringify(obj), secretKey);
7+
return encrypted.toString();
8+
},
9+
10+
decrypt(encryptedText: string, secretKey: string): ?string {
11+
const decryptedStr = AES.decrypt(encryptedText, secretKey).toString(ENC);
12+
return decryptedStr;
13+
},
14+
};
15+
16+
module.exports = CryptoController;

src/Parse.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import decode from './decode';
1111
import encode from './encode';
1212
import CoreManager from './CoreManager';
13+
import CryptoController from './CryptoController';
1314
import InstallationController from './InstallationController';
1415
import * as ParseOp from './ParseOp';
1516
import RESTController from './RESTController';
@@ -169,6 +170,34 @@ Object.defineProperty(Parse, 'liveQueryServerURL', {
169170
CoreManager.set('LIVEQUERY_SERVER_URL', value);
170171
}
171172
});
173+
174+
/**
175+
* @member Parse.encryptedUser
176+
* @type boolean
177+
* @static
178+
*/
179+
Object.defineProperty(Parse, 'encryptedUser', {
180+
get() {
181+
return CoreManager.get('ENCRYPTED_USER');
182+
},
183+
set(value) {
184+
CoreManager.set('ENCRYPTED_USER', value);
185+
}
186+
});
187+
188+
/**
189+
* @member Parse.secret
190+
* @type string
191+
* @static
192+
*/
193+
Object.defineProperty(Parse, 'secret', {
194+
get() {
195+
return CoreManager.get('ENCRYPTED_KEY');
196+
},
197+
set(value) {
198+
CoreManager.set('ENCRYPTED_KEY', value);
199+
}
200+
});
172201
/* End setters */
173202

174203
Parse.ACL = require('./ParseACL').default;
@@ -255,6 +284,27 @@ Parse.dumpLocalDatastore = function() {
255284
return Parse.LocalDatastore._getAllContents();
256285
}
257286
}
287+
288+
/**
289+
* Enable the current user encryption.
290+
* This must be called before login any user.
291+
*
292+
* @static
293+
*/
294+
Parse.enableEncryptedUser = function() {
295+
Parse.encryptedUser = true;
296+
}
297+
298+
/**
299+
* Flag that indicates whether Encrypted User is enabled.
300+
*
301+
* @static
302+
*/
303+
Parse.isEncryptedUserEnabled = function() {
304+
return Parse.encryptedUser;
305+
}
306+
307+
CoreManager.setCryptoController(CryptoController);
258308
CoreManager.setInstallationController(InstallationController);
259309
CoreManager.setRESTController(RESTController);
260310

src/ParseUser.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -872,8 +872,13 @@ const DefaultController = {
872872
delete json.password;
873873

874874
json.className = '_User';
875+
let userData = JSON.stringify(json);
876+
if (CoreManager.get('ENCRYPTED_USER')) {
877+
const crypto = CoreManager.getCryptoController();
878+
userData = crypto.encrypt(json, CoreManager.get('ENCRYPTED_KEY'))
879+
}
875880
return Storage.setItemAsync(
876-
path, JSON.stringify(json)
881+
path, userData
877882
).then(() => {
878883
return user;
879884
});
@@ -918,6 +923,10 @@ const DefaultController = {
918923
currentUserCache = null;
919924
return null;
920925
}
926+
if (CoreManager.get('ENCRYPTED_USER')) {
927+
const crypto = CoreManager.getCryptoController();
928+
userData = crypto.decrypt(userData, CoreManager.get('ENCRYPTED_KEY'));
929+
}
921930
userData = JSON.parse(userData);
922931
if (!userData.className) {
923932
userData.className = '_User';
@@ -954,6 +963,10 @@ const DefaultController = {
954963
currentUserCache = null;
955964
return Promise.resolve(null);
956965
}
966+
if (CoreManager.get('ENCRYPTED_USER')) {
967+
const crypto = CoreManager.getCryptoController();
968+
userData = crypto.decrypt(userData.toString(), CoreManager.get('ENCRYPTED_KEY'));
969+
}
957970
userData = JSON.parse(userData);
958971
if (!userData.className) {
959972
userData.className = '_User';

src/__tests__/Parse-test.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
*/
99

1010
jest.dontMock('../CoreManager');
11+
jest.dontMock('../CryptoController');
1112
jest.dontMock('../Parse');
1213
jest.dontMock('../LocalDatastore');
14+
jest.dontMock('crypto-js/aes');
1315

1416
const CoreManager = require('../CoreManager');
1517
const Parse = require('../Parse');
@@ -109,4 +111,19 @@ describe('Parse module', () => {
109111
LDS = await Parse.dumpLocalDatastore();
110112
expect(LDS).toEqual({ key: 'value' });
111113
});
114+
115+
it('can enable encrypter CurrentUser', () => {
116+
jest.spyOn(console, 'log').mockImplementationOnce(() => {});
117+
process.env.PARSE_BUILD = 'browser';
118+
Parse.encryptedUser = false;
119+
Parse.enableEncryptedUser();
120+
expect(Parse.encryptedUser).toBe(true);
121+
expect(Parse.isEncryptedUserEnabled()).toBe(true);
122+
});
123+
124+
it('can set an encrypt token as String', () => {
125+
Parse.secret = 'My Super secret key';
126+
expect(CoreManager.get('ENCRYPTED_KEY')).toBe('My Super secret key');
127+
expect(Parse.secret).toBe('My Super secret key');
128+
});
112129
});

0 commit comments

Comments
 (0)