diff --git a/src/api/utils.js b/src/api/utils.js index d0bdc82f85..73f8bb67b4 100644 --- a/src/api/utils.js +++ b/src/api/utils.js @@ -28,16 +28,22 @@ utils.tokens.get = async (tokens) => { singular = true; } - const [tokenObjs, lastSeen] = await Promise.all([ + let [tokenObjs, lastSeen] = await Promise.all([ db.getObjects(tokens.map(t => `token:${t}`)), utils.tokens.getLastSeen(tokens), ]); - tokenObjs.forEach((tokenObj, idx) => { + tokenObjs = tokenObjs.map((tokenObj, idx) => { + if (!tokenObj) { + return null; + } + tokenObj.token = tokens[idx]; tokenObj.lastSeen = lastSeen[idx]; tokenObj.lastSeenISO = lastSeen[idx] ? new Date(lastSeen[idx]).toISOString() : null; tokenObj.timestampISO = new Date(parseInt(tokenObj.timestamp, 10)).toISOString(); + + return tokenObj; }); return singular ? tokenObjs[0] : tokenObjs; diff --git a/test/tokens.js b/test/tokens.js index 65ccfbffb9..2af4221bac 100644 --- a/test/tokens.js +++ b/test/tokens.js @@ -30,7 +30,7 @@ describe('API tokens', () => { describe('.create()', () => { it('should fail to create a token for a user that does not exist', async () => { - assert.rejects(api.utils.tokens.generate({ uid: 1 }), '[[error:no-user]]'); + assert.rejects(api.utils.tokens.generate({ uid: 1 }), { message: '[[error:no-user]]' }); }); it('should create a token for a user that exists', async () => { @@ -63,7 +63,23 @@ describe('API tokens', () => { }); it('should fail if you pass in invalid data', async () => { - assert.rejects(api.utils.tokens.get(), '[[error:invalid-data]]'); + assert.rejects(api.utils.tokens.get(token), { message: '[[error:invalid-data]]' }); + }); + + it('should show lastSeen and lastSeenISO as undefined/null if it is a new token', async () => { + const { lastSeen, lastSeenISO } = await api.utils.tokens.get(token); + + assert.strictEqual(lastSeen, null); + assert.strictEqual(lastSeenISO, null); + }); + + it('should show lastSeenISO as an ISO formatted datetime string if the token has been used', async () => { + const now = new Date(); + await db.sortedSetAdd('tokens:lastSeen', now.getTime(), token); + const { lastSeen, lastSeenISO } = await api.utils.tokens.get(token); + + assert.strictEqual(lastSeen, now.getTime()); + assert.strictEqual(lastSeenISO, now.toISOString()); }); }); @@ -101,6 +117,26 @@ describe('API tokens', () => { }); }); + describe('.roll()', () => { + it('should invalidate the old token', async () => { + const newToken = await api.utils.tokens.roll(token); + assert(newToken); + + const gets = await api.utils.tokens.get([token, newToken]); + assert.strictEqual(gets[0], null); + assert(gets[1]); + }); + + it('should change a token but leave all other metadata intact', async () => { + await api.utils.tokens.update(token, { uid: 1, description: 'foobar' }); + const newToken = await api.utils.tokens.roll(token); + const tokenObj = await api.utils.tokens.get(newToken); + + assert.strictEqual(parseInt(tokenObj.uid, 10), 1); + assert.strictEqual(tokenObj.description, 'foobar'); + }); + }); + describe('.delete()', () => { it('should delete a token from a system', async () => { await api.utils.tokens.delete(token);