diff --git a/.mdeprc.js b/.mdeprc.js index 460c4770..8d49f990 100644 --- a/.mdeprc.js +++ b/.mdeprc.js @@ -14,7 +14,7 @@ if (os.platform() !== 'darwin') { } catch (e) { } } -exports.node = "20"; +exports.node = "22.13.1"; exports.in_one = true; exports.auto_compose = true; exports.with_local_compose = true; diff --git a/Dockerfile b/Dockerfile index 326fd1ba..28732fd8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,7 @@ FROM makeomatic/node:$NODE_VERSION ENV NCONF_NAMESPACE=MS_USERS \ - NODE_ENV=$NODE_ENV \ - COREPACK_ENABLE_NETWORK=1 + NODE_ENV=$NODE_ENV WORKDIR /src @@ -12,7 +11,6 @@ RUN \ apk --update --upgrade \ add ca-certificates --virtual .buildDeps git ca-certificates openssl g++ make python3 linux-headers \ && update-ca-certificates \ - && corepack install -g pnpm@9 \ && chown node:node /src \ && su node sh -c "cd /src && pnpm fetch --prod" \ && su node sh -c "rm -rf ~/.cache && pnpm store prune" \ diff --git a/src/utils/contacts.js b/src/utils/contacts.js index 627a7570..e42e7d54 100644 --- a/src/utils/contacts.js +++ b/src/utils/contacts.js @@ -3,7 +3,7 @@ const { HttpStatusError } = require('@microfleet/validation'); const challengeAct = require('./challenges/challenge'); const redisKey = require('./key'); const handlePipeline = require('./pipeline-error'); -const { getInternalData, getUserId } = require('./userData'); +const { getUserId } = require('./userData'); const { USERS_CONTACTS, USERS_DEFAULT_CONTACT, @@ -64,11 +64,8 @@ async function removeAllEmailContactsOfUser(redisPipe, userId, exceptEmail) { } } -async function replaceUserName(redisPipe, userId, verifiedEmail) { +async function setUserName(redisPipe, userId, verifiedEmail) { const { config: { jwt: { defaultAudience } } } = this; - const internalData = await getInternalData.call(this, userId); - const username = internalData[USERS_USERNAME_FIELD]; - redisPipe.hdel(USERS_USERNAME_TO_ID, username); redisPipe.hset(USERS_USERNAME_TO_ID, verifiedEmail, userId); redisPipe.hset(redisKey(userId, USERS_DATA), USERS_USERNAME_FIELD, verifiedEmail); redisPipe.hset(redisKey(userId, USERS_METADATA, defaultAudience), USERS_USERNAME_FIELD, JSON.stringify(verifiedEmail)); @@ -176,7 +173,7 @@ async function verifyEmail({ secret }) { await removeAllEmailContactsOfUser.call(this, pipe, userId, contact.value); } if (this.config.contacts.updateUsername) { - await replaceUserName.call(this, pipe, userId, contact.value); + await setUserName.call(this, pipe, userId, contact.value); } pipe.hset(key, 'verified', 'true'); metadata.contact.verified = true; @@ -241,6 +238,10 @@ async function remove({ userId, contact }) { pipe.del(key); pipe.srem(redisKey(userId, USERS_CONTACTS), contact.value); + if (this.config.contacts.updateUsername) { + pipe.hdel(USERS_USERNAME_TO_ID, contact.value); + } + return pipe.exec().then(handlePipeline); } diff --git a/test/suites/actions/contacts.js b/test/suites/actions/contacts.js index 2fa4b701..f4987b00 100644 --- a/test/suites/actions/contacts.js +++ b/test/suites/actions/contacts.js @@ -4,6 +4,7 @@ const { faker } = require('@faker-js/faker'); const sinon = require('sinon'); const { createMembers } = require('../../helpers/organization'); const { startService, clearRedis } = require('../../config'); +const { USERS_USERNAME_TO_ID } = require('../../../src/constants'); describe('#user contacts', function registerSuite() { const audience = '*.localhost'; @@ -288,4 +289,25 @@ describe('#user contacts', function registerSuite() { assert.equal(name, params.metadata.name); amqpStub.restore(); }); + + it('should remove username to userid mapping on contact removal', async function test() { + const params = { + username: this.testUser.username, + contact: { + value: 'email@mail.org', + type: 'email', + }, + }; + await this.users.dispatch('contacts.add', { params }); + const amqpStub = sinon.stub(this.users.amqp, 'publish'); + amqpStub.withArgs('mailer.predefined').resolves({ queued: true }); + await this.users.dispatch('contacts.challenge', { params }); + const { ctx: { template: { token: { secret } } } } = amqpStub.args[0][1]; + await this.users.dispatch('contacts.verify-email', { params: { secret } }); + const userid = await this.users.redis.hget(USERS_USERNAME_TO_ID, params.contact.value); + assert.notEqual(userid, null); + await this.users.dispatch('contacts.remove', { params }); + const useridRemoved = await this.users.redis.hget(USERS_USERNAME_TO_ID, params.contact.value); + assert.equal(useridRemoved, null); + }); }); diff --git a/test/suites/actions/invite.js b/test/suites/actions/invite.js index 7efda6e1..81b6f033 100644 --- a/test/suites/actions/invite.js +++ b/test/suites/actions/invite.js @@ -76,9 +76,10 @@ describe('#invite', function registerSuite() { password: '123', inviteToken: this.invitationToken, audience: '*.localhost', - } }), { - name: 'AssertionError', - message: `Sanity check failed for "id" failed: "abnormal@yandex.ru" vs "${email}"`, + } }), (err) => { + assert.strictEqual(err.name, 'AssertionError'); + assert.match(err.message, /Sanity check failed for "id" failed: "abnormal@yandex.ru" vs "v@yandex.ru"/); + return true; }); });