'use strict'; exports = module.exports = { get, add, list, del, removePrivateFields }; const assert = require('assert'), BoxError = require('./boxerror.js'), crypto = require('crypto'), database = require('./database.js'), hat = require('./hat.js'), safe = require('safetydance'), uuid = require('uuid'), _ = require('underscore'); const APP_PASSWORD_FIELDS = [ 'id', 'name', 'userId', 'identifier', 'hashedPassword', 'creationTime' ].join(','); function validateAppPasswordName(name) { assert.strictEqual(typeof name, 'string'); if (name.length < 1) return new BoxError(BoxError.BAD_FIELD, 'name must be atleast 1 char'); if (name.length >= 200) return new BoxError(BoxError.BAD_FIELD, 'name too long'); return null; } function removePrivateFields(appPassword) { return _.pick(appPassword, 'id', 'name', 'userId', 'identifier', 'creationTime'); } async function get(id) { assert.strictEqual(typeof id, 'string'); const result = await database.query('SELECT ' + APP_PASSWORD_FIELDS + ' FROM appPasswords WHERE id = ?', [ id ]); if (result.length === 0) return null; return result[0]; } async function add(userId, identifier, name) { assert.strictEqual(typeof userId, 'string'); assert.strictEqual(typeof identifier, 'string'); assert.strictEqual(typeof name, 'string'); let error = validateAppPasswordName(name); if (error) throw error; if (identifier.length < 1) throw new BoxError(BoxError.BAD_FIELD, 'identifier must be atleast 1 char'); const password = hat(16 * 4); const hashedPassword = crypto.createHash('sha256').update(password).digest('base64'); const appPassword = { id: 'uid-' + uuid.v4(), name, userId, identifier, password, hashedPassword }; const query = 'INSERT INTO appPasswords (id, userId, identifier, name, hashedPassword) VALUES (?, ?, ?, ?, ?)'; const args = [ appPassword.id, appPassword.userId, appPassword.identifier, appPassword.name, appPassword.hashedPassword ]; [error] = await safe(database.query(query, args)); if (error && error.code === 'ER_DUP_ENTRY' && error.sqlMessage.indexOf('appPasswords_name_userId_identifier') !== -1) throw new BoxError(BoxError.ALREADY_EXISTS, 'name/app combination already exists'); if (error && error.code === 'ER_NO_REFERENCED_ROW_2' && error.sqlMessage.indexOf('userId')) throw new BoxError(BoxError.NOT_FOUND, 'user not found'); if (error) throw error; return { id: appPassword.id, password: appPassword.password }; } async function list(userId) { assert.strictEqual(typeof userId, 'string'); return await database.query('SELECT ' + APP_PASSWORD_FIELDS + ' FROM appPasswords WHERE userId = ?', [ userId ]); } async function del(id) { assert.strictEqual(typeof id, 'string'); const result = await database.query('DELETE FROM appPasswords WHERE id = ?', [ id ]); if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'password not found'); }