Skip to content

Commit

Permalink
Adds endpoint for non-revocable session token upgrade (#2646)
Browse files Browse the repository at this point in the history
  • Loading branch information
flovilmart authored Sep 9, 2016
1 parent c5fdd91 commit 340eb46
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 3 deletions.
92 changes: 92 additions & 0 deletions spec/RevocableSessionsUpgrade.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
const Config = require('../src/Config');
const sessionToken = 'legacySessionToken';
const rp = require('request-promise');
const Parse = require('parse/node');

function createUser() {
const config = new Config(Parse.applicationId);
const user = {
objectId: '1234567890',
username: 'hello',
password: 'pass',
_session_token: sessionToken
}
return config.database.create('_User', user);
}

describe('revocable sessions', () => {

beforeEach((done) => {
// Create 1 user with the legacy
createUser().then(done);
});

it('should upgrade legacy session token', done => {
let user = Parse.Object.fromJSON({
className: '_User',
objectId: '1234567890',
sessionToken: sessionToken
});
user._upgradeToRevocableSession().then((res) => {
expect(res.getSessionToken().indexOf('r:')).toBe(0);
const config = new Config(Parse.applicationId);
// use direct access to the DB to make sure we're not
// getting the session token stripped
return config.database.loadSchema().then(schemaController => {
return schemaController.getOneSchema('_User', true)
}).then((schema) => {
return config.database.adapter.find('_User', schema, {objectId: '1234567890'}, {})
}).then((results) => {
expect(results.length).toBe(1);
expect(results[0].sessionToken).toBeUndefined();
});
}).then(() => {
done();
}, (err) => {
jfail(err);
done();
});
});

it('should be able to become with revocable session token', done => {
let user = Parse.Object.fromJSON({
className: '_User',
objectId: '1234567890',
sessionToken: sessionToken
});
user._upgradeToRevocableSession().then((res) => {
expect(res.getSessionToken().indexOf('r:')).toBe(0);
return Parse.User.logOut().then(() => {
return Parse.User.become(res.getSessionToken())
}).then((user) => {
expect(user.id).toEqual('1234567890');
});
}).then(() => {
done();
}, (err) => {
jfail(err);
done();
});
});

it('should not upgrade bad legacy session token', done => {
rp.post({
url: Parse.serverURL+'/upgradeToRevocableSession',
headers: {
'X-Parse-Application-Id': Parse.applicationId,
'X-Parse-Rest-API-Key': 'rest',
'X-Parse-Session-Token': 'badSessionToken'
},
json: true
}).then((res) => {
fail('should not be able to upgrade a bad token');
}, (response) => {
expect(response.statusCode).toBe(400);
expect(response.error).not.toBeUndefined();
expect(response.error.code).toBe(Parse.Error.INVALID_SESSION_TOKEN);
expect(response.error.error).toEqual('invalid legacy session token');
}).then(() => {
done();
});
});
})
20 changes: 19 additions & 1 deletion src/Auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,23 @@ var getAuthForSessionToken = function({ config, sessionToken, installationId } =
});
};

var getAuthForLegacySessionToken = function({config, sessionToken, installationId } = {}) {
var restOptions = {
limit: 1
};
var query = new RestQuery(config, master(config), '_User', { sessionToken: sessionToken}, restOptions);
return query.execute().then((response) => {
var results = response.results;
if (results.length !== 1) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'invalid legacy session token');
}
let obj = results[0];
obj.className = '_User';
let userObject = Parse.Object.fromJSON(obj);
return new Auth({config, isMaster: false, installationId, user: userObject});
});
}

// Returns a promise that resolves to an array of role names
Auth.prototype.getUserRoles = function() {
if (this.isMaster || !this.user) {
Expand Down Expand Up @@ -195,5 +212,6 @@ module.exports = {
Auth: Auth,
master: master,
nobody: nobody,
getAuthForSessionToken: getAuthForSessionToken
getAuthForSessionToken,
getAuthForLegacySessionToken
};
35 changes: 35 additions & 0 deletions src/Routers/SessionsRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import ClassesRouter from './ClassesRouter';
import PromiseRouter from '../PromiseRouter';
import rest from '../rest';
import Auth from '../Auth';
import RestWrite from '../RestWrite';
import { newToken } from '../cryptoUtils';

export class SessionsRouter extends ClassesRouter {
handleFind(req) {
Expand Down Expand Up @@ -51,12 +53,45 @@ export class SessionsRouter extends ClassesRouter {
});
}

handleUpdateToRevocableSession(req) {
const config = req.config;
const masterAuth = Auth.master(config)
const user = req.auth.user;
const expiresAt = config.generateSessionExpiresAt();
const sessionData = {
sessionToken: 'r:' + newToken(),
user: {
__type: 'Pointer',
className: '_User',
objectId: user.id
},
createdWith: {
'action': 'upgrade',
},
restricted: false,
installationId: req.auth.installationId,
expiresAt: Parse._encode(expiresAt)
};
const create = new RestWrite(config, masterAuth, '_Session', null, sessionData);
return create.execute().then(() => {
// delete the session token, use the db to skip beforeSave
return config.database.update('_User', {
objectId: user.id
}, {
sessionToken: {__op: 'Delete'}
});
}).then((res) => {
return Promise.resolve({ response: sessionData });
});
}

mountRoutes() {
this.route('GET', '/sessions', req => { return this.handleFind(req); });
this.route('GET', '/sessions/:objectId', req => { return this.handleGet(req); });
this.route('POST', '/sessions', req => { return this.handleCreate(req); });
this.route('PUT', '/sessions/:objectId', req => { return this.handleUpdate(req); });
this.route('DELETE', '/sessions/:objectId', req => { return this.handleDelete(req); });
this.route('POST', '/upgradeToRevocableSession', req => { return this.handleUpdateToRevocableSession(req); })
}
}

Expand Down
12 changes: 10 additions & 2 deletions src/middlewares.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,16 @@ export function handleParseHeaders(req, res, next) {
return;
}

return auth.getAuthForSessionToken({ config: req.config, installationId: info.installationId, sessionToken: info.sessionToken })
.then((auth) => {
return Promise.resolve().then(() => {
// handle the upgradeToRevocableSession path on it's own
if (info.sessionToken &&
req.url === '/upgradeToRevocableSession' &&
info.sessionToken.indexOf('r:') != 0) {
return auth.getAuthForLegacySessionToken({ config: req.config, installationId: info.installationId, sessionToken: info.sessionToken })
} else {
return auth.getAuthForSessionToken({ config: req.config, installationId: info.installationId, sessionToken: info.sessionToken })
}
}).then((auth) => {
if (auth) {
req.auth = auth;
next();
Expand Down

0 comments on commit 340eb46

Please sign in to comment.