index.js 17.6 KB
Newer Older
1 2 3 4 5 6
'use strict';
let uniID = require('uni-id')
const uniCaptcha = require('uni-captcha')
const createConfig = require('uni-config-center')
const uniIdConfig = createConfig({
	pluginId: 'uni-id'
7
}).config()
8
const db = uniCloud.database()
9
const dbCmd = db.command
10
const usersDB = db.collection('uni-id-users')
11
const deviceDB = db.collection('uni-id-device')
12
exports.main = async (event, context) => {
13 14 15
	console.log({
		context
	});
16
	//UNI_WYQ:这里的uniID换成新的,保证多人访问不会冲突
17 18 19
	uniID = uniID.createInstance({
		context
	})
20
	console.log('event : ' + JSON.stringify(event))
21 22
	/*
	1.event为客户端 uniCloud.callFunction填写的data的值,这里介绍一下其中的属性
23 24 25
		action:表示要执行的任务名称、比如:登录login、退出登录 logout等
		params:业务数据内容
		uniIdToken:系统自动传递的token,数据来源客户端的 uni.getStorageSync('uni_id_token')
26 27 28
	*/
	const {
		action,
29
		uniIdToken,
30
		inviteCode
31 32
	} = event;
	const deviceInfo = event.deviceInfo || {};
33 34
	let params = event.params || {},
		tokenExpired;
35 36
	/*
	2.在某些操作之前我们要对用户对身份进行校验(也就是要检查用户的token)再将得到的uid写入params.uid
37 38 39
		校验用到的方法是uniID.checkToken 详情:https://uniapp.dcloud.io/uniCloud/uni-id?id=checktoken
		讨论,我们假设一个这样的场景,代码如下。
		如:
40 41 42 43 44 45 46 47
		uniCloud.callFunction({
			name:"xxx",
			data:{
				"params":{
					uid:"通过某种方式获取来的别人的uid"
				}
			}
		})
48 49
		用户就这样轻易地伪造了他人的uid传递给服务端,有一句话叫:前端传来的数据都是不可信任的
		所以这里我们需要将uniID.checkToken返回的uid写入到params.uid
50
	*/
51 52 53
	let noCheckAction = ['register', 'checkToken', 'login', 'logout', 'sendSmsCode', 'getNeedCaptcha',
		'createCaptcha', 'verifyCaptcha', 'refreshCaptcha', 'inviteLogin', 'loginByWeixin',
		'loginByUniverify', 'loginByApple', 'loginBySms', 'resetPwdBySmsCode', 'registerAdmin'
54
	]
55 56 57 58 59 60 61 62 63 64 65 66
	if (!noCheckAction.includes(action)) {
		if (!uniIdToken) {
			return {
				code: 403,
				msg: '缺少token'
			}
		}
		let payload = await uniID.checkToken(uniIdToken)
		if (payload.code && payload.code > 0) {
			return payload
		}
		params.uid = payload.uid
67
		tokenExpired = payload.tokenExpired
68
	}
DCloud_JSON's avatar
DCloud_JSON 已提交
69

70
	//禁止前台用户传递角色
71
	if (action.slice(0, 7) == "loginBy") {
72 73 74 75 76 77 78
		if (params.role) {
			return {
				code: 403,
				msg: '禁止前台用户传递角色'
			}
		}
	}
79

80
	// 3.注册成功后触发。
81
	async function registerSuccess(res) {
82
		//用户接受邀请
83 84 85 86 87
		if (inviteCode) {
			await uniID.acceptInvite({
				inviteCode,
				uid
			});
88
		}
89
		//添加当前用户设备信息
90
		await addDeviceInfo(res)
91 92
	}
	//4.记录成功登录的日志方法
93
	const loginLog = async (res = {}) => {
94 95 96
		const now = Date.now()
		const uniIdLogCollection = db.collection('uni-id-log')
		let logData = {
97 98
			deviceId: context.DEVICEID,
			ip: context.CLIENTIP,
99
			type: res.type,
100 101
			ua: context.CLIENTUA,
			create_date: now
102
		};
103

104
		if (res.code === 0) {
105 106
			logData.user_id = res.uid
			logData.state = 1
107
			if (res.userInfo && res.userInfo.password) {
108 109 110
				delete res.userInfo.password
			}
			if (res.type == 'register') {
111
				await registerSuccess(res)
112 113
			} else {
				if (Object.keys(deviceInfo).length) {
114 115 116
					console.log(context.DEVICEID);
					//避免重复新增设备信息,先判断是否已存在
					let getDeviceRes = await deviceDB.where({
117
						"device_id": context.DEVICEID
118 119 120 121 122
					}).get()
					if (getDeviceRes.data.length == 0) {
						await addDeviceInfo(res)
					} else {
						await deviceDB.where({
123
							"device_id": context.DEVICEID,
124 125
						}).update({
							...deviceInfo,
126
							"tokenExpired": res.tokenExpired,
127 128 129 130 131 132 133 134 135
							"user_id": res.uid,
							"device_id": context.DEVICEID,
							"ua": context.CLIENTUA,
							"platform": context.PLATFORM,
							"create_date": Date.now(),
							"last_active_date": Date.now(),
							"last_active_ip": context.CLIENTIP
						})
					}
136 137
				}
			}
138
		} else {
139
			logData.state = 0
140 141 142 143
		}
		return await uniIdLogCollection.add(logData)
	}

144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
	async function addDeviceInfo({
		uid,
		tokenExpired
	}) {
		return await deviceDB.add({
			...deviceInfo,
			tokenExpired,
			"user_id": uid,
			"device_id": context.DEVICEID,
			"ua": context.CLIENTUA,
			"platform": context.PLATFORM,
			"create_date": Date.now(),
			"last_active_date": Date.now(),
			"last_active_ip": context.CLIENTIP
		})
	}

	//5.防止恶意破解登录,连续登录失败一定次数后,需要用户提供验证码
	const isNeedCaptcha = async () => {
		//当用户最近“2小时内(recordDate)”登录失败达到2次(recordSize)时。要求用户提交验证码
		const now = Date.now(),
			recordDate = 120 * 60 * 1000,
			recordSize = 2;
		const uniIdLogCollection = db.collection('uni-id-log')
		let recentRecord = await uniIdLogCollection.where({
			deviceId: params.deviceId || context.DEVICEID,
			create_date: dbCmd.gt(now - recordDate),
			type: 'login'
		})
			.orderBy('create_date', 'desc')
			.limit(recordSize)
			.get();
		return recentRecord.data.filter(item => item.state === 0).length === recordSize;
	}

179
	let res = {}
180
	switch (action) { //根据action的值执行对应的操作
181
		case 'renewDeviceTokenExpired':
182 183
			return await deviceDB.where({
				"user_id": params.uid,
184 185
				"device_id": context.DEVICEID
			}).update({
186 187
				"user_id": params.uid,
				"push_clientid": params.push_clientid,
188 189 190
				tokenExpired
			})
			break;
191
		case 'refreshSessionKey':
192 193 194 195
			let getSessionKey = await uniID.code2SessionWeixin({
				code: params.code
			});
			if (getSessionKey.code) {
196 197
				return getSessionKey
			}
198
			res = await uniID.updateUser({
199
				uid: params.uid,
200
				sessionKey: getSessionKey.sessionKey
201 202 203 204 205 206 207 208 209
			})
			console.log(res);
			break;
		case 'bindMobileByMpWeixin':
			console.log(params);
			let getSessionKeyRes = await uniID.getUserInfo({
				uid: params.uid,
				field: ['sessionKey']
			})
210
			if (getSessionKeyRes.code) {
211 212 213 214 215 216 217 218 219
				return getSessionKeyRes
			}
			let sessionKey = getSessionKeyRes.userInfo.sessionKey
			console.log(getSessionKeyRes);
			res = await uniID.wxBizDataCrypt({
				...params,
				sessionKey
			})
			console.log(res);
220
			if (res.code) {
221 222 223 224 225 226 227
				return res
			}
			res = await uniID.bindMobile({
				uid: params.uid,
				mobile: res.purePhoneNumber
			})
			console.log(res);
228
			break;
229
		case 'bindMobileByUniverify':
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
			let {
				appid, apiKey, apiSecret
			} = uniIdConfig.service.univerify
			let univerifyRes = await uniCloud.getPhoneNumber({
				provider: 'univerify',
				appid,
				apiKey,
				apiSecret,
				access_token: params.access_token,
				openid: params.openid
			})
			if (univerifyRes.code === 0) {
				res = await uniID.bindMobile({
					uid: params.uid,
					mobile: univerifyRes.phoneNumber
				})
				res.mobile = univerifyRes.phoneNumber
			}
			break;
249
		case 'bindMobileBySms':
250 251 252 253 254
			// console.log({
			// 	uid: params.uid,
			// 	mobile: params.mobile,
			// 	code: params.code
			// });
255 256 257 258 259
			res = await uniID.bindMobile({
				uid: params.uid,
				mobile: params.mobile,
				code: params.code
			})
260
			// console.log(res);
261
			break;
262
		case 'register':
263 264 265
			var {
				username, password, nickname
			} = params
266 267 268 269 270 271 272 273 274 275 276 277
			if (/^1\d{10}$/.test(username)) {
				return {
					code: 401,
					msg: '用户名不能是手机号'
				}
			};
			if (/^(\w-*\.*)+@(\w-?)+(\.\w{2,})+$/.test(username)) {
				return {
					code: 401,
					msg: '用户名不能是邮箱'
				}
			}
278 279 280 281 282 283
			res = await uniID.register({
				username,
				password,
				nickname,
				inviteCode
			});
284
			if (res.code === 0) {
285
				await registerSuccess(res)
286 287
			}
			break;
288

289 290 291 292 293 294 295
		case 'getNeedCaptcha': {
			const needCaptcha = await isNeedCaptcha()
			res.needCaptcha = needCaptcha
			break;
		}

		case 'login':
296
			let passed = false;
297
			let needCaptcha = await isNeedCaptcha();
298 299 300 301 302 303 304 305 306 307 308 309 310
			console.log('needCaptcha', needCaptcha);
			if (needCaptcha) {
				res = await uniCaptcha.verify({
					...params,
					scene: 'login'
				})
				if (res.code === 0) passed = true;
			}

			if (!needCaptcha || passed) {
				res = await uniID.login({
					...params,
					queryField: ['username', 'email', 'mobile']
311
				});
312
				res.type = 'login'
313
				await loginLog(res);
314
				needCaptcha = await isNeedCaptcha();
315 316 317 318
			}

			res.needCaptcha = needCaptcha;
			break;
319 320
		case 'loginByWeixin':
			let loginRes = await uniID.loginByWeixin(params);
321
			if (loginRes.code === 0) {
322
				//用户完善资料(昵称、头像)
323 324 325 326 327 328 329 330
				if (context.PLATFORM == "app-plus" && !loginRes.userInfo.nickname) {
					let {
						accessToken: access_token,
						openid
					} = loginRes, {
						appid,
						appsecret: secret
					} = uniIdConfig['app-plus'].oauth.weixin;
331 332
					let wxRes = await uniCloud.httpclient.request(
						`https://api.weixin.qq.com/sns/userinfo?access_token=${access_token}&openid=${openid}&scope=snsapi_userinfo&appid=${appid}&secret=${secret}`, {
333 334 335 336
						method: 'POST',
						contentType: 'json', // 指定以application/json发送data内的数据
						dataType: 'json' // 指定返回值为json格式,自动进行parse
					})
337 338 339 340 341 342 343
					if (wxRes.status == 200) {
						let {
							nickname,
							headimgurl
						} = wxRes.data;
						let headimgurlFile = {},
							cloudPath = loginRes.uid + '/' + Date.now() + "headimgurl.jpg";
344
						let getImgBuffer = await uniCloud.httpclient.request(headimgurl)
345 346 347 348 349
						if (getImgBuffer.status == 200) {
							let {
								fileID
							} = await uniCloud.uploadFile({
								cloudPath,
350
								fileContent: getImgBuffer.data
351 352
							});
							headimgurlFile = {
353 354 355
								name: cloudPath,
								extname: "jpg",
								url: fileID
356
							}
357
						} else {
358 359 360 361 362
							return getImgBuffer
						}
						await uniID.updateUser({
							uid: loginRes.uid,
							nickname,
363
							avatar_file: headimgurlFile
364 365 366
						})
						loginRes.userInfo.nickname = nickname;
						loginRes.userInfo.avatar_file = headimgurlFile;
367
					} else {
368 369
						return wxRes
					}
370
				}
371 372
				if (context.PLATFORM == "mp-weixin") {
					let resUpdateUser = await uniID.updateUser({
373
						uid: loginRes.uid,
374
						sessionKey: loginRes.sessionKey
375 376 377
					})
					console.log(resUpdateUser);
				}
378 379
				delete loginRes.openid
				delete loginRes.sessionKey
380 381 382
				delete loginRes.accessToken
				delete loginRes.refreshToken
			}
383
			await loginLog(loginRes)
384
			return loginRes
385
			break;
386
		case 'loginByUniverify':
387
			res = await uniID.loginByUniverify(params)
388 389
			await loginLog(res)
			break;
390
		case 'loginByApple':
391 392 393 394 395 396 397
			res = await uniID.loginByApple(params)
			await loginLog(res)
			break;
		case 'checkToken':
			res = await uniID.checkToken(uniIdToken);
			break;
		case 'logout':
398 399 400 401 402 403
			res = await uniID.logout(uniIdToken)
			await deviceDB.where({
				"device_id": context.DEVICEID,
			}).update({
				"tokenExpired": Date.now()
			})
404
			break;
405
		case 'sendSmsCode':
406
			/* -开始- 测试期间,为节约资源。统一虚拟短信验证码为: 123456;开启以下代码块即可  */
study夏羽's avatar
study夏羽 已提交
407 408 409 410 411
			return uniID.setVerifyCode({
				mobile: params.mobile,
				code: '123456',
				type: params.type
			})
412 413
			/* -结束- */

414
			// 简单限制一下客户端调用频率
415
			const ipLimit = await db.collection('opendb-verify-codes').where({
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
				ip: context.CLIENTIP,
				created_at: dbCmd.gt(Date.now() - 60000)
			}).get()
			if (ipLimit.data.length > 0) {
				return {
					code: 429,
					msg: '请求过于频繁'
				}
			}
			const templateId = '11753' // 替换为自己申请的模板id
			if (!templateId) {
				return {
					code: 500,
					msg: 'sendSmsCode需要传入自己的templateId,参考https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=sendsmscode'
				}
			}
			const randomStr = '00000' + Math.floor(Math.random() * 1000000)
			const code = randomStr.substring(randomStr.length - 6)
			res = await uniID.sendSmsCode({
				mobile: params.mobile,
				code,
				type: params.type,
				templateId
			})
			break;
		case 'loginBySms':
			if (!params.code) {
				return {
					code: 500,
					msg: '请填写验证码'
				}
			}
			if (!/^1\d{10}$/.test(params.mobile)) {
				return {
					code: 500,
					msg: '手机号码填写错误'
				}
			}
			res = await uniID.loginBySms(params)
			await loginLog(res)
			break;
		case 'resetPwdBySmsCode':
			if (!params.code) {
				return {
					code: 500,
					msg: '请填写验证码'
				}
			}
			if (!/^1\d{10}$/.test(params.mobile)) {
				return {
					code: 500,
					msg: '手机号码填写错误'
				}
469
			}
470
			params.type = 'login'
471
			let loginBySmsRes = await uniID.loginBySms(params)
472
			// console.log(loginBySmsRes);
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
			if (loginBySmsRes.code === 0) {
				res = await uniID.resetPwd({
					password: params.password,
					"uid": loginBySmsRes.uid
				})
			} else {
				return loginBySmsRes
			}
			break;
		case 'getInviteCode':
			res = await uniID.getUserInfo({
				uid: params.uid,
				field: ['my_invite_code']
			})
			if (res.code === 0) {
				res.myInviteCode = res.userInfo.my_invite_code
				delete res.userInfo
			}
			break;
		case 'getInvitedUser':
			res = await uniID.getInvitedUser(params)
			break;
		case 'updatePwd':
496
			res = await uniID.updatePwd(params)
497 498 499 500 501 502 503
			break;
		case 'createCaptcha':
			res = await uniCaptcha.create(params)
			break;
		case 'refreshCaptcha':
			res = await uniCaptcha.refresh(params)
			break;
504 505 506 507 508 509 510 511 512 513
		case 'getUserInviteCode':
			res = await uniID.getUserInfo({
				uid: params.uid,
				field: ['my_invite_code']
			})
			if (!res.userInfo.my_invite_code) {
				res = await uniID.setUserInviteCode({
					uid: params.uid
				})
			}
514 515 516 517 518 519
			break;
		case 'closeAccount':
			console.log(params.uid, '-----------------------');
			res = await uniID.closeAccount({
				uid: params.uid
			});
520 521
			break;

522
		// =========================== admin api start =========================
523
		case 'registerAdmin': {
524
			var {
525 526
				username,
				password
527 528 529 530 531 532 533 534 535 536 537 538
			} = params
			let {
				total
			} = await db.collection('uni-id-users').where({
				role: 'admin'
			}).count()
			if (total) {
				return {
					code: 10001,
					message: '超级管理员已存在,请登录...'
				}
			}
539 540 541 542 543
			const appid = params.appid
			const appName = params.appName
			delete params.appid
			delete params.appName
			res = await uniID.register({
544 545 546 547
				username,
				password,
				role: ["admin"]
			})
548 549 550 551 552 553 554 555 556 557 558
			if (res.code === 0) {
				const app = await db.collection('opendb-app-list').where({
					appid
				}).count()
				if (!app.total) {
					await db.collection('opendb-app-list').add({
						appid,
						name: appName,
						description: "admin 管理后台",
						create_date: Date.now()
					})
559
				}
560

561
			}
562
		}
563 564 565 566 567 568
			break;
		case 'registerUser':
			const {
				userInfo
			} = await uniID.getUserInfo({
				uid: params.uid
569
			})
570 571 572 573 574 575 576 577 578 579 580 581
			if (userInfo.role.indexOf('admin') === -1) {
				res = {
					code: 403,
					message: '非法访问, 无权限注册超级管理员',
				}
			} else {
				// 过滤 dcloud_appid,注册用户成功后再提交
				const dcloudAppidList = params.dcloud_appid
				delete params.dcloud_appid
				res = await uniID.register({
					autoSetDcloudAppid: false,
					...params
582
				})
583 584 585 586 587 588 589 590
				if (res.code === 0) {
					delete res.token
					delete res.tokenExpired
					await uniID.setAuthorizedAppLogin({
						uid: res.uid,
						dcloudAppidList
					})
				}
591
			}
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
			break;
		case 'updateUser': {
			const {
				userInfo
			} = await uniID.getUserInfo({
				uid: params.uid
			})
			if (userInfo.role.indexOf('admin') === -1) {
				res = {
					code: 403,
					message: '非法访问, 无权限注册超级管理员',
				}
			} else {
				// 过滤 dcloud_appid,注册用户成功后再提交
				const dcloudAppidList = params.dcloud_appid
				delete params.dcloud_appid
608

609 610 611
				// 过滤 password,注册用户成功后再提交
				const password = params.password
				delete params.password
612

613 614 615 616
				// 过滤 uid、id
				const id = params.id
				delete params.id
				delete params.uid
617 618


619 620 621 622 623 624 625 626 627 628 629 630
				res = await uniID.updateUser({
					uid: id,
					...params
				})
				if (res.code === 0) {
					if (password) {
						await uniID.resetPwd({
							uid: id,
							password
						})
					}
					await uniID.setAuthorizedAppLogin({
631
						uid: id,
632
						dcloudAppidList
633 634
					})
				}
635
			}
636
			break;
637
		}
638 639 640 641
		case 'getCurrentUserInfo':
			res = await uniID.getUserInfo({
				uid: params.uid,
				...params
642
			})
643 644 645 646 647 648
			break;
		case 'managerMultiTag': {
			const {
				userInfo
			} = await uniID.getUserInfo({
				uid: params.uid
649
			})
650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687
			// 限制只有 admin 角色的用户可管理标签,如需非 admin 角色需自行实现
			if (userInfo.role.indexOf('admin') === -1) {
				res = {
					code: 403,
					message: '非法访问, 无权限修改用户标签',
				}
				return
			}
			let {
				ids,
				type,
				value
			} = params
			if (type === 'add') {
				res = await db.collection('uni-id-users').where({
					_id: dbCmd.in(ids)
				}).update({
					tags: dbCmd.addToSet({
						$each: value
					})
				})
			} else if (type === 'del') {
				res = await db.collection('uni-id-users').where({
					_id: dbCmd.in(ids)
				}).update({
					tags: dbCmd.pull(dbCmd.in(value))
				})
			} else {
				res = {
					code: 403,
					msg: '无效操作'
				}
				return
			}
			break;
		}
		// =========================== admin api end =========================
		default:
688 689
			res = {
				code: 403,
690
				msg: '非法访问'
691
			}
692
			break;
693 694 695
	}
	//返回数据给客户端
	return res
696
}