@kne/fastify-account 1.0.0-alpha.16 → 1.0.0-alpha.18

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,27 @@
1
+ module.exports = ({ DataTypes }) => {
2
+ return {
3
+ model: {
4
+ userId: {
5
+ type: DataTypes.UUID, allowNull: false
6
+ }, tenantId: {
7
+ type: DataTypes.UUID
8
+ }, type: {
9
+ type: DataTypes.STRING, comment: 'user,tenant,admin'
10
+ }, applicationId: {
11
+ type: DataTypes.UUID
12
+ }, action: {
13
+ type: DataTypes.TEXT
14
+ }, summary: {
15
+ type: DataTypes.TEXT
16
+ }
17
+ }, options: {
18
+ indexes: [{
19
+ fields: ['user_id', 'type']
20
+ }, {
21
+ fields: ['tenant_id', 'type']
22
+ }, {
23
+ fields: ['action', 'type']
24
+ }]
25
+ }
26
+ };
27
+ };
@@ -7,7 +7,8 @@ module.exports = ({ DataTypes }) => {
7
7
  },
8
8
  type: {
9
9
  type: DataTypes.INTEGER,
10
- allowNull: false //0:手机注册,1:邮箱注册,2:手机登录,3:邮箱登录,4:验证租户管理员
10
+ allowNull: false,
11
+ comment: '0:注册,2:登录,4:验证租户管理员,5:忘记密码'
11
12
  },
12
13
  code: {
13
14
  type: DataTypes.STRING,
@@ -15,7 +16,8 @@ module.exports = ({ DataTypes }) => {
15
16
  },
16
17
  status: {
17
18
  type: DataTypes.INTEGER,
18
- defaultValue: 0 //0:未验证,1:已验证,2:已过期
19
+ defaultValue: 0,
20
+ comment: '0:未验证,1:已验证,2:已过期'
19
21
  }
20
22
  }
21
23
  };
@@ -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,6 +55,37 @@ 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
+
75
+ const modifyPassword = async ({ email, phone, oldPwd, newPwd }) => {
76
+ const user = await services.user.getUserInstanceByName({ name: email || phone, status: 1 });
77
+ if (!user) {
78
+ throw new Error('新用户密码只能初始化一次');
79
+ }
80
+ if (oldPwd === newPwd) {
81
+ throw new Error('重置密码不能和初始化密码相同');
82
+ }
83
+ await passwordAuthentication({ accountId: user.userAccountId, password: oldPwd });
84
+ await resetPassword({ userId: user.uuid, password: newPwd });
85
+ user.status = 0;
86
+ await user.save();
87
+ };
88
+
54
89
  const passwordAuthentication = async ({ accountId, password }) => {
55
90
  const userAccount = await models.userAccount.findOne({
56
91
  where: {
@@ -83,39 +118,12 @@ module.exports = fp(async (fastify, options) => {
83
118
  return hash.digest('hex');
84
119
  };
85
120
 
86
- const resetPassword = async ({ userId, password }) => {
87
- const userInfo = await models.user.findOne({
88
- where: { uuid: userId }
89
- });
90
- if (!userInfo) {
91
- throw new Error('用户不存在');
92
- }
93
- const account = await models.userAccount.create(
94
- Object.assign({}, await passwordEncryption(password), {
95
- belongToUserId: userId
96
- })
97
- );
98
-
99
- await userInfo.update({ userAccountId: account.uuid });
100
- };
101
-
102
121
  const register = async ({ avatar, nickname, gender, birthday, description, phone, email, code, password, status, invitationCode }) => {
103
122
  const type = phone ? 0 : 1;
104
- const verificationCode = await models.verificationCode.findOne({
105
- where: {
106
- name: type === 0 ? phone : email,
107
- type,
108
- code,
109
- status: 1
110
- }
111
- });
112
- if (!verificationCode) {
123
+ if (!(await verificationCodeValidate({ name: type === 0 ? phone : email, type: 0, code }))) {
113
124
  throw new Error('验证码不正确或者已经过期');
114
125
  }
115
126
 
116
- verificationCode.status = 2;
117
- await verificationCode.save();
118
-
119
127
  return await services.user.addUser({
120
128
  avatar,
121
129
  nickname,
@@ -129,30 +137,34 @@ module.exports = fp(async (fastify, options) => {
129
137
  });
130
138
  };
131
139
 
132
- const sendEmailCode = async ({ email }) => {
140
+ const generateVerificationCode = async ({ name, type }) => {
133
141
  const code = generateRandom6DigitNumber();
134
-
135
- // 这里写发送逻辑
136
-
137
142
  await models.verificationCode.update(
138
143
  {
139
144
  status: 2
140
145
  },
141
146
  {
142
147
  where: {
143
- name: email,
144
- type: 1,
148
+ name,
149
+ type,
145
150
  status: 0
146
151
  }
147
152
  }
148
153
  );
149
-
150
154
  await models.verificationCode.create({
151
- name: email,
152
- type: 1,
155
+ name,
156
+ type,
153
157
  code
154
158
  });
159
+ return code;
160
+ };
155
161
 
162
+ const sendVerificationCode = async ({ name, type }) => {
163
+ // messageType: 0:短信验证码,1:邮件验证码 type: 0:注册,2:登录,4:验证租户管理员,5:忘记密码
164
+ const code = await generateVerificationCode({ name, type });
165
+ const isEmail = userNameIsEmail(name);
166
+ // 这里写发送逻辑
167
+ await options.sendMessage({ name, type, messageType: isEmail ? 1 : 0, props: { code } });
156
168
  return code;
157
169
  };
158
170
 
@@ -177,42 +189,36 @@ module.exports = fp(async (fastify, options) => {
177
189
  return isPass;
178
190
  };
179
191
 
180
- const sendSMSCode = async ({ phone }) => {
181
- const code = generateRandom6DigitNumber();
182
-
192
+ const sendJWTVerificationCode = async ({ name, type }) => {
193
+ const code = await generateVerificationCode({ name, type });
194
+ const token = fastify.jwt.sign({ name, type, code });
195
+ const isEmail = userNameIsEmail(name);
183
196
  // 这里写发送逻辑
197
+ await options.sendMessage({ name, type, messageType: isEmail ? 1 : 0, props: { token } });
198
+ return token;
199
+ };
184
200
 
185
- await models.verificationCode.update(
186
- {
187
- status: 2
188
- },
189
- {
190
- where: {
191
- name: phone,
192
- type: 0,
193
- status: 0
194
- }
195
- }
196
- );
197
-
198
- await models.verificationCode.create({
199
- name: phone,
200
- type: 0,
201
- code
202
- });
203
-
204
- return code;
201
+ const verificationJWTCodeValidate = async ({ token }) => {
202
+ const { iat, name, type, code } = fastify.jwt.decode(token);
203
+ if (!(await verificationCodeValidate({ name, type, code }))) {
204
+ throw new Error('验证码不正确或者已经过期');
205
+ }
206
+ return { name, type, code };
205
207
  };
206
208
 
207
209
  services.account = {
208
210
  md5,
209
211
  login,
212
+ modifyPassword,
210
213
  register,
211
- sendEmailCode,
212
- sendSMSCode,
214
+ generateVerificationCode,
215
+ sendVerificationCode,
213
216
  verificationCodeValidate,
217
+ sendJWTVerificationCode,
218
+ verificationJWTCodeValidate,
214
219
  passwordEncryption,
215
220
  passwordAuthentication,
216
- resetPassword
221
+ resetPassword,
222
+ resetPasswordByToken
217
223
  };
218
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
  });
@@ -81,7 +81,12 @@ module.exports = fp(async (fastify, options) => {
81
81
  const addPermissions = async (pid, applicationId) =>
82
82
  await Promise.all(
83
83
  (get(permissionsPidMapping, pid) || []).map(async ({ id, ...permissionProps }) => {
84
- const permission = await services.permission.addPermission(Object.assign({}, permissionProps, { applicationId, pid }));
84
+ const permission = await services.permission.addPermission(
85
+ Object.assign({}, permissionProps, {
86
+ applicationId,
87
+ pid
88
+ })
89
+ );
85
90
  await addPermissions(permission.id, applicationId);
86
91
  })
87
92
  );
@@ -95,7 +100,12 @@ module.exports = fp(async (fastify, options) => {
95
100
  if (current) {
96
101
  await addPermissions(current.id, applicationId, id);
97
102
  } else {
98
- const permission = await services.permission.addPermission(Object.assign({}, permissionProps, { applicationId, pid }));
103
+ const permission = await services.permission.addPermission(
104
+ Object.assign({}, permissionProps, {
105
+ applicationId,
106
+ pid
107
+ })
108
+ );
99
109
  await addPermissions(permission.id, applicationId);
100
110
  }
101
111
  })
@@ -274,13 +284,17 @@ module.exports = fp(async (fastify, options) => {
274
284
  }
275
285
  };
276
286
 
277
- const saveRolePermissionList = async ({ roleId, applications, permissions }) => {
287
+ const saveRolePermissionList = async ({ roleId, tenantId, applications, permissions }) => {
278
288
  const role = await models.tenantRole.findByPk(roleId);
279
289
  if (!role) {
280
290
  throw new Error('角色不存在');
281
291
  }
282
292
 
283
- const tenantId = role.tenantId;
293
+ if (tenantId && role.tenantId !== tenantId) {
294
+ throw new Error('数据已过期,请刷新页面后重试');
295
+ }
296
+
297
+ tenantId = role.tenantId;
284
298
 
285
299
  await services.tenant.getTenant({ id: tenantId });
286
300
 
@@ -399,11 +413,16 @@ module.exports = fp(async (fastify, options) => {
399
413
  return { applications, permissions };
400
414
  };
401
415
 
402
- const getRolePermissionList = async ({ roleId }) => {
416
+ const getRolePermissionList = async ({ roleId, tenantId }) => {
403
417
  const role = await models.tenantRole.findByPk(roleId);
404
418
  if (!role) {
405
419
  throw new Error('角色不存在');
406
420
  }
421
+
422
+ if (tenantId && role.tenantId !== tenantId) {
423
+ throw new Error('数据已过期,请刷新页面后重试');
424
+ }
425
+
407
426
  const applications = await models.tenantRoleApplication.findAll({
408
427
  where: { roleId: role.id, tenantId: role.tenantId }
409
428
  });
@@ -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.16",
3
+ "version": "1.0.0-alpha.18",
4
4
  "description": "fastify的用户管理账号等实现",
5
5
  "main": "index.js",
6
6
  "scripts": {