Skip to content

Commit 81a4df5

Browse files
authored
Auth module refactoring in order to be reusable (parse-community#4940)
* Auth module refactoring in order to be reusable * Ensure cache controller is properly forwarded from helpers * Nits
1 parent 33a5801 commit 81a4df5

File tree

4 files changed

+171
-96
lines changed

4 files changed

+171
-96
lines changed

spec/Auth.spec.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
describe('Auth', () => {
2-
const Auth = require('../lib/Auth.js').Auth;
3-
2+
const { Auth, getAuthForSessionToken } = require('../lib/Auth.js');
3+
const Config = require('../lib/Config');
44
describe('getUserRoles', () => {
55
let auth;
66
let config;
@@ -90,4 +90,33 @@ describe('Auth', () => {
9090
});
9191

9292
});
93+
94+
it('should load auth without a config', async () => {
95+
const user = new Parse.User();
96+
await user.signUp({
97+
username: 'hello',
98+
password: 'password'
99+
});
100+
expect(user.getSessionToken()).not.toBeUndefined();
101+
const userAuth = await getAuthForSessionToken({
102+
sessionToken: user.getSessionToken()
103+
});
104+
expect(userAuth.user instanceof Parse.User).toBe(true);
105+
expect(userAuth.user.id).toBe(user.id);
106+
});
107+
108+
it('should load auth with a config', async () => {
109+
const user = new Parse.User();
110+
await user.signUp({
111+
username: 'hello',
112+
password: 'password'
113+
});
114+
expect(user.getSessionToken()).not.toBeUndefined();
115+
const userAuth = await getAuthForSessionToken({
116+
sessionToken: user.getSessionToken(),
117+
config: Config.get('test'),
118+
});
119+
expect(userAuth.user instanceof Parse.User).toBe(true);
120+
expect(userAuth.user.id).toBe(user.id);
121+
});
93122
});

spec/CloudCode.spec.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -832,10 +832,7 @@ describe('Cloud Code', () => {
832832
expect(body.result).toEqual('second data');
833833
done();
834834
})
835-
.catch(error => {
836-
fail(JSON.stringify(error));
837-
done();
838-
});
835+
.catch(done.fail);
839836
});
840837

841838
it('trivial beforeSave should not affect fetched pointers (regression test for #1238)', done => {

spec/ParseRole.spec.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ describe('Parse Role testing', () => {
142142

143143
});
144144

145-
it("should recursively load roles", (done) => {
145+
function testLoadRoles(config, done) {
146146
const rolesNames = ["FooRole", "BarRole", "BazRole"];
147147
const roleIds = {};
148148
createTestUser().then((user) => {
@@ -159,7 +159,7 @@ describe('Parse Role testing', () => {
159159
return createRole(rolesNames[2], anotherRole, null);
160160
}).then((lastRole) => {
161161
roleIds[lastRole.get("name")] = lastRole.id;
162-
const auth = new Auth({ config: Config.get("test"), isMaster: true, user: user });
162+
const auth = new Auth({ config, isMaster: true, user: user });
163163
return auth._loadRoles();
164164
})
165165
}).then((roles) => {
@@ -172,6 +172,14 @@ describe('Parse Role testing', () => {
172172
fail("should succeed")
173173
done();
174174
});
175+
}
176+
177+
it("should recursively load roles", (done) => {
178+
testLoadRoles(Config.get('test'), done);
179+
});
180+
181+
it("should recursively load roles without config", (done) => {
182+
testLoadRoles(undefined, done);
175183
});
176184

177185
it("_Role object should not save without name.", (done) => {

src/Auth.js

Lines changed: 129 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ const Parse = require('parse/node');
55
// An Auth object tells you who is requesting something and whether
66
// the master key was used.
77
// userObject is a Parse.User and can be null if there's no user.
8-
function Auth({ config, isMaster = false, isReadOnly = false, user, installationId } = {}) {
8+
function Auth({ config, cacheController = undefined, isMaster = false, isReadOnly = false, user, installationId }) {
99
this.config = config;
10+
this.cacheController = cacheController || (config && config.cacheController);
1011
this.installationId = installationId;
1112
this.isMaster = isMaster;
1213
this.user = user;
@@ -48,47 +49,58 @@ function nobody(config) {
4849

4950

5051
// Returns a promise that resolves to an Auth object
51-
var getAuthForSessionToken = function({ config, sessionToken, installationId } = {}) {
52-
return config.cacheController.user.get(sessionToken).then((userJSON) => {
52+
const getAuthForSessionToken = async function({ config, cacheController, sessionToken, installationId }) {
53+
cacheController = cacheController || (config && config.cacheController);
54+
if (cacheController) {
55+
const userJSON = await cacheController.user.get(sessionToken);
5356
if (userJSON) {
5457
const cachedUser = Parse.Object.fromJSON(userJSON);
55-
return Promise.resolve(new Auth({config, isMaster: false, installationId, user: cachedUser}));
58+
return Promise.resolve(new Auth({config, cacheController, isMaster: false, installationId, user: cachedUser}));
5659
}
60+
}
5761

58-
var restOptions = {
62+
let results;
63+
if (config) {
64+
const restOptions = {
5965
limit: 1,
6066
include: 'user'
6167
};
6268

63-
var query = new RestQuery(config, master(config), '_Session', {sessionToken}, restOptions);
64-
return query.execute().then((response) => {
65-
var results = response.results;
66-
if (results.length !== 1 || !results[0]['user']) {
67-
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
68-
}
69+
const query = new RestQuery(config, master(config), '_Session', { sessionToken }, restOptions);
70+
results = (await query.execute()).results;
71+
} else {
72+
results = (await new Parse.Query(Parse.Session)
73+
.limit(1)
74+
.include('user')
75+
.equalTo('sessionToken', sessionToken)
76+
.find({ useMasterKey: true })).map((obj) => obj.toJSON())
77+
}
6978

70-
var now = new Date(),
71-
expiresAt = results[0].expiresAt ? new Date(results[0].expiresAt.iso) : undefined;
72-
if (expiresAt < now) {
73-
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN,
74-
'Session token is expired.');
75-
}
76-
var obj = results[0]['user'];
77-
delete obj.password;
78-
obj['className'] = '_User';
79-
obj['sessionToken'] = sessionToken;
80-
config.cacheController.user.put(sessionToken, obj);
81-
const userObject = Parse.Object.fromJSON(obj);
82-
return new Auth({config, isMaster: false, installationId, user: userObject});
83-
});
84-
});
79+
if (results.length !== 1 || !results[0]['user']) {
80+
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Invalid session token');
81+
}
82+
const now = new Date(),
83+
expiresAt = results[0].expiresAt ? new Date(results[0].expiresAt.iso) : undefined;
84+
if (expiresAt < now) {
85+
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN,
86+
'Session token is expired.');
87+
}
88+
const obj = results[0]['user'];
89+
delete obj.password;
90+
obj['className'] = '_User';
91+
obj['sessionToken'] = sessionToken;
92+
if (cacheController) {
93+
cacheController.user.put(sessionToken, obj);
94+
}
95+
const userObject = Parse.Object.fromJSON(obj);
96+
return new Auth({ config, cacheController, isMaster: false, installationId, user: userObject });
8597
};
8698

87-
var getAuthForLegacySessionToken = function({config, sessionToken, installationId } = {}) {
99+
var getAuthForLegacySessionToken = function({ config, sessionToken, installationId }) {
88100
var restOptions = {
89101
limit: 1
90102
};
91-
var query = new RestQuery(config, master(config), '_User', { sessionToken: sessionToken}, restOptions);
103+
var query = new RestQuery(config, master(config), '_User', { sessionToken }, restOptions);
92104
return query.execute().then((response) => {
93105
var results = response.results;
94106
if (results.length !== 1) {
@@ -97,7 +109,7 @@ var getAuthForLegacySessionToken = function({config, sessionToken, installationI
97109
const obj = results[0];
98110
obj.className = '_User';
99111
const userObject = Parse.Object.fromJSON(obj);
100-
return new Auth({config, isMaster: false, installationId, user: userObject});
112+
return new Auth({ config, isMaster: false, installationId, user: userObject });
101113
});
102114
}
103115

@@ -116,84 +128,113 @@ Auth.prototype.getUserRoles = function() {
116128
return this.rolePromise;
117129
};
118130

119-
// Iterates through the role tree and compiles a users roles
120-
Auth.prototype._loadRoles = function() {
121-
var cacheAdapter = this.config.cacheController;
122-
return cacheAdapter.role.get(this.user.id).then((cachedRoles) => {
123-
if (cachedRoles != null) {
124-
this.fetchedRoles = true;
125-
this.userRoles = cachedRoles;
126-
return Promise.resolve(cachedRoles);
127-
}
128-
129-
var restWhere = {
131+
Auth.prototype.getRolesForUser = function() {
132+
if (this.config) {
133+
const restWhere = {
130134
'users': {
131135
__type: 'Pointer',
132136
className: '_User',
133137
objectId: this.user.id
134138
}
135139
};
136-
// First get the role ids this user is directly a member of
137-
var query = new RestQuery(this.config, master(this.config), '_Role', restWhere, {});
138-
return query.execute().then((response) => {
139-
var results = response.results;
140-
if (!results.length) {
141-
this.userRoles = [];
142-
this.fetchedRoles = true;
143-
this.rolePromise = null;
144-
145-
cacheAdapter.role.put(this.user.id, Array(...this.userRoles));
146-
return Promise.resolve(this.userRoles);
147-
}
148-
var rolesMap = results.reduce((m, r) => {
149-
m.names.push(r.name);
150-
m.ids.push(r.objectId);
151-
return m;
152-
}, {ids: [], names: []});
153-
154-
// run the recursive finding
155-
return this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names)
156-
.then((roleNames) => {
157-
this.userRoles = roleNames.map((r) => {
158-
return 'role:' + r;
159-
});
160-
this.fetchedRoles = true;
161-
this.rolePromise = null;
162-
cacheAdapter.role.put(this.user.id, Array(...this.userRoles));
163-
return Promise.resolve(this.userRoles);
164-
});
165-
});
140+
const query = new RestQuery(this.config, master(this.config), '_Role', restWhere, {});
141+
return query.execute().then(({ results }) => results);
142+
}
143+
144+
return new Parse.Query(Parse.Role)
145+
.equalTo('users', this.user)
146+
.find({ useMasterKey: true })
147+
.then((results) => results.map((obj) => obj.toJSON()));
148+
}
149+
150+
// Iterates through the role tree and compiles a user's roles
151+
Auth.prototype._loadRoles = async function() {
152+
if (this.cacheController) {
153+
const cachedRoles = await this.cacheController.role.get(this.user.id);
154+
if (cachedRoles != null) {
155+
this.fetchedRoles = true;
156+
this.userRoles = cachedRoles;
157+
return cachedRoles;
158+
}
159+
}
160+
161+
// First get the role ids this user is directly a member of
162+
const results = await this.getRolesForUser();
163+
if (!results.length) {
164+
this.userRoles = [];
165+
this.fetchedRoles = true;
166+
this.rolePromise = null;
167+
168+
this.cacheRoles();
169+
return this.userRoles;
170+
}
171+
172+
const rolesMap = results.reduce((m, r) => {
173+
m.names.push(r.name);
174+
m.ids.push(r.objectId);
175+
return m;
176+
}, {ids: [], names: []});
177+
178+
// run the recursive finding
179+
const roleNames = await this._getAllRolesNamesForRoleIds(rolesMap.ids, rolesMap.names);
180+
this.userRoles = roleNames.map((r) => {
181+
return 'role:' + r;
166182
});
183+
this.fetchedRoles = true;
184+
this.rolePromise = null;
185+
this.cacheRoles();
186+
return this.userRoles;
167187
};
168188

169-
// Given a list of roleIds, find all the parent roles, returns a promise with all names
170-
Auth.prototype._getAllRolesNamesForRoleIds = function(roleIDs, names = [], queriedRoles = {}) {
171-
const ins = roleIDs.filter((roleID) => {
172-
return queriedRoles[roleID] !== true;
173-
}).map((roleID) => {
174-
// mark as queried
175-
queriedRoles[roleID] = true;
189+
Auth.prototype.cacheRoles = function() {
190+
if (!this.cacheController) {
191+
return false;
192+
}
193+
this.cacheController.role.put(this.user.id, Array(...this.userRoles));
194+
return true;
195+
}
196+
197+
Auth.prototype.getRolesByIds = function(ins) {
198+
const roles = ins.map((id) => {
176199
return {
177200
__type: 'Pointer',
178201
className: '_Role',
179-
objectId: roleID
202+
objectId: id
180203
}
181204
});
205+
const restWhere = { 'roles': { '$in': roles }};
206+
207+
// Build an OR query across all parentRoles
208+
if (!this.config) {
209+
return new Parse.Query(Parse.Role)
210+
.containedIn('roles', ins.map((id) => {
211+
const role = new Parse.Object(Parse.Role);
212+
role.id = id;
213+
return role;
214+
}))
215+
.find({ useMasterKey: true })
216+
.then((results) => results.map((obj) => obj.toJSON()));
217+
}
218+
219+
return new RestQuery(this.config, master(this.config), '_Role', restWhere, {})
220+
.execute()
221+
.then(({ results }) => results);
222+
}
223+
224+
// Given a list of roleIds, find all the parent roles, returns a promise with all names
225+
Auth.prototype._getAllRolesNamesForRoleIds = function(roleIDs, names = [], queriedRoles = {}) {
226+
const ins = roleIDs.filter((roleID) => {
227+
const wasQueried = queriedRoles[roleID] !== true;
228+
queriedRoles[roleID] = true;
229+
return wasQueried;
230+
});
182231

183232
// all roles are accounted for, return the names
184233
if (ins.length == 0) {
185234
return Promise.resolve([...new Set(names)]);
186235
}
187-
// Build an OR query across all parentRoles
188-
let restWhere;
189-
if (ins.length == 1) {
190-
restWhere = { 'roles': ins[0] };
191-
} else {
192-
restWhere = { 'roles': { '$in': ins }}
193-
}
194-
const query = new RestQuery(this.config, master(this.config), '_Role', restWhere, {});
195-
return query.execute().then((response) => {
196-
var results = response.results;
236+
237+
return this.getRolesByIds(ins).then((results) => {
197238
// Nothing found
198239
if (!results.length) {
199240
return Promise.resolve(names);

0 commit comments

Comments
 (0)