2016-05-26 07:58:50 +00:00
|
|
|
/**
|
|
|
|
* Mailvelope - secure email with OpenPGP encryption for Webmail
|
|
|
|
* Copyright (C) 2016 Mailvelope GmbH
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License version 3
|
|
|
|
* as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
'use strict';
|
|
|
|
|
2017-08-22 04:13:15 +00:00
|
|
|
const config = require('config');
|
2016-05-27 17:57:48 +00:00
|
|
|
const util = require('./util');
|
2017-08-19 09:06:36 +00:00
|
|
|
const tpl = require('../email/templates');
|
2016-05-27 17:57:48 +00:00
|
|
|
|
2016-05-26 11:45:32 +00:00
|
|
|
/**
|
|
|
|
* Database documents have the format:
|
|
|
|
* {
|
2016-06-09 15:07:51 +00:00
|
|
|
* _id: ObjectId, // a randomly generated MongoDB document ID
|
|
|
|
* keyId: 'b8e4105cc9dedc77', // the 16 char key id in lowercase hex
|
|
|
|
* fingerprint: 'e3317db04d3958fd5f662c37b8e4105cc9dedc77', // the 40 char key fingerprint in lowercase hex
|
|
|
|
* userIds: [
|
|
|
|
* {
|
|
|
|
* name:'Jon Smith',
|
|
|
|
* email:'jon@smith.com',
|
2016-06-10 08:44:26 +00:00
|
|
|
* nonce: "6a314915c09368224b11df0feedbc53c", // random 32 char verifier used to prove ownership
|
2016-06-09 15:07:51 +00:00
|
|
|
* verified: true // if the user ID has been verified
|
|
|
|
* }
|
|
|
|
* ],
|
|
|
|
* created: Sat Oct 17 2015 12:17:03 GMT+0200 (CEST), // key creation time as JavaScript Date
|
2017-08-23 10:09:54 +00:00
|
|
|
* uploaded: Sat Oct 17 2015 12:17:03 GMT+0200 (CEST), // time of key upload as JavaScript Date
|
2016-06-09 15:07:51 +00:00
|
|
|
* algorithm: 'rsa_encrypt_sign', // primary key alogrithm
|
|
|
|
* keySize: 4096, // key length in bits
|
|
|
|
* publicKeyArmored: '-----BEGIN PGP PUBLIC KEY BLOCK----- ... -----END PGP PUBLIC KEY BLOCK-----'
|
2016-05-26 11:45:32 +00:00
|
|
|
* }
|
|
|
|
*/
|
|
|
|
const DB_TYPE = 'publickey';
|
2019-02-14 17:11:37 +00:00
|
|
|
const KEY_STATUS_VALID = 3;
|
2016-05-26 11:45:32 +00:00
|
|
|
|
2016-05-26 07:58:50 +00:00
|
|
|
/**
|
2016-05-28 13:17:46 +00:00
|
|
|
* A service that handlers PGP public keys queries to the database
|
2016-05-26 07:58:50 +00:00
|
|
|
*/
|
|
|
|
class PublicKey {
|
|
|
|
/**
|
2016-05-28 13:17:46 +00:00
|
|
|
* Create an instance of the service
|
2016-06-09 15:07:51 +00:00
|
|
|
* @param {Object} pgp An instance of the OpenPGP.js wrapper
|
2016-05-27 17:57:48 +00:00
|
|
|
* @param {Object} mongo An instance of the MongoDB client
|
|
|
|
* @param {Object} email An instance of the Email Sender
|
2016-05-26 07:58:50 +00:00
|
|
|
*/
|
2016-06-09 15:07:51 +00:00
|
|
|
constructor(pgp, mongo, email) {
|
|
|
|
this._pgp = pgp;
|
2016-05-26 07:58:50 +00:00
|
|
|
this._mongo = mongo;
|
2016-05-27 17:57:48 +00:00
|
|
|
this._email = email;
|
2016-05-26 07:58:50 +00:00
|
|
|
}
|
|
|
|
|
2016-05-27 17:57:48 +00:00
|
|
|
/**
|
|
|
|
* Persist a new public key
|
2019-02-14 17:11:37 +00:00
|
|
|
* @param {Array} emails (optional) The emails to upload/update
|
2016-05-29 14:47:45 +00:00
|
|
|
* @param {String} publicKeyArmored The ascii armored pgp key block
|
|
|
|
* @param {Object} origin Required for links to the keyserver e.g. { protocol:'https', host:'openpgpkeys@example.com' }
|
2019-06-13 10:14:51 +00:00
|
|
|
* @param {Object} ctx Context
|
2019-02-14 17:11:37 +00:00
|
|
|
* @return {Promise}
|
2016-05-27 17:57:48 +00:00
|
|
|
*/
|
2019-06-13 10:14:51 +00:00
|
|
|
async put({emails = [], publicKeyArmored, origin}, ctx) {
|
2019-03-06 14:47:46 +00:00
|
|
|
emails = emails.map(util.normalizeEmail);
|
2017-08-22 04:13:15 +00:00
|
|
|
// lazily purge old/unverified keys on every key upload
|
|
|
|
await this._purgeOldUnverified();
|
2016-05-27 17:57:48 +00:00
|
|
|
// parse key block
|
2019-02-07 12:53:49 +00:00
|
|
|
const key = await this._pgp.parseKey(publicKeyArmored);
|
2019-02-14 17:11:37 +00:00
|
|
|
// if emails array is empty, all userIds of the key will be submitted
|
|
|
|
if (emails.length) {
|
|
|
|
// keep submitted user IDs only
|
|
|
|
key.userIds = key.userIds.filter(({email}) => emails.includes(email));
|
|
|
|
if (key.userIds.length !== emails.length) {
|
|
|
|
util.throw(400, 'Provided email address does not match a valid user ID of the key');
|
|
|
|
}
|
|
|
|
}
|
2017-08-24 08:36:32 +00:00
|
|
|
// check for existing verified key with same id
|
|
|
|
const verified = await this.getVerified({keyId: key.keyId});
|
2016-05-27 17:57:48 +00:00
|
|
|
if (verified) {
|
2019-02-14 17:11:37 +00:00
|
|
|
key.userIds = await this._mergeUsers(verified.userIds, key.userIds, key.publicKeyArmored);
|
|
|
|
// reduce new key to verified user IDs
|
|
|
|
const filteredPublicKeyArmored = await this._pgp.filterKeyByUserIds(key.userIds.filter(({verified}) => verified), key.publicKeyArmored);
|
|
|
|
// update verified key with new key
|
|
|
|
key.publicKeyArmored = await this._pgp.updateKey(verified.publicKeyArmored, filteredPublicKeyArmored);
|
|
|
|
} else {
|
|
|
|
key.userIds = key.userIds.filter(userId => userId.status === KEY_STATUS_VALID);
|
2019-03-06 08:52:08 +00:00
|
|
|
if (!key.userIds.length) {
|
|
|
|
util.throw(400, 'Invalid PGP key: no valid user IDs found');
|
|
|
|
}
|
2019-02-14 17:11:37 +00:00
|
|
|
await this._addKeyArmored(key.userIds, key.publicKeyArmored);
|
|
|
|
// new key, set armored to null
|
|
|
|
key.publicKeyArmored = null;
|
2016-05-27 17:57:48 +00:00
|
|
|
}
|
2019-02-14 17:11:37 +00:00
|
|
|
// send mails to verify user ids
|
2019-06-13 10:14:51 +00:00
|
|
|
await this._sendVerifyEmail(key, origin, ctx);
|
2019-02-14 17:11:37 +00:00
|
|
|
// store key in database
|
|
|
|
await this._persistKey(key);
|
2016-05-26 11:45:32 +00:00
|
|
|
}
|
|
|
|
|
2017-08-22 04:13:15 +00:00
|
|
|
/**
|
2017-08-22 07:26:15 +00:00
|
|
|
* Delete all keys where no user id has been verified after x days.
|
2019-02-14 17:11:37 +00:00
|
|
|
* @return {Promise}
|
2017-08-22 04:13:15 +00:00
|
|
|
*/
|
|
|
|
async _purgeOldUnverified() {
|
|
|
|
// create date in the past to compare with
|
|
|
|
const xDaysAgo = new Date();
|
|
|
|
xDaysAgo.setDate(xDaysAgo.getDate() - config.publicKey.purgeTimeInDays);
|
|
|
|
// remove unverified keys older than x days (or no 'uploaded' attribute)
|
2017-08-25 08:11:35 +00:00
|
|
|
return this._mongo.remove({
|
2017-08-22 04:13:15 +00:00
|
|
|
'userIds.verified': {$ne: true},
|
2017-08-22 07:26:15 +00:00
|
|
|
uploaded: {$lt: xDaysAgo}
|
2017-08-25 08:11:35 +00:00
|
|
|
}, DB_TYPE);
|
2017-08-22 04:13:15 +00:00
|
|
|
}
|
|
|
|
|
2016-06-01 10:28:37 +00:00
|
|
|
/**
|
2019-02-14 17:11:37 +00:00
|
|
|
* Merge existing and new user IDs
|
|
|
|
* @param {Array} existingUsers source user IDs
|
|
|
|
* @param {Array} newUsers new user IDs
|
|
|
|
* @param {String} publicKeyArmored armored key block of new user IDs
|
|
|
|
* @return {Array} merged user IDs
|
2016-06-01 10:28:37 +00:00
|
|
|
*/
|
2019-02-14 17:11:37 +00:00
|
|
|
async _mergeUsers(existingUsers, newUsers, publicKeyArmored) {
|
|
|
|
const result = [];
|
|
|
|
// existing verified valid or revoked users
|
|
|
|
const verifiedUsers = existingUsers.filter(userId => userId.verified);
|
|
|
|
// valid new users which are not yet verified
|
|
|
|
const validUsers = newUsers.filter(userId => userId.status === KEY_STATUS_VALID && !this._includeEmail(verifiedUsers, userId));
|
|
|
|
// pending users are not verified, not newly submitted
|
|
|
|
const pendingUsers = existingUsers.filter(userId => !userId.verified && !this._includeEmail(validUsers, userId));
|
|
|
|
await this._addKeyArmored(validUsers, publicKeyArmored);
|
|
|
|
result.push(...validUsers, ...pendingUsers, ...verifiedUsers);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create amored key block which contains the corresponding user ID only and add it to the user ID object
|
|
|
|
* @param {Array} userIds user IDs to be extended
|
|
|
|
* @param {String} PublicKeyArmored armored key block to be filtered
|
|
|
|
* @return {Promise}
|
|
|
|
*/
|
|
|
|
async _addKeyArmored(userIds, publicKeyArmored) {
|
|
|
|
for (const userId of userIds) {
|
|
|
|
userId.publicKeyArmored = await this._pgp.filterKeyByUserIds([userId], publicKeyArmored);
|
|
|
|
userId.notify = true;
|
2016-06-01 10:28:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-14 17:11:37 +00:00
|
|
|
_includeEmail(users, user) {
|
|
|
|
return users.find(({email}) => email === user.email);
|
|
|
|
}
|
|
|
|
|
2016-06-01 10:28:37 +00:00
|
|
|
/**
|
|
|
|
* Send verification emails to the public keys user ids for verification.
|
|
|
|
* If a primary email address is provided only one email will be sent.
|
2016-06-02 14:19:54 +00:00
|
|
|
* @param {Array} userIds user id documents containg the verification nonces
|
|
|
|
* @param {Object} origin the server's origin (required for email links)
|
2019-06-13 10:14:51 +00:00
|
|
|
* @param {Object} ctx Context
|
2019-02-14 17:11:37 +00:00
|
|
|
* @return {Promise}
|
2016-06-01 10:28:37 +00:00
|
|
|
*/
|
2019-06-13 10:14:51 +00:00
|
|
|
async _sendVerifyEmail({userIds, keyId}, origin, ctx) {
|
2017-08-15 08:03:06 +00:00
|
|
|
for (const userId of userIds) {
|
2019-02-14 17:11:37 +00:00
|
|
|
if (userId.notify && userId.notify === true) {
|
|
|
|
// generate nonce for verification
|
|
|
|
userId.nonce = util.random();
|
2019-06-13 10:14:51 +00:00
|
|
|
await this._email.send({template: tpl.verifyKey.bind(null, ctx), userId, keyId, origin, publicKeyArmored: userId.publicKeyArmored});
|
2019-02-14 17:11:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Persist the public key and its user ids in the database.
|
|
|
|
* @param {Object} key public key parameters
|
|
|
|
* @return {Promise}
|
|
|
|
*/
|
|
|
|
async _persistKey(key) {
|
|
|
|
// delete old/unverified key
|
|
|
|
await this._mongo.remove({keyId: key.keyId}, DB_TYPE);
|
|
|
|
// generate nonces for verification
|
|
|
|
for (const userId of key.userIds) {
|
|
|
|
// remove status from user
|
|
|
|
delete userId.status;
|
|
|
|
// remove notify flag from user
|
|
|
|
delete userId.notify;
|
|
|
|
}
|
|
|
|
// persist new key
|
|
|
|
const r = await this._mongo.create(key, DB_TYPE);
|
|
|
|
if (r.insertedCount !== 1) {
|
|
|
|
util.throw(500, 'Failed to persist key');
|
2016-06-01 10:28:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-27 17:57:48 +00:00
|
|
|
/**
|
2016-06-09 15:07:51 +00:00
|
|
|
* Verify a user id by proving knowledge of the nonce.
|
|
|
|
* @param {string} keyId Correspronding public key id
|
|
|
|
* @param {string} nonce The verification nonce proving email address ownership
|
2019-06-13 10:14:51 +00:00
|
|
|
* @return {Promise} The email that has been verified
|
2016-05-27 17:57:48 +00:00
|
|
|
*/
|
2017-08-16 09:55:32 +00:00
|
|
|
async verify({keyId, nonce}) {
|
2016-06-09 15:07:51 +00:00
|
|
|
// look for verification nonce in database
|
2017-08-15 08:03:06 +00:00
|
|
|
const query = {keyId, 'userIds.nonce': nonce};
|
2017-08-16 09:55:32 +00:00
|
|
|
const key = await this._mongo.get(query, DB_TYPE);
|
2016-06-09 15:07:51 +00:00
|
|
|
if (!key) {
|
2019-03-06 08:52:08 +00:00
|
|
|
util.throw(404, 'User ID not found');
|
2016-05-27 17:57:48 +00:00
|
|
|
}
|
2017-08-24 08:36:32 +00:00
|
|
|
await this._removeKeysWithSameEmail(key, nonce);
|
2019-06-13 10:14:51 +00:00
|
|
|
let {publicKeyArmored, email} = key.userIds.find(userId => userId.nonce === nonce);
|
2019-02-14 17:11:37 +00:00
|
|
|
// update armored key
|
|
|
|
if (key.publicKeyArmored) {
|
|
|
|
publicKeyArmored = await this._pgp.updateKey(key.publicKeyArmored, publicKeyArmored);
|
|
|
|
}
|
2016-06-09 15:07:51 +00:00
|
|
|
// flag the user id as verified
|
2017-08-16 09:55:32 +00:00
|
|
|
await this._mongo.update(query, {
|
2019-02-14 17:11:37 +00:00
|
|
|
publicKeyArmored,
|
2016-06-09 15:07:51 +00:00
|
|
|
'userIds.$.verified': true,
|
2019-02-14 17:11:37 +00:00
|
|
|
'userIds.$.nonce': null,
|
|
|
|
'userIds.$.publicKeyArmored': null
|
2016-06-09 15:07:51 +00:00
|
|
|
}, DB_TYPE);
|
2019-06-13 10:14:51 +00:00
|
|
|
return {email};
|
2016-05-26 11:45:32 +00:00
|
|
|
}
|
|
|
|
|
2019-02-14 17:11:37 +00:00
|
|
|
/**
|
|
|
|
* Removes keys with the same email address
|
|
|
|
* @param {String} options.keyId source key ID
|
|
|
|
* @param {Array} options.userIds user IDs of source key
|
|
|
|
* @param {Array} nonce relevant nonce
|
|
|
|
* @return {Promise}
|
|
|
|
*/
|
2017-08-24 08:36:32 +00:00
|
|
|
async _removeKeysWithSameEmail({keyId, userIds}, nonce) {
|
2017-08-25 08:20:33 +00:00
|
|
|
return this._mongo.remove({
|
2017-08-24 08:36:32 +00:00
|
|
|
keyId: {$ne: keyId},
|
2017-08-25 08:20:33 +00:00
|
|
|
'userIds.email': userIds.find(u => u.nonce === nonce).email
|
2017-08-24 08:36:32 +00:00
|
|
|
}, DB_TYPE);
|
|
|
|
}
|
|
|
|
|
2016-06-02 20:55:32 +00:00
|
|
|
/**
|
2016-06-09 15:07:51 +00:00
|
|
|
* Check if a verified key already exists either by fingerprint, 16 char key id,
|
|
|
|
* or email address. There can only be one verified user ID for an email address
|
|
|
|
* at any given time.
|
|
|
|
* @param {Array} userIds A list of user ids to check
|
|
|
|
* @param {string} fingerprint The public key fingerprint
|
|
|
|
* @param {string} keyId (optional) The public key id
|
2019-02-14 17:11:37 +00:00
|
|
|
* @return {Object} The verified key document
|
2016-06-02 20:55:32 +00:00
|
|
|
*/
|
2017-08-16 09:55:32 +00:00
|
|
|
async getVerified({userIds, fingerprint, keyId}) {
|
2016-06-09 15:07:51 +00:00
|
|
|
let queries = [];
|
|
|
|
// query by fingerprint
|
|
|
|
if (fingerprint) {
|
|
|
|
queries.push({
|
|
|
|
fingerprint: fingerprint.toLowerCase(),
|
|
|
|
'userIds.verified': true
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// query by key id (to prevent key id collision)
|
|
|
|
if (keyId) {
|
|
|
|
queries.push({
|
|
|
|
keyId: keyId.toLowerCase(),
|
|
|
|
'userIds.verified': true
|
|
|
|
});
|
2016-06-02 20:55:32 +00:00
|
|
|
}
|
2016-06-09 15:07:51 +00:00
|
|
|
// query by user id
|
|
|
|
if (userIds) {
|
|
|
|
queries = queries.concat(userIds.map(uid => ({
|
|
|
|
userIds: {
|
|
|
|
$elemMatch: {
|
2019-03-06 14:47:46 +00:00
|
|
|
'email': util.normalizeEmail(uid.email),
|
2016-06-09 15:07:51 +00:00
|
|
|
'verified': true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})));
|
|
|
|
}
|
2017-08-16 09:55:32 +00:00
|
|
|
return this._mongo.get({$or: queries}, DB_TYPE);
|
2016-06-02 20:55:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-06-09 15:07:51 +00:00
|
|
|
* Fetch a verified public key from the database. Either the key id or the
|
|
|
|
* email address muss be provided.
|
|
|
|
* @param {string} fingerprint (optional) The public key fingerprint
|
|
|
|
* @param {string} keyId (optional) The public key id
|
|
|
|
* @param {String} email (optional) The user's email address
|
2019-06-13 10:14:51 +00:00
|
|
|
* @param {Object} ctx Context
|
2019-02-14 17:11:37 +00:00
|
|
|
* @return {Object} The public key document
|
2016-06-02 20:55:32 +00:00
|
|
|
*/
|
2019-06-13 10:14:51 +00:00
|
|
|
async get({fingerprint, keyId, email}, ctx) {
|
2016-06-09 15:07:51 +00:00
|
|
|
// look for verified key
|
2017-08-15 08:03:06 +00:00
|
|
|
const userIds = email ? [{email}] : undefined;
|
2017-08-16 09:55:32 +00:00
|
|
|
const key = await this.getVerified({keyId, fingerprint, userIds});
|
2016-06-09 15:07:51 +00:00
|
|
|
if (!key) {
|
2019-06-13 10:14:51 +00:00
|
|
|
util.throw(404, ctx.__('key_not_found'));
|
2016-06-09 15:07:51 +00:00
|
|
|
}
|
|
|
|
// clean json return value (_id, nonce)
|
|
|
|
delete key._id;
|
|
|
|
key.userIds = key.userIds.map(uid => ({
|
|
|
|
name: uid.name,
|
|
|
|
email: uid.email,
|
|
|
|
verified: uid.verified
|
|
|
|
}));
|
|
|
|
return key;
|
2016-06-02 20:55:32 +00:00
|
|
|
}
|
|
|
|
|
2016-06-01 06:59:25 +00:00
|
|
|
/**
|
|
|
|
* Request removal of the public key by flagging all user ids and sending
|
|
|
|
* a verification email to the primary email address. Only one email
|
|
|
|
* needs to sent to a single user id to authenticate removal of all user ids
|
|
|
|
* that belong the a certain key id.
|
2016-06-09 15:07:51 +00:00
|
|
|
* @param {String} keyId (optional) The public key id
|
2016-06-01 06:59:25 +00:00
|
|
|
* @param {String} email (optional) The user's email address
|
|
|
|
* @param {Object} origin Required for links to the keyserver e.g. { protocol:'https', host:'openpgpkeys@example.com' }
|
2019-06-13 10:14:51 +00:00
|
|
|
* @param {Object} ctx Context
|
2019-02-14 17:11:37 +00:00
|
|
|
* @return {Promise}
|
2016-06-01 06:59:25 +00:00
|
|
|
*/
|
2019-06-13 10:14:51 +00:00
|
|
|
async requestRemove({keyId, email, origin}, ctx) {
|
2016-06-10 17:58:26 +00:00
|
|
|
// flag user ids for removal
|
2017-08-16 09:55:32 +00:00
|
|
|
const key = await this._flagForRemove(keyId, email);
|
2016-06-10 11:17:28 +00:00
|
|
|
if (!key) {
|
2019-03-06 08:52:08 +00:00
|
|
|
util.throw(404, 'User ID not found');
|
2016-06-01 10:28:37 +00:00
|
|
|
}
|
2016-06-10 17:58:26 +00:00
|
|
|
// send verification mails
|
2016-06-10 11:17:28 +00:00
|
|
|
keyId = key.keyId; // get keyId in case request was by email
|
2017-08-15 08:03:06 +00:00
|
|
|
for (const userId of key.userIds) {
|
2019-06-13 10:14:51 +00:00
|
|
|
await this._email.send({template: tpl.verifyRemove.bind(null, ctx), userId, keyId, origin});
|
2016-06-09 15:07:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Flag all user IDs of a key for removal by generating a new nonce and
|
|
|
|
* saving it. Either a key id or email address must be provided
|
|
|
|
* @param {String} keyId (optional) The public key id
|
|
|
|
* @param {String} email (optional) The user's email address
|
2019-02-14 17:11:37 +00:00
|
|
|
* @return {Array} A list of user ids with nonces
|
2016-06-09 15:07:51 +00:00
|
|
|
*/
|
2017-08-16 09:55:32 +00:00
|
|
|
async _flagForRemove(keyId, email) {
|
2019-03-06 14:47:46 +00:00
|
|
|
email = util.normalizeEmail(email);
|
2017-08-15 08:03:06 +00:00
|
|
|
const query = email ? {'userIds.email': email} : {keyId};
|
2017-08-16 09:55:32 +00:00
|
|
|
const key = await this._mongo.get(query, DB_TYPE);
|
2016-06-09 15:07:51 +00:00
|
|
|
if (!key) {
|
2016-06-10 11:17:28 +00:00
|
|
|
return;
|
2016-06-09 15:07:51 +00:00
|
|
|
}
|
2016-06-10 17:58:26 +00:00
|
|
|
// flag only the provided user id
|
2016-06-09 15:07:51 +00:00
|
|
|
if (email) {
|
2017-08-15 08:03:06 +00:00
|
|
|
const nonce = util.random();
|
2017-08-16 09:55:32 +00:00
|
|
|
await this._mongo.update(query, {'userIds.$.nonce': nonce}, DB_TYPE);
|
2017-08-15 08:03:06 +00:00
|
|
|
const uid = key.userIds.find(u => u.email === email);
|
2016-06-09 15:07:51 +00:00
|
|
|
uid.nonce = nonce;
|
2017-08-15 08:03:06 +00:00
|
|
|
return {userIds: [uid], keyId: key.keyId};
|
2016-06-09 15:07:51 +00:00
|
|
|
}
|
2016-06-10 17:58:26 +00:00
|
|
|
// flag all key user ids
|
2016-06-09 15:07:51 +00:00
|
|
|
if (keyId) {
|
2017-08-15 08:03:06 +00:00
|
|
|
for (const uid of key.userIds) {
|
|
|
|
const nonce = util.random();
|
2017-08-16 09:55:32 +00:00
|
|
|
await this._mongo.update({'userIds.email': uid.email}, {'userIds.$.nonce': nonce}, DB_TYPE);
|
2016-06-09 15:07:51 +00:00
|
|
|
uid.nonce = nonce;
|
|
|
|
}
|
2016-06-10 11:17:28 +00:00
|
|
|
return key;
|
2016-06-01 06:59:25 +00:00
|
|
|
}
|
2016-05-26 11:45:32 +00:00
|
|
|
}
|
|
|
|
|
2016-06-01 06:59:25 +00:00
|
|
|
/**
|
|
|
|
* Verify the removal of the user's key id by proving knowledge of the nonce.
|
|
|
|
* Also deletes all user id documents of that key id.
|
2016-06-09 15:07:51 +00:00
|
|
|
* @param {string} keyId public key id
|
2016-06-01 06:59:25 +00:00
|
|
|
* @param {string} nonce The verification nonce proving email address ownership
|
2019-02-14 17:11:37 +00:00
|
|
|
* @return {Promise}
|
2016-06-01 06:59:25 +00:00
|
|
|
*/
|
2017-08-16 09:55:32 +00:00
|
|
|
async verifyRemove({keyId, nonce}) {
|
2016-06-10 17:58:26 +00:00
|
|
|
// check if key exists in database
|
2017-08-16 09:55:32 +00:00
|
|
|
const flagged = await this._mongo.get({keyId, 'userIds.nonce': nonce}, DB_TYPE);
|
2016-06-01 06:59:25 +00:00
|
|
|
if (!flagged) {
|
2019-03-06 08:52:08 +00:00
|
|
|
util.throw(404, 'User ID not found');
|
2016-06-01 06:59:25 +00:00
|
|
|
}
|
2019-02-14 17:11:37 +00:00
|
|
|
if (flagged.userIds.length === 1) {
|
|
|
|
// delete the key
|
2019-06-17 14:15:07 +00:00
|
|
|
await this._mongo.remove({keyId}, DB_TYPE);
|
|
|
|
return flagged.userIds[0];
|
2019-02-14 17:11:37 +00:00
|
|
|
}
|
|
|
|
// update the key
|
|
|
|
const rmIdx = flagged.userIds.findIndex(userId => userId.nonce === nonce);
|
|
|
|
const rmUserId = flagged.userIds[rmIdx];
|
|
|
|
if (rmUserId.verified) {
|
|
|
|
if (flagged.userIds.filter(({verified}) => verified).length > 1) {
|
|
|
|
flagged.publicKeyArmored = await this._pgp.removeUserId(rmUserId.email, flagged.publicKeyArmored);
|
|
|
|
} else {
|
|
|
|
flagged.publicKeyArmored = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
flagged.userIds.splice(rmIdx, 1);
|
|
|
|
await this._mongo.update({keyId}, flagged, DB_TYPE);
|
2019-06-17 14:15:07 +00:00
|
|
|
return rmUserId;
|
2016-05-26 11:45:32 +00:00
|
|
|
}
|
2016-05-26 07:58:50 +00:00
|
|
|
}
|
|
|
|
|
2017-08-15 08:03:06 +00:00
|
|
|
module.exports = PublicKey;
|