@@ -257,6 +257,71 @@ describe('Parse.Session', () => {
257257 expect ( newSession . createdWith . authProvider ) . toBeUndefined ( ) ;
258258 } ) ;
259259
260+ it ( 'does not delete another user\'s session when creating a session via POST /classes/_Session' , async ( ) => {
261+ const victim = await Parse . User . signUp ( 'dedupvictim' , 'password' ) ;
262+ const attacker = await Parse . User . signUp ( 'dedupattacker' , 'password' ) ;
263+ const victimId = victim . id ;
264+ const installationId = 'a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d' ;
265+
266+ // Victim logs in on a known installation, creating a session with that installationId.
267+ const victimLogin = await request ( {
268+ method : 'POST' ,
269+ url : 'http://localhost:8378/1/login' ,
270+ headers : {
271+ 'X-Parse-Application-Id' : 'test' ,
272+ 'X-Parse-REST-API-Key' : 'rest' ,
273+ 'X-Parse-Installation-Id' : installationId ,
274+ 'Content-Type' : 'application/json' ,
275+ } ,
276+ body : { username : 'dedupvictim' , password : 'password' } ,
277+ } ) ;
278+ const victimSessionToken = victimLogin . data . sessionToken ;
279+
280+ // Another user creates a session while naming the victim as `user` and supplying
281+ // the victim's installationId. The session dedup must not delete the victim's session.
282+ await request ( {
283+ method : 'POST' ,
284+ url : 'http://localhost:8378/1/classes/_Session' ,
285+ headers : {
286+ 'X-Parse-Application-Id' : 'test' ,
287+ 'X-Parse-REST-API-Key' : 'rest' ,
288+ 'X-Parse-Session-Token' : attacker . getSessionToken ( ) ,
289+ 'Content-Type' : 'application/json' ,
290+ } ,
291+ body : {
292+ user : { __type : 'Pointer' , className : '_User' , objectId : victimId } ,
293+ installationId,
294+ sessionToken : 'r:someothertoken' ,
295+ } ,
296+ } ) ;
297+
298+ // The victim's session on that installation must still exist...
299+ const sessions = await request ( {
300+ method : 'GET' ,
301+ url : 'http://localhost:8378/1/classes/_Session' ,
302+ headers : {
303+ 'X-Parse-Application-Id' : 'test' ,
304+ 'X-Parse-Master-Key' : 'test' ,
305+ } ,
306+ } ) ;
307+ const victimSession = sessions . data . results . find (
308+ s => s . installationId === installationId && s . user && s . user . objectId === victimId
309+ ) ;
310+ expect ( victimSession ) . toBeDefined ( ) ;
311+
312+ // ...and the victim's session token must still authenticate.
313+ const meResponse = await request ( {
314+ method : 'GET' ,
315+ url : 'http://localhost:8378/1/users/me' ,
316+ headers : {
317+ 'X-Parse-Application-Id' : 'test' ,
318+ 'X-Parse-REST-API-Key' : 'rest' ,
319+ 'X-Parse-Session-Token' : victimSessionToken ,
320+ } ,
321+ } ) ;
322+ expect ( meResponse . data . objectId ) . toBe ( victimId ) ;
323+ } ) ;
324+
260325 it ( 'should reject expiresAt when updating a session via PUT' , async ( ) => {
261326 const user = await Parse . User . signUp ( 'sessionupdateuser1' , 'password' ) ;
262327 const sessionToken = user . getSessionToken ( ) ;
0 commit comments