'use strict'; exports = module.exports = { add, get, update, del, delByAccessToken, delExpired, listByUserId, getByAccessToken, validateTokenType, // token client ids. we categorize them so we can have different restrictions based on the client ID_WEBADMIN: 'cid-webadmin', // dashboard oauth ID_SDK: 'cid-sdk', // created by user via dashboard ID_CLI: 'cid-cli' // created via cli tool }; const TOKENS_FIELDS = [ 'id', 'accessToken', 'identifier', 'clientId', 'scope', 'expires', 'name', 'lastUsedTime' ].join(','); const assert = require('assert'), BoxError = require('./boxerror.js'), database = require('./database.js'), hat = require('./hat.js'), uuid = require('uuid'); function validateTokenName(name) { assert.strictEqual(typeof name, 'string'); if (name.length > 64) return new BoxError(BoxError.BAD_FIELD, 'name too long', { field: 'name' }); return null; } function validateTokenType(type) { assert.strictEqual(typeof type, 'string'); const types = [ exports.ID_WEBADMIN, exports.ID_CLI, exports.ID_SDK ]; if (types.indexOf(type) === -1) return BoxError(BoxError.BAD_FIELD, `type must be one of ${types.join(',')}`); return null; } async function add(token) { assert.strictEqual(typeof token, 'object'); const { clientId, identifier, expires } = token; const name = token.name || ''; const error = validateTokenName(name); if (error) throw error; const id = 'tid-' + uuid.v4(); const accessToken = hat(8 * 32); const scope = 'unused'; await database.query('INSERT INTO tokens (id, accessToken, identifier, clientId, expires, scope, name) VALUES (?, ?, ?, ?, ?, ?, ?)', [ id, accessToken, identifier, clientId, expires, scope, name ]); return { id, accessToken, scope, clientId, identifier, expires, name }; } async function get(id) { assert.strictEqual(typeof id, 'string'); const result = await database.query(`SELECT ${TOKENS_FIELDS} FROM tokens WHERE id = ?`, [ id ]); if (result.length === 0) return null; return result[0]; } async function del(id) { assert.strictEqual(typeof id, 'string'); const result = await database.query('DELETE FROM tokens WHERE id = ?', [ id ]); if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Token not found'); } async function listByUserId(userId) { assert.strictEqual(typeof userId, 'string'); return await database.query(`SELECT ${TOKENS_FIELDS} FROM tokens WHERE identifier = ?`, [ userId ]); } async function getByAccessToken(accessToken) { assert.strictEqual(typeof accessToken, 'string'); const result = await database.query('SELECT ' + TOKENS_FIELDS + ' FROM tokens WHERE accessToken = ? AND expires > ?', [ accessToken, Date.now() ]); if (result.length === 0) return null; return result[0]; } async function delByAccessToken(accessToken) { assert.strictEqual(typeof accessToken, 'string'); const result = await database.query('DELETE FROM tokens WHERE accessToken = ?', [ accessToken ]); if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Token not found'); } async function delExpired() { const result = await database.query('DELETE FROM tokens WHERE expires <= ?', [ Date.now() ]); return result.affectedRows; } async function update(id, values) { assert.strictEqual(typeof id, 'string'); assert.strictEqual(typeof values, 'object'); let args = [ ]; let fields = [ ]; for (let k in values) { fields.push(k + ' = ?'); args.push(values[k]); } args.push(id); const result = await database.query('UPDATE tokens SET ' + fields.join(', ') + ' WHERE id = ?', args); if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Token not found'); }