@kne/fastify-account 1.0.0-alpha.17 → 1.0.0-alpha.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ module.exports = ({ DataTypes }) => {
2
+ return {
3
+ model: {
4
+ userId: {
5
+ type: DataTypes.UUID,
6
+ allowNull: false
7
+ },
8
+ tenantId: {
9
+ type: DataTypes.UUID
10
+ },
11
+ type: {
12
+ type: DataTypes.STRING,
13
+ comment: 'user,tenant,admin'
14
+ },
15
+ applicationId: {
16
+ type: DataTypes.UUID
17
+ },
18
+ action: {
19
+ type: DataTypes.STRING
20
+ },
21
+ summary: {
22
+ type: DataTypes.TEXT
23
+ }
24
+ },
25
+ options: {
26
+ indexes: [
27
+ {
28
+ fields: ['user_id', 'type']
29
+ },
30
+ {
31
+ fields: ['tenant_id', 'type']
32
+ },
33
+ {
34
+ fields: ['action', 'type']
35
+ }
36
+ ]
37
+ }
38
+ };
39
+ };
@@ -14,7 +14,7 @@ function userNameIsEmail(username) {
14
14
 
15
15
  module.exports = fp(async (fastify, options) => {
16
16
  const { models, services } = fastify.account;
17
- const login = async ({ username, password, ip }) => {
17
+ const login = async ({ username, password, ip, appName }) => {
18
18
  const isEmail = userNameIsEmail(username);
19
19
  const user = await models.user.findOne({
20
20
  where: Object.assign(
@@ -40,9 +40,13 @@ module.exports = fp(async (fastify, options) => {
40
40
 
41
41
  await passwordAuthentication({ accountId: user.userAccountId, password });
42
42
 
43
+ const application = appName && (await services.application.getApplicationByCode({ code: appName }));
44
+
43
45
  await models.loginLog.create({
44
46
  userId: user.uuid,
45
- ip
47
+ ip,
48
+ currentTenantId: user.currentTenantId,
49
+ applicationId: application?.id
46
50
  });
47
51
 
48
52
  return {
@@ -51,22 +55,25 @@ module.exports = fp(async (fastify, options) => {
51
55
  };
52
56
  };
53
57
 
58
+ const resetPassword = async ({ password, userId }) => {
59
+ const userInfo = await services.user.getUserInstance({ id: userId });
60
+ const account = await models.userAccount.create(
61
+ Object.assign({}, await passwordEncryption(password), {
62
+ belongToUserId: userInfo.uuid
63
+ })
64
+ );
65
+
66
+ await userInfo.update({ userAccountId: account.uuid });
67
+ };
68
+
69
+ const resetPasswordByToken = async ({ password, token }) => {
70
+ const { name } = await verificationJWTCodeValidate({ token });
71
+ const user = await services.user.getUserInstanceByName({ name, status: [0, 1] });
72
+ await resetPassword({ password, userId: user.uuid });
73
+ };
74
+
54
75
  const modifyPassword = async ({ email, phone, oldPwd, newPwd }) => {
55
- const user = await models.user.findOne({
56
- where: Object.assign(
57
- {},
58
- email
59
- ? {
60
- email
61
- }
62
- : {
63
- phone
64
- },
65
- {
66
- status: 1
67
- }
68
- )
69
- });
76
+ const user = await services.user.getUserInstanceByName({ name: email || phone, status: 1 });
70
77
  if (!user) {
71
78
  throw new Error('新用户密码只能初始化一次');
72
79
  }
@@ -111,30 +118,9 @@ module.exports = fp(async (fastify, options) => {
111
118
  return hash.digest('hex');
112
119
  };
113
120
 
114
- const resetPassword = async ({ password, token }) => {
115
- const { name } = await verificationJWTCodeValidate({ token });
116
-
117
- const isEmail = userNameIsEmail(name);
118
-
119
- const userInfo = await models.user.findOne({
120
- where: isEmail ? { email: name } : { phone: name }
121
- });
122
- if (!userInfo) {
123
- throw new Error('用户不存在');
124
- }
125
- const account = await models.userAccount.create(
126
- Object.assign({}, await passwordEncryption(password), {
127
- belongToUserId: userInfo.uuid
128
- })
129
- );
130
-
131
- await userInfo.update({ userAccountId: account.uuid });
132
- };
133
-
134
121
  const register = async ({ avatar, nickname, gender, birthday, description, phone, email, code, password, status, invitationCode }) => {
135
122
  const type = phone ? 0 : 1;
136
-
137
- if (!(await verificationCodeValidate({ name: type === 0 ? phone : email, type, code }))) {
123
+ if (!(await verificationCodeValidate({ name: type === 0 ? phone : email, type: 0, code }))) {
138
124
  throw new Error('验证码不正确或者已经过期');
139
125
  }
140
126
 
@@ -173,9 +159,12 @@ module.exports = fp(async (fastify, options) => {
173
159
  return code;
174
160
  };
175
161
 
176
- const sendVerificationCode = async ({ name, type, messageType }) => {
162
+ const sendVerificationCode = async ({ name, type }) => {
163
+ // messageType: 0:短信验证码,1:邮件验证码 type: 0:注册,2:登录,4:验证租户管理员,5:忘记密码
177
164
  const code = await generateVerificationCode({ name, type });
165
+ const isEmail = userNameIsEmail(name);
178
166
  // 这里写发送逻辑
167
+ await options.sendMessage({ name, type, messageType: isEmail ? 1 : 0, props: { code } });
179
168
  return code;
180
169
  };
181
170
 
@@ -200,10 +189,12 @@ module.exports = fp(async (fastify, options) => {
200
189
  return isPass;
201
190
  };
202
191
 
203
- const sendJWTVerificationCode = async ({ name, type, messageType }) => {
192
+ const sendJWTVerificationCode = async ({ name, type }) => {
204
193
  const code = await generateVerificationCode({ name, type });
205
194
  const token = fastify.jwt.sign({ name, type, code });
195
+ const isEmail = userNameIsEmail(name);
206
196
  // 这里写发送逻辑
197
+ await options.sendMessage({ name, type, messageType: isEmail ? 1 : 0, props: { token } });
207
198
  return token;
208
199
  };
209
200
 
@@ -227,6 +218,7 @@ module.exports = fp(async (fastify, options) => {
227
218
  verificationJWTCodeValidate,
228
219
  passwordEncryption,
229
220
  passwordAuthentication,
230
- resetPassword
221
+ resetPassword,
222
+ resetPasswordByToken
231
223
  };
232
224
  });
@@ -116,7 +116,7 @@ module.exports = fp(async (fastify, options) => {
116
116
  }
117
117
  };
118
118
 
119
- const getApplicationList = async ({ tenantId }) => {
119
+ const getApplicationList = async ({ tenantId, appName }) => {
120
120
  const query = {};
121
121
  if (tenantId) {
122
122
  const tenant = await services.tenant.getTenant({ id: tenantId });
@@ -130,6 +130,9 @@ module.exports = fp(async (fastify, options) => {
130
130
  [fastify.sequelize.Sequelize.Op.in]: tenantApplications.map(({ applicationId }) => applicationId)
131
131
  };
132
132
  }
133
+ if (appName) {
134
+ query['code'] = appName;
135
+ }
133
136
  const list = await models.application.findAll({
134
137
  where: query
135
138
  });
@@ -68,52 +68,75 @@ module.exports = fp(async (fastify, options) => {
68
68
  });
69
69
  };
70
70
 
71
+ const importPermissionsToApplication = async ({ applicationId, permissions }) => {
72
+ const permissionsPidMapping = groupBy(permissions, 'pid');
73
+ const pidMapping = {};
74
+ for (let pid of await Promise.all(Object.keys(permissionsPidMapping).sort())) {
75
+ const targetPid = pid === '0' ? 0 : pidMapping[pid];
76
+ await Promise.all(
77
+ permissionsPidMapping[pid].map(async item => {
78
+ const originPermission = await models.permission.findOne({
79
+ where: {
80
+ pid: targetPid,
81
+ code: item.code,
82
+ applicationId
83
+ }
84
+ });
85
+ if (originPermission) {
86
+ pidMapping[item.id] = originPermission.id;
87
+ return;
88
+ }
89
+ const newPermission = await services.permission.addPermission({
90
+ applicationId,
91
+ pid: targetPid,
92
+ code: item.code,
93
+ name: item.name,
94
+ type: item.type,
95
+ isModule: item.isModule,
96
+ isMust: item.isMust,
97
+ description: item.description
98
+ });
99
+ pidMapping[item.id] = newPermission.id;
100
+ })
101
+ );
102
+ }
103
+ };
104
+
71
105
  const parsePermissionListJSON = async ({ file }) => {
72
106
  const data = JSON.parse(await file.toBuffer());
73
107
  await Promise.all(
74
108
  data.map(async application => {
75
109
  const { permissions, ...other } = application;
76
- const app = await services.application.getApplicationByCode({ code: application.code });
77
-
110
+ let app = await services.application.getApplicationByCode({ code: application.code });
78
111
  if (!app) {
79
- const newApplication = await services.application.addApplication(other);
80
- const permissionsPidMapping = groupBy(permissions, 'pid');
81
- const addPermissions = async (pid, applicationId) =>
82
- await Promise.all(
83
- (get(permissionsPidMapping, pid) || []).map(async ({ id, ...permissionProps }) => {
84
- const permission = await services.permission.addPermission(Object.assign({}, permissionProps, { applicationId, pid }));
85
- await addPermissions(permission.id, applicationId);
86
- })
87
- );
88
- await addPermissions(0, newApplication.uuid);
89
- } else {
90
- const permissionsPidMapping = groupBy(permissions, 'pid');
91
- const addPermissions = async (pid, applicationId, importPid) => {
92
- await Promise.all(
93
- (get(permissionsPidMapping, importPid || pid) || []).map(async ({ id, ...permissionProps }) => {
94
- const current = await models.permission.findOne({ where: { code: permissionProps.code, pid } });
95
- if (current) {
96
- await addPermissions(current.id, applicationId, id);
97
- } else {
98
- const permission = await services.permission.addPermission(Object.assign({}, permissionProps, { applicationId, pid }));
99
- await addPermissions(permission.id, applicationId);
100
- }
101
- })
102
- );
103
- };
104
- await addPermissions(0, app.uuid);
112
+ app = await services.application.addApplication(other);
105
113
  }
114
+ await services.permission.importPermissionsToApplication({ applicationId: app.uuid, permissions });
106
115
  return app;
107
116
  })
108
117
  );
109
118
  return data;
110
119
  };
111
120
 
112
- const exportPermissionList = async ({ applicationIds, tenantId }) => {
121
+ const copyPermissions = async ({ applicationId, originApplicationId }) => {
122
+ if (applicationId === originApplicationId) {
123
+ throw new Error('复制对象不能和自己相同');
124
+ }
125
+ await services.application.getApplication({ id: originApplicationId });
126
+ await services.application.getApplication({ id: applicationId });
127
+ const permissions = await models.permission.findAll({
128
+ where: { applicationId: originApplicationId }
129
+ });
130
+ await services.permission.importPermissionsToApplication({ applicationId, permissions });
131
+ };
132
+
133
+ const exportPermissionList = async ({ applicationIds }) => {
113
134
  return await Promise.all(
114
135
  (applicationIds || []).map(async applicationId => {
115
136
  let application = await services.application.getApplication({ id: applicationId });
116
- application.permissions = await services.permission.getPermissionList({ applicationId, tenantId });
137
+ application.permissions = await models.permission.findAll({
138
+ where: { applicationId }
139
+ });
117
140
  return application;
118
141
  })
119
142
  );
@@ -274,13 +297,17 @@ module.exports = fp(async (fastify, options) => {
274
297
  }
275
298
  };
276
299
 
277
- const saveRolePermissionList = async ({ roleId, applications, permissions }) => {
300
+ const saveRolePermissionList = async ({ roleId, tenantId, applications, permissions }) => {
278
301
  const role = await models.tenantRole.findByPk(roleId);
279
302
  if (!role) {
280
303
  throw new Error('角色不存在');
281
304
  }
282
305
 
283
- const tenantId = role.tenantId;
306
+ if (tenantId && role.tenantId !== tenantId) {
307
+ throw new Error('数据已过期,请刷新页面后重试');
308
+ }
309
+
310
+ tenantId = role.tenantId;
284
311
 
285
312
  await services.tenant.getTenant({ id: tenantId });
286
313
 
@@ -399,11 +426,16 @@ module.exports = fp(async (fastify, options) => {
399
426
  return { applications, permissions };
400
427
  };
401
428
 
402
- const getRolePermissionList = async ({ roleId }) => {
429
+ const getRolePermissionList = async ({ roleId, tenantId }) => {
403
430
  const role = await models.tenantRole.findByPk(roleId);
404
431
  if (!role) {
405
432
  throw new Error('角色不存在');
406
433
  }
434
+
435
+ if (tenantId && role.tenantId !== tenantId) {
436
+ throw new Error('数据已过期,请刷新页面后重试');
437
+ }
438
+
407
439
  const applications = await models.tenantRoleApplication.findAll({
408
440
  where: { roleId: role.id, tenantId: role.tenantId }
409
441
  });
@@ -423,6 +455,9 @@ module.exports = fp(async (fastify, options) => {
423
455
  saveTenantPermissionList,
424
456
  saveRolePermissionList,
425
457
  getTenantPermissionList,
426
- getRolePermissionList
458
+ getRolePermissionList,
459
+ parsePermissionListJSON,
460
+ copyPermissions,
461
+ importPermissionsToApplication
427
462
  };
428
463
  });
@@ -0,0 +1,19 @@
1
+ const fp = require('fastify-plugin');
2
+ module.exports = fp(async (fastify, options) => {
3
+ const { models, services } = fastify.account;
4
+
5
+ const addRequestLog = async ({ userInfo, type, tenantId, appName, action, summary }) => {
6
+ const application = appName && (await services.application.getApplicationByCode({ code: appName }));
7
+ await models.requestLog.create({
8
+ userId: userInfo.id,
9
+ tenantId,
10
+ applicationId: application?.id,
11
+ type,
12
+ action,
13
+ summary
14
+ });
15
+ return {};
16
+ };
17
+
18
+ services.requestLog = { addRequestLog };
19
+ });
@@ -1,7 +1,7 @@
1
1
  const fp = require('fastify-plugin');
2
2
  module.exports = fp(async (fastify, options) => {
3
3
  const { models, services } = fastify.account;
4
-
4
+ const { Op } = fastify.sequelize.Sequelize;
5
5
  const getTenantOrgInstance = async ({ id }) => {
6
6
  const tenantOrg = await models.tenantOrg.findByPk(id, {
7
7
  where: {
@@ -37,21 +37,30 @@ module.exports = fp(async (fastify, options) => {
37
37
  });
38
38
  };
39
39
 
40
- const saveTenantOrg = async ({ id, ...otherInfo }) => {
40
+ const saveTenantOrg = async ({ id, tenantId, ...otherInfo }) => {
41
41
  const tenantOrg = await getTenantOrgInstance({ id });
42
+ if (tenantId && tenantOrg.tenantId !== tenantId) {
43
+ throw new Error('数据已过期,请刷新页面后重试');
44
+ }
42
45
  if (
43
46
  await models.tenantOrg.count({
44
47
  where: {
45
48
  name: otherInfo.name,
46
49
  pid: otherInfo.pid,
47
- tenantId: otherInfo.tenantId
50
+ [Op.not]: {
51
+ id
52
+ }
48
53
  }
49
54
  })
50
55
  ) {
51
56
  throw new Error('组织名称在同一父组织下有重复');
52
57
  }
53
58
 
54
- ['name', 'enName', 'tenantId', 'pid'].forEach(name => {
59
+ if (otherInfo.pid === tenantOrg.id) {
60
+ throw new Error('不能用自己作为自己的父节点');
61
+ }
62
+
63
+ ['name', 'enName', 'pid'].forEach(name => {
55
64
  if (otherInfo[name]) {
56
65
  tenantOrg[name] = otherInfo[name];
57
66
  }
@@ -63,11 +72,15 @@ module.exports = fp(async (fastify, options) => {
63
72
  const deleteTenantOrg = async ({ id, tenantId }) => {
64
73
  const tenantOrg = await getTenantOrgInstance({ id });
65
74
 
66
- const { rows } = await models.tenantOrg.findAndCountAll({
67
- where: { tenantId, pid: id }
75
+ if (tenantId && tenantOrg.tenantId !== tenantId) {
76
+ throw new Error('数据已过期,请刷新页面后重试');
77
+ }
78
+
79
+ const count = await models.tenantOrg.count({
80
+ where: { tenantId: tenantOrg.tenantId, pid: id }
68
81
  });
69
82
 
70
- if (rows?.length) {
83
+ if (count > 0) {
71
84
  throw new Error('组织下有用户或子组织无法删除');
72
85
  }
73
86
 
@@ -42,9 +42,13 @@ module.exports = fp(async (fastify, options) => {
42
42
  });
43
43
  };
44
44
 
45
- const saveTenantRole = async ({ id, ...otherInfo }) => {
45
+ const saveTenantRole = async ({ id, tenantId, ...otherInfo }) => {
46
46
  const tenantRole = await getTenantRoleInstance({ id });
47
47
 
48
+ if (tenantId && tenantRole.tenantId !== tenantId) {
49
+ throw new Error('数据已过期,请刷新页面后重试');
50
+ }
51
+
48
52
  ['name', 'description'].forEach(name => {
49
53
  if (otherInfo[name]) {
50
54
  tenantRole[name] = otherInfo[name];
@@ -54,9 +58,13 @@ module.exports = fp(async (fastify, options) => {
54
58
  await tenantRole.save();
55
59
  };
56
60
 
57
- const removeTenantRole = async ({ id }) => {
61
+ const removeTenantRole = async ({ id, tenantId }) => {
58
62
  const tenantRole = await getTenantRoleInstance({ id });
59
63
 
64
+ if (tenantId && tenantRole.tenantId !== tenantId) {
65
+ throw new Error('数据已过期,请刷新页面后重试');
66
+ }
67
+
60
68
  await services.tenantUser.checkTenantRoleUsed({ tenantRoleId: tenantRole.id });
61
69
 
62
70
  if (tenantRole.type === 1) {
@@ -92,7 +92,8 @@ module.exports = fp(async (fastify, options) => {
92
92
  const user = await services.user.getUser(authenticatePayload);
93
93
  const tenantUserList = await models.tenantUser.findAll({
94
94
  where: {
95
- userId: user.id
95
+ userId: user.id,
96
+ status: 0
96
97
  }
97
98
  });
98
99
 
@@ -137,12 +138,19 @@ module.exports = fp(async (fastify, options) => {
137
138
  return currentTenantUser;
138
139
  };
139
140
 
140
- const getTenantUserByUserId = async user => {
141
+ const getTenantUserByUserId = async ({ userInfo: user, appName }) => {
141
142
  if (!user.currentTenantId) {
142
143
  throw new Unauthorized('没有找到当前绑定租户');
143
144
  }
145
+
144
146
  const tenant = await services.tenant.getTenant({ id: user.currentTenantId });
145
147
 
148
+ const tenantApplications = await services.application.getApplicationList({ tenantId: tenant.id, appName });
149
+
150
+ if (appName && !tenantApplications.some(item => item.code === appName)) {
151
+ throw Unauthorized('当前租户没有开通该应用权限');
152
+ }
153
+
146
154
  const tenantUser = await models.tenantUser.findOne({
147
155
  attributes: ['uuid', 'avatar', 'name', 'description', 'phone', 'email'],
148
156
  include: [
@@ -181,11 +189,15 @@ module.exports = fp(async (fastify, options) => {
181
189
  const tenantRoleIds = tenantUser.tenantRoles.map(({ id }) => id);
182
190
  tenantRoleIds.push(defaultTenant.id);
183
191
 
184
- const { userPermissionList } = await getTenantUserPermissionList({ tenantRoleIds });
192
+ const { userPermissionList, applications: roleApplications } = await getTenantUserPermissionList({ tenantRoleIds });
185
193
  if (!tenantUser) {
186
194
  throw new Error('当前租户用户不存在或者已经被关闭');
187
195
  }
188
196
 
197
+ if (appName && !roleApplications.some(item => item.code === appName)) {
198
+ throw Unauthorized('当前用户没有开通该应用权限');
199
+ }
200
+
189
201
  const outputTenantUser = Object.assign({}, tenantUser.get({ plain: true }), { id: tenantUser.uuid });
190
202
  outputTenantUser.tenantOrgs = outputTenantUser?.tenantOrgs.map(({ id, name }) => ({ id, name }));
191
203
  outputTenantUser.tenantRoles = outputTenantUser?.tenantRoles.map(({ id, name }) => ({ id, name }));
@@ -194,7 +206,7 @@ module.exports = fp(async (fastify, options) => {
194
206
  tenantUser: Object.assign({}, outputTenantUser, {
195
207
  permissions: userPermissionList
196
208
  }),
197
- user
209
+ userInfo: user
198
210
  };
199
211
  };
200
212
 
@@ -2,6 +2,12 @@ const fp = require('fastify-plugin');
2
2
  const { Unauthorized } = require('http-errors');
3
3
  const get = require('lodash/get');
4
4
  const pick = require('lodash/pick');
5
+ const isNil = require('lodash/isNil');
6
+
7
+ function userNameIsEmail(username) {
8
+ return /^([a-zA-Z0-9_.-])+@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/.test(username);
9
+ }
10
+
5
11
  module.exports = fp(async (fastify, options) => {
6
12
  const { models, services } = fastify.account;
7
13
 
@@ -19,6 +25,27 @@ module.exports = fp(async (fastify, options) => {
19
25
  return user;
20
26
  };
21
27
 
28
+ const getUserInstanceByName = async ({ name, status }) => {
29
+ const isEmail = userNameIsEmail(name);
30
+ const query = {};
31
+ if (!isNil(status)) {
32
+ query['status'] = Array.isArray(status)
33
+ ? {
34
+ [fastify.sequelize.Sequelize.Op.or]: status
35
+ }
36
+ : status;
37
+ }
38
+ const user = await models.user.findOne({
39
+ where: Object.assign({}, isEmail ? { email: name } : { phone: name }, query)
40
+ });
41
+
42
+ if (!user) {
43
+ throw new Error('用户不存在');
44
+ }
45
+
46
+ return user;
47
+ };
48
+
22
49
  const getUser = async authenticatePayload => {
23
50
  if (!(authenticatePayload && authenticatePayload.id)) {
24
51
  throw new Unauthorized();
@@ -127,7 +154,12 @@ module.exports = fp(async (fastify, options) => {
127
154
  });
128
155
  return {
129
156
  pageData: rows.map(item => {
130
- return Object.assign({}, item.get({ pain: true }), { id: item.uuid });
157
+ return Object.assign({}, item.get({ pain: true }), {
158
+ id: item.uuid,
159
+ tenants: item.tenants.map(({ uuid, name }) => {
160
+ return { id: uuid, name };
161
+ })
162
+ });
131
163
  }),
132
164
  totalCount: count
133
165
  };
@@ -136,6 +168,7 @@ module.exports = fp(async (fastify, options) => {
136
168
  services.user = {
137
169
  getUser,
138
170
  getUserInstance,
171
+ getUserInstanceByName,
139
172
  saveUser,
140
173
  accountIsExists,
141
174
  addUser,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kne/fastify-account",
3
- "version": "1.0.0-alpha.17",
3
+ "version": "1.0.0-alpha.19",
4
4
  "description": "fastify的用户管理账号等实现",
5
5
  "main": "index.js",
6
6
  "scripts": {