Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swapped to JWT tokens #345

Merged
merged 1 commit into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions lib/generateJwt.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
var jwt = require('jsonwebtoken');

module.exports = function (config) {
module.exports = function (config, additionalClaims) {
var currentTime = Math.floor(new Date() / 1000);
var token = jwt.sign({
const initialClaims = {
iss: config.apiKey,
ist: 'project',
iat: currentTime,
exp: currentTime + config.auth.expire
}, config.apiSecret);
exp: additionalClaims?.expire_time || currentTime + config.auth.expire
};

const claims = {...initialClaims, ...additionalClaims};
const token = jwt.sign(claims, config.apiSecret);

return token;
};
10 changes: 7 additions & 3 deletions lib/opentok.js
Original file line number Diff line number Diff line change
Expand Up @@ -2128,7 +2128,7 @@ OpenTok.prototype.createSession = function (opts, callback) {
* @return The token string.
*/

OpenTok.prototype.generateToken = function (sessionId, opts) {
OpenTok.prototype.generateToken = function (sessionId, opts, generateT1Token = false) {
var decoded;
var tokenData;
var now = Math.round(new Date().getTime() / 1000);
Expand Down Expand Up @@ -2207,8 +2207,12 @@ OpenTok.prototype.generateToken = function (sessionId, opts) {
+ 'concatenated length of less than 1024');
}

return encodeToken(tokenData, this.apiKey, this.apiSecret);

if (generateT1Token) {
return encodeToken(tokenData, this.apiKey, this.apiSecret);
}

tokenData.scope = 'session.connect';
return generateJwt(this.client.c, tokenData);
};

/*
Expand Down
207 changes: 191 additions & 16 deletions test/opentok-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -907,6 +907,179 @@ describe('#generateToken', function () {
var token = this.opentok.generateToken(this.sessionId);
var decoded;
expect(token).to.be.a('string');
decoded = jwt.verify(token, apiSecret);
expect(decoded.session_id).to.equal(this.sessionId);
expect(decoded.create_time).to.exist;
expect(decoded.nonce).to.exist;
expect(decoded.role).to.equal('publisher');
});

it('assigns a role in the token', function () {
// expects one with no role defined to assign "publisher"
var defaultRoleToken = this.opentok.generateToken(this.sessionId);
var decoded;
var subscriberToken;
expect(defaultRoleToken).to.be.a('string');
decoded = jwt.verify(defaultRoleToken, apiSecret);
expect(decoded.role).to.equal('publisher');

// expects one with a valid role defined to set it
subscriberToken = this.opentok.generateToken(this.sessionId, {
role: 'subscriber'
});
expect(subscriberToken).to.be.a('string');
decoded = jwt.verify(subscriberToken, apiSecret);
expect(decoded.role).to.equal('subscriber');

// expects one with an invalid role to complain
expect(function () {
this.opentok.generateToken(this.sessionId, { role: 5 });
}).to.throw(Error);
});

it('sets an expiration time for the token', function () {
var now = Math.round(new Date().getTime() / 1000);
var delta = 10;
var decoded;
var expireTime;

// expects a token with no expiration time to assign 1 day
var inOneDay = now + (60 * 60 * 24);
var defaultExpireToken = this.opentok.generateToken(this.sessionId);
var oneHourToken;
var inOneHour;
var oneHourAgo;

var roundedToken;

expect(defaultExpireToken).to.be.a('string');
decoded = jwt.verify(defaultExpireToken, apiSecret);
expireTime = parseInt(decoded.expire_time, 10);
expect(expireTime).to.be.closeTo(inOneDay, delta);
// expects a token with an expiration time to have it
inOneHour = now + (60 * 60);
oneHourToken = this.opentok.generateToken(this.sessionId, {
expireTime: inOneHour
});
expect(oneHourToken).to.be.a('string');
decoded = jwt.verify(oneHourToken, apiSecret);
expireTime = parseInt(decoded.expire_time, 10);
expect(expireTime).to.be.closeTo(inOneHour, delta);

// expects a token with an invalid expiration time to complain
expect(function () {
this.opentok.generateToken(this.sessionId, { expireTime: 'not a time' });
}).to.throw(Error);

oneHourAgo = now - (60 * 60);
expect(function () {
this.opentok.generateToken(this.sessionId, { expireTime: oneHourAgo });
}).to.throw(Error);

// rounds down fractional expiration time
const fractionalExpireTime = now + 60.5;
roundedToken = this.opentok.generateToken(this.sessionId, {
expireTime: fractionalExpireTime
});
decoded = jwt.verify(roundedToken, apiSecret);
expect(decoded.exp).to.equal(Math.round(fractionalExpireTime));
});

it('sets initial layout class list in the token', function () {
var layoutClassList = ['focus', 'inactive'];
var singleLayoutClass = 'focus';
var layoutBearingToken = this.opentok.generateToken(this.sessionId, {
initialLayoutClassList: layoutClassList
});
var decoded = jwt.verify(layoutBearingToken, apiSecret);
var singleLayoutBearingToken = this.opentok.generateToken(this.sessionId, {
initialLayoutClassList: singleLayoutClass
});

expect(layoutBearingToken).to.be.a('string');
expect(decoded.initial_layout_class_list).to.equal(layoutClassList.join(' '));
expect(singleLayoutBearingToken).to.be.a('string');
decoded = jwt.verify(singleLayoutBearingToken, apiSecret);
expect(decoded.initial_layout_class_list).to.equal(singleLayoutClass);

// NOTE: ignores invalid options instead of throwing an error, except if its too long
});

it('complains if the sessionId is not valid', function () {
expect(function () {
this.opentok.generateToken();
}).to.throw(Error);
});

it('sets connection data in the token', function () {
// expects a token with a connection data to have it
var sampleData = 'name=Johnny';
var decoded;
var dataBearingToken = this.opentok.generateToken(this.sessionId, {
data: sampleData
});
expect(dataBearingToken).to.be.a('string');
decoded = jwt.verify(dataBearingToken, apiSecret);
expect(decoded.connection_data).to.equal(sampleData);

// expects a token with invalid connection data to complain
expect(function () {
this.opentok.generateToken(this.sessionId, { data: { dont: 'work' } });
}).to.throw(Error);

expect(function () {
this.opentok.generateToken(this.sessionId, {
data: Array(2000).join('a') // 1999 char string of all 'a's
});
}).to.throw(Error);
});

it('complains if the sessionId is not valid', function () {
expect(function () {
this.opentok.generateToken();
}).to.throw(Error);

expect(function () {
this.opentok.generateToken('blahblahblah');
}).to.throw(Error);
});

it('contains a unique nonce', function () {
// generate a few and show the nonce exists each time and that they are different
var tokens = [
this.opentok.generateToken(this.sessionId),
this.opentok.generateToken(this.sessionId)
];
var nonces = _.map(tokens, function (token) {
return jwt.verify(token, apiSecret).nonce;
});

expect(_.uniq(nonces)).to.have.length(nonces.length);
});

it('does not modify passed in options', function () {
var options = { data: 'test' };
var optionsUntouched = _.clone(options);
this.opentok.generateToken(this.sessionId, options);
expect(options).to.deep.equal(optionsUntouched);
});
});

describe('#generateTokenLegacy', function () {
afterEach(function () {
nock.cleanAll();
});

beforeEach(function () {
this.opentok = new OpenTok(apiKey, apiSecret);
this.sessionId = sessionId;
});

it('given a valid session, generates a token', function () {
// call generateToken with no options
var token = this.opentok.generateToken(this.sessionId, {}, true);
var decoded;
expect(token).to.be.a('string');
expect(helpers.verifyTokenSignature(token, apiSecret)).to.be.true;
decoded = helpers.decodeToken(token);
expect(decoded.partner_id).to.equal(apiKey);
Expand All @@ -916,7 +1089,7 @@ describe('#generateToken', function () {

it('assigns a role in the token', function () {
// expects one with no role defined to assign "publisher"
var defaultRoleToken = this.opentok.generateToken(this.sessionId);
var defaultRoleToken = this.opentok.generateToken(this.sessionId, {}, true);
var decoded;
var subscriberToken;
expect(defaultRoleToken).to.be.a('string');
Expand All @@ -928,15 +1101,17 @@ describe('#generateToken', function () {
// expects one with a valid role defined to set it
subscriberToken = this.opentok.generateToken(this.sessionId, {
role: 'subscriber'
});
},
true
);
expect(subscriberToken).to.be.a('string');
expect(helpers.verifyTokenSignature(subscriberToken, apiSecret)).to.be.true;
decoded = helpers.decodeToken(subscriberToken);
expect(decoded.role).to.equal('subscriber');

// expects one with an invalid role to complain
expect(function () {
this.opentok.generateToken(this.sessionId, { role: 5 });
this.opentok.generateToken(this.sessionId, { role: 5 }, true);
}).to.throw(Error);
});

Expand All @@ -948,7 +1123,7 @@ describe('#generateToken', function () {

// expects a token with no expiration time to assign 1 day
var inOneDay = now + (60 * 60 * 24);
var defaultExpireToken = this.opentok.generateToken(this.sessionId);
var defaultExpireToken = this.opentok.generateToken(this.sessionId, {}, true);
var oneHourToken;
var inOneHour;
var oneHourAgo;
Expand All @@ -967,7 +1142,7 @@ describe('#generateToken', function () {
inOneHour = now + (60 * 60);
oneHourToken = this.opentok.generateToken(this.sessionId, {
expireTime: inOneHour
});
}, true);
expect(oneHourToken).to.be.a('string');
expect(helpers.verifyTokenSignature(oneHourToken, apiSecret)).to.be.true;
decoded = helpers.decodeToken(oneHourToken);
Expand All @@ -976,19 +1151,19 @@ describe('#generateToken', function () {

// expects a token with an invalid expiration time to complain
expect(function () {
this.opentok.generateToken(this.sessionId, { expireTime: 'not a time' });
this.opentok.generateToken(this.sessionId, { expireTime: 'not a time' }, true);
}).to.throw(Error);

oneHourAgo = now - (60 * 60);
expect(function () {
this.opentok.generateToken(this.sessionId, { expireTime: oneHourAgo });
this.opentok.generateToken(this.sessionId, { expireTime: oneHourAgo }, true);
}).to.throw(Error);

// rounds down fractional expiration time
fractionalExpireTime = now + 60.5;
roundedToken = this.opentok.generateToken(this.sessionId, {
expireTime: fractionalExpireTime
});
}, true);
expect(helpers.verifyTokenSignature(roundedToken, apiSecret)).to.be.true;
decoded = helpers.decodeToken(roundedToken);
expect(decoded.expire_time).to.equal(Math.round(fractionalExpireTime).toString());
Expand All @@ -999,11 +1174,11 @@ describe('#generateToken', function () {
var singleLayoutClass = 'focus';
var layoutBearingToken = this.opentok.generateToken(this.sessionId, {
initialLayoutClassList: layoutClassList
});
}, true);
var decoded = helpers.decodeToken(layoutBearingToken);
var singleLayoutBearingToken = this.opentok.generateToken(this.sessionId, {
initialLayoutClassList: singleLayoutClass
});
}, true);

expect(layoutBearingToken).to.be.a('string');
expect(helpers.verifyTokenSignature(layoutBearingToken, apiSecret)).to.be
Expand All @@ -1030,7 +1205,7 @@ describe('#generateToken', function () {
var decoded;
var dataBearingToken = this.opentok.generateToken(this.sessionId, {
data: sampleData
});
}, true);
expect(dataBearingToken).to.be.a('string');
expect(helpers.verifyTokenSignature(dataBearingToken, apiSecret)).to.be
.true;
Expand All @@ -1039,7 +1214,7 @@ describe('#generateToken', function () {

// expects a token with invalid connection data to complain
expect(function () {
this.opentok.generateToken(this.sessionId, { data: { dont: 'work' } });
this.opentok.generateToken(this.sessionId, { data: { dont: 'work' } }, true);
}).to.throw(Error);

expect(function () {
Expand All @@ -1055,15 +1230,15 @@ describe('#generateToken', function () {
}).to.throw(Error);

expect(function () {
this.opentok.generateToken('blahblahblah');
this.opentok.generateToken('blahblahblah', {}, true);
}).to.throw(Error);
});

it('contains a unique nonce', function () {
// generate a few and show the nonce exists each time and that they are different
var tokens = [
this.opentok.generateToken(this.sessionId),
this.opentok.generateToken(this.sessionId)
this.opentok.generateToken(this.sessionId, {}, true),
this.opentok.generateToken(this.sessionId, {}, true)
];
var nonces = _.map(tokens, function (token) {
return helpers.decodeToken(token).nonce;
Expand All @@ -1074,7 +1249,7 @@ describe('#generateToken', function () {
it('does not modify passed in options', function () {
var options = { data: 'test' };
var optionsUntouched = _.clone(options);
this.opentok.generateToken(this.sessionId, options);
this.opentok.generateToken(this.sessionId, options, true);
expect(options).to.deep.equal(optionsUntouched);
});
});
Expand Down
Loading