Adds endpoint for non-revocable session token upgrade (#2646)
This commit is contained in:
92
spec/RevocableSessionsUpgrade.spec.js
Normal file
92
spec/RevocableSessionsUpgrade.spec.js
Normal file
@@ -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
src/Auth.js
20
src/Auth.js
@@ -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
|
// Returns a promise that resolves to an array of role names
|
||||||
Auth.prototype.getUserRoles = function() {
|
Auth.prototype.getUserRoles = function() {
|
||||||
if (this.isMaster || !this.user) {
|
if (this.isMaster || !this.user) {
|
||||||
@@ -195,5 +212,6 @@ module.exports = {
|
|||||||
Auth: Auth,
|
Auth: Auth,
|
||||||
master: master,
|
master: master,
|
||||||
nobody: nobody,
|
nobody: nobody,
|
||||||
getAuthForSessionToken: getAuthForSessionToken
|
getAuthForSessionToken,
|
||||||
|
getAuthForLegacySessionToken
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import ClassesRouter from './ClassesRouter';
|
|||||||
import PromiseRouter from '../PromiseRouter';
|
import PromiseRouter from '../PromiseRouter';
|
||||||
import rest from '../rest';
|
import rest from '../rest';
|
||||||
import Auth from '../Auth';
|
import Auth from '../Auth';
|
||||||
|
import RestWrite from '../RestWrite';
|
||||||
|
import { newToken } from '../cryptoUtils';
|
||||||
|
|
||||||
export class SessionsRouter extends ClassesRouter {
|
export class SessionsRouter extends ClassesRouter {
|
||||||
handleFind(req) {
|
handleFind(req) {
|
||||||
@@ -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() {
|
mountRoutes() {
|
||||||
this.route('GET', '/sessions', req => { return this.handleFind(req); });
|
this.route('GET', '/sessions', req => { return this.handleFind(req); });
|
||||||
this.route('GET', '/sessions/:objectId', req => { return this.handleGet(req); });
|
this.route('GET', '/sessions/:objectId', req => { return this.handleGet(req); });
|
||||||
this.route('POST', '/sessions', req => { return this.handleCreate(req); });
|
this.route('POST', '/sessions', req => { return this.handleCreate(req); });
|
||||||
this.route('PUT', '/sessions/:objectId', req => { return this.handleUpdate(req); });
|
this.route('PUT', '/sessions/:objectId', req => { return this.handleUpdate(req); });
|
||||||
this.route('DELETE', '/sessions/:objectId', req => { return this.handleDelete(req); });
|
this.route('DELETE', '/sessions/:objectId', req => { return this.handleDelete(req); });
|
||||||
|
this.route('POST', '/upgradeToRevocableSession', req => { return this.handleUpdateToRevocableSession(req); })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -147,8 +147,16 @@ export function handleParseHeaders(req, res, next) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return auth.getAuthForSessionToken({ config: req.config, installationId: info.installationId, sessionToken: info.sessionToken })
|
return Promise.resolve().then(() => {
|
||||||
.then((auth) => {
|
// 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) {
|
if (auth) {
|
||||||
req.auth = auth;
|
req.auth = auth;
|
||||||
next();
|
next();
|
||||||
|
|||||||
Reference in New Issue
Block a user