Skip to content

Commit 0dc98f2

Browse files
committed
feat: delete unconfirmed users code
1 parent 9e25f85 commit 0dc98f2

File tree

9 files changed

+213
-54
lines changed

9 files changed

+213
-54
lines changed

schemas/config.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"amqp",
66
"redis",
77
"deleteInactiveAccounts",
8+
"deleteInactiveAccountsInterval",
89
"jwt",
910
"validation",
1011
"server",
@@ -19,6 +20,10 @@
1920
"type": "integer",
2021
"minimum": 0
2122
},
23+
"deleteInactiveAccountsInterval": {
24+
"type": "integer",
25+
"minimum": 1
26+
},
2227
"admins": {
2328
"type": "array",
2429
"items": {

src/actions/activate.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const jwt = require('../utils/jwt.js');
55
const { getInternalData } = require('../utils/userData');
66
const getMetadata = require('../utils/getMetadata');
77
const handlePipeline = require('../utils/pipelineError.js');
8+
const { removeInactiveUser } = require('../utils/inactiveUsers');
89
const {
910
USERS_INDEX,
1011
USERS_DATA,
@@ -126,6 +127,8 @@ function activateAccount(data, metadata) {
126127
.persist(userKey)
127128
.sadd(USERS_INDEX, userId);
128129

130+
removeInactiveUser(pipeline, userId);
131+
129132
if (alias) {
130133
pipeline.sadd(USERS_PUBLIC_INDEX, userId);
131134
}

src/actions/register.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const checkLimits = require('../utils/checkIpLimits');
2222
const challenge = require('../utils/challenges/challenge');
2323
const handlePipeline = require('../utils/pipelineError');
2424
const hashPassword = require('../utils/register/password/hash');
25+
const { addInactiveUser } = require('../utils/inactiveUsers');
2526
const {
2627
USERS_REF,
2728
USERS_INDEX,
@@ -208,7 +209,7 @@ async function performRegistration({ service, params }) {
208209
pipeline.hset(USERS_USERNAME_TO_ID, username, userId);
209210

210211
if (activate === false && config.deleteInactiveAccounts >= 0) {
211-
pipeline.expire(userDataKey, config.deleteInactiveAccounts);
212+
addInactiveUser(pipeline, userId, audience);
212213
}
213214

214215
await pipeline.exec().then(handlePipeline);

src/actions/remove.js

Lines changed: 3 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,14 @@ const { ActionTransport } = require('@microfleet/core');
22
const Promise = require('bluebird');
33
const Errors = require('common-errors');
44
const intersection = require('lodash/intersection');
5-
const get = require('../utils/get-value');
6-
const key = require('../utils/key');
5+
const removeUserUtil = require('../utils/removeUser');
76
const { getInternalData } = require('../utils/userData');
87
const getMetadata = require('../utils/getMetadata');
98
const handlePipeline = require('../utils/pipelineError');
109
const {
11-
USERS_INDEX,
12-
USERS_PUBLIC_INDEX,
13-
USERS_ALIAS_TO_ID,
14-
USERS_SSO_TO_ID,
15-
USERS_USERNAME_TO_ID,
16-
USERS_USERNAME_FIELD,
17-
USERS_DATA,
18-
USERS_METADATA,
19-
USERS_TOKENS,
2010
USERS_ID_FIELD,
21-
USERS_ALIAS_FIELD,
2211
USERS_ADMIN_ROLE,
2312
USERS_SUPER_ADMIN_ROLE,
24-
USERS_ACTION_ACTIVATE,
25-
USERS_ACTION_RESET,
26-
USERS_ACTION_PASSWORD,
27-
USERS_ACTION_REGISTER,
28-
THROTTLE_PREFIX,
29-
SSO_PROVIDERS,
3013
} = require('../constants');
3114

3215
// intersection of priority users
@@ -69,41 +52,8 @@ async function removeUser({ params }) {
6952
}
7053

7154
const transaction = redis.pipeline();
72-
const alias = internal[USERS_ALIAS_FIELD];
73-
const userId = internal[USERS_ID_FIELD];
74-
const resolvedUsername = internal[USERS_USERNAME_FIELD];
75-
76-
if (alias) {
77-
transaction.hdel(USERS_ALIAS_TO_ID, alias.toLowerCase(), alias);
78-
}
79-
80-
transaction.hdel(USERS_USERNAME_TO_ID, resolvedUsername);
81-
82-
// remove refs to SSO account
83-
for (const provider of SSO_PROVIDERS) {
84-
const uid = get(internal, `${provider}.uid`, { default: false });
85-
86-
if (uid) {
87-
transaction.hdel(USERS_SSO_TO_ID, uid);
88-
}
89-
}
90-
91-
// clean indices
92-
transaction.srem(USERS_PUBLIC_INDEX, userId);
93-
transaction.srem(USERS_INDEX, userId);
94-
95-
// remove metadata & internal data
96-
transaction.del(key(userId, USERS_DATA));
97-
transaction.del(key(userId, USERS_METADATA, audience));
98-
99-
// remove auth tokens
100-
transaction.del(key(userId, USERS_TOKENS));
101-
102-
// remove throttling on actions
103-
transaction.del(key(THROTTLE_PREFIX, USERS_ACTION_ACTIVATE, userId));
104-
transaction.del(key(THROTTLE_PREFIX, USERS_ACTION_PASSWORD, userId));
105-
transaction.del(key(THROTTLE_PREFIX, USERS_ACTION_REGISTER, userId));
106-
transaction.del(key(THROTTLE_PREFIX, USERS_ACTION_RESET, userId));
55+
// assign remove logic
56+
removeUserUtil(transaction, internal, audience);
10757

10858
// complete it
10959
return transaction

src/configs/core.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ exports.name = 'ms-users';
1212
*/
1313
exports.deleteInactiveAccounts = 30 * 24 * 60 * 60;
1414

15+
/**
16+
* Seconds to run inactive account cleanup process
17+
* @type {Number} seconds - defaults to 1 day;
18+
*/
19+
exports.deleteInactiveAccountsInterval = 1 * 24 * 60 * 60;
20+
1521
/**
1622
* Flake ids - sequential unique 64 bit ids
1723
* NOTE: this is used for JWT issue id & should be used for the

src/constants.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ module.exports = exports = {
66
USERS_PUBLIC_INDEX: 'users-public',
77
USERS_REFERRAL_INDEX: 'users-referral',
88
ORGANIZATIONS_INDEX: 'organization-iterator-set',
9+
// inactive user id's list
10+
USERS_ACTIVATE: 'users-activate',
11+
// handles json list of audiences provided at registrations
12+
USERS_ACTIVATE_AUDIENCE: 'users-activate-audiences',
13+
914
// id mapping
1015
USERS_ALIAS_TO_ID: 'users-alias',
1116
USERS_SSO_TO_ID: 'users-sso-hash',

src/users.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const RedisCluster = require('ioredis').Cluster;
99
const Flakeless = require('ms-flakeless');
1010
const conf = require('./config');
1111
const get = require('./utils/get-value');
12+
const { getCleanerTask, stopCleanerTask } = require('./utils/inactiveUsers');
1213

1314
/**
1415
* @namespace Users
@@ -76,6 +77,7 @@ module.exports = class Users extends Microfleet {
7677
// init token manager
7778
const tokenManagerOpts = { backend: { connection: redis } };
7879
this.tokenManager = new TokenManager(merge({}, config.tokenManager, tokenManagerOpts));
80+
this.userCleanTask = getCleanerTask(this);
7981
});
8082

8183
this.on('plugin:start:http', (server) => {
@@ -96,6 +98,7 @@ module.exports = class Users extends Microfleet {
9698
this.on(`plugin:close:${this.redisType}`, () => {
9799
this.dlock = null;
98100
this.tokenManager = null;
101+
stopCleanerTask(this.userCleanTask);
99102
});
100103

101104
// add migration connector

src/utils/inactiveUsers.js

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
const Promise = require('bluebird');
2+
const { LockAcquisitionError } = require('ioredis-lock');
3+
4+
const { getInternalData } = require('./userData');
5+
const removeUser = require('./removeUser');
6+
7+
const { USERS_ACTIVATE,
8+
USERS_ACTIVATE_AUDIENCE,
9+
} = require('../constants');
10+
11+
const lockKey = `${USERS_ACTIVATE}:data-lock`;
12+
13+
// Get user ids having score lt current time - interval
14+
async function getInactiveUsers(redis, interval) {
15+
const start = '-inf';
16+
const end = Date.now() - (interval * 1000);
17+
return redis.zrangebyscore(USERS_ACTIVATE, start, end);
18+
}
19+
20+
async function getAudience(redis, userId) {
21+
const audience = await redis.hget(USERS_ACTIVATE_AUDIENCE, userId);
22+
return audience;
23+
}
24+
25+
async function deleteUser(userId, audience) {
26+
const { redis } = this;
27+
const context = { redis, audience };
28+
29+
const internal = await Promise
30+
.bind(context, userId)
31+
.then(getInternalData);
32+
33+
const transaction = redis.multi();
34+
35+
removeUser(transaction, internal, audience);
36+
return transaction.exec();
37+
}
38+
39+
/**
40+
* Add user id to inacive users list
41+
* @param {ioredis} redis
42+
* @param {userId} userId
43+
* @param {audience[]} audience
44+
*/
45+
function addInactiveUser(redis, userId, audience) {
46+
const created = Date.now();
47+
redis.zadd(USERS_ACTIVATE, userId, created);
48+
redis.hset(USERS_ACTIVATE_AUDIENCE, userId, JSON.stringify(audience));
49+
}
50+
51+
/**
52+
* Remove user id from inactive users list
53+
* @param {ioredis} redis
54+
* @param {userId} userId
55+
*/
56+
function removeInactiveUser(redis, userId) {
57+
redis.zrem(USERS_ACTIVATE, userId);
58+
redis.hdel(USERS_ACTIVATE_AUDIENCE, userId);
59+
}
60+
61+
/**
62+
* Clean all users, who did't pass activation
63+
* Call in `service` context
64+
*/
65+
async function cleanInactiveUsers() {
66+
const { redis } = this;
67+
const { deleteInactiveAccounts } = this.config;
68+
69+
const lock = await this.dlock.once(lockKey);
70+
const inactiveAccounts = await getInactiveUsers(redis, deleteInactiveAccounts);
71+
72+
inactiveAccounts.forEach(async (acc) => {
73+
const accAudiences = getAudience(redis, acc);
74+
await deleteUser.bind(this, acc, accAudiences);
75+
await removeInactiveUser(redis, acc);
76+
});
77+
78+
lock.release().reflect();
79+
}
80+
81+
/**
82+
* Returns inactive users cleanup task
83+
* @param {Mfleet} service
84+
* @returns {NodeJS.Timeout}
85+
*/
86+
function getCleanerTask(service) {
87+
const { deleteInactiveAccountsInterval: interval } = service.config;
88+
89+
return setInterval(() => {
90+
cleanInactiveUsers.call(service)
91+
.catch(LockAcquisitionError, () => {})
92+
.catch((error) => {
93+
service.log.error('Inactive User Clean task error', error);
94+
});
95+
}, interval * 1000);
96+
}
97+
98+
/**
99+
* Stops currently running task
100+
* @param {NodeJS.Timeout} task
101+
*/
102+
function stopCleanerTask(task) {
103+
clearInterval(task);
104+
}
105+
106+
107+
module.exports = {
108+
addInactiveUser,
109+
removeInactiveUser,
110+
cleanInactiveUsers,
111+
getCleanerTask,
112+
stopCleanerTask,
113+
};

src/utils/removeUser.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
const get = require('../utils/get-value');
2+
const key = require('../utils/key');
3+
4+
const { USERS_ALIAS_FIELD,
5+
USERS_ID_FIELD,
6+
USERS_ALIAS_TO_ID,
7+
USERS_USERNAME_TO_ID,
8+
USERS_USERNAME_FIELD,
9+
USERS_PUBLIC_INDEX,
10+
USERS_INDEX,
11+
USERS_DATA,
12+
USERS_METADATA,
13+
USERS_TOKENS,
14+
SSO_PROVIDERS,
15+
THROTTLE_PREFIX,
16+
USERS_ACTION_ACTIVATE,
17+
USERS_ACTION_REGISTER,
18+
USERS_ACTION_PASSWORD,
19+
USERS_ACTION_RESET,
20+
USERS_SSO_TO_ID } = require('../constants');
21+
22+
/**
23+
* Assigns common user remove logic into passed transaction
24+
* @param {ioredis} transaction
25+
* @param {ms-user data} internal
26+
* @param {ms-user audience/[]} _audiences
27+
*/
28+
function removeUser(transaction, internal, _audiences) {
29+
const audiences = Array.isArray(_audiences) ? _audiences : [_audiences];
30+
31+
const alias = internal[USERS_ALIAS_FIELD];
32+
const userId = internal[USERS_ID_FIELD];
33+
const resolvedUsername = internal[USERS_USERNAME_FIELD];
34+
35+
if (alias) {
36+
transaction.hdel(USERS_ALIAS_TO_ID, alias.toLowerCase(), alias);
37+
}
38+
39+
transaction.hdel(USERS_USERNAME_TO_ID, resolvedUsername);
40+
41+
// remove refs to SSO account
42+
for (const provider of SSO_PROVIDERS) {
43+
const uid = get(internal, `${provider}.uid`, { default: false });
44+
45+
if (uid) {
46+
transaction.hdel(USERS_SSO_TO_ID, uid);
47+
}
48+
}
49+
50+
// clean indices
51+
transaction.srem(USERS_PUBLIC_INDEX, userId);
52+
transaction.srem(USERS_INDEX, userId);
53+
54+
// remove metadata & internal data
55+
transaction.del(key(userId, USERS_DATA));
56+
57+
audiences.forEach((audience) => {
58+
transaction.del(key(userId, USERS_METADATA, audience));
59+
});
60+
61+
// remove auth tokens
62+
transaction.del(key(userId, USERS_TOKENS));
63+
64+
// remove throttling on actions
65+
transaction.del(key(THROTTLE_PREFIX, USERS_ACTION_ACTIVATE, userId));
66+
transaction.del(key(THROTTLE_PREFIX, USERS_ACTION_PASSWORD, userId));
67+
transaction.del(key(THROTTLE_PREFIX, USERS_ACTION_REGISTER, userId));
68+
transaction.del(key(THROTTLE_PREFIX, USERS_ACTION_RESET, userId));
69+
70+
return transaction;
71+
}
72+
73+
module.exports = removeUser;

0 commit comments

Comments
 (0)