|
|
@@ -2,6 +2,7 @@ import { DataSource, Repository } from 'typeorm';
|
|
|
import { UserEntity } from '../users/user.entity';
|
|
|
import { FileService } from '../files/file.service';
|
|
|
import { JWTUtil } from '../../utils/jwt.util';
|
|
|
+import { redisUtil } from '../../utils/redis.util';
|
|
|
import axios from 'axios';
|
|
|
import process from 'node:process'
|
|
|
|
|
|
@@ -15,25 +16,28 @@ export class MiniAuthService {
|
|
|
}
|
|
|
|
|
|
async miniLogin(code: string): Promise<{ token: string; user: UserEntity; isNewUser: boolean }> {
|
|
|
- // 1. 通过code获取openid
|
|
|
+ // 1. 通过code获取openid和session_key
|
|
|
const openidInfo = await this.getOpenIdByCode(code);
|
|
|
-
|
|
|
+
|
|
|
// 2. 查找或创建用户
|
|
|
- let user = await this.userRepository.findOne({
|
|
|
- where: { openid: openidInfo.openid }
|
|
|
+ let user = await this.userRepository.findOne({
|
|
|
+ where: { openid: openidInfo.openid }
|
|
|
});
|
|
|
-
|
|
|
+
|
|
|
let isNewUser = false;
|
|
|
-
|
|
|
+
|
|
|
if (!user) {
|
|
|
// 自动注册新用户
|
|
|
user = await this.createMiniUser(openidInfo);
|
|
|
isNewUser = true;
|
|
|
}
|
|
|
-
|
|
|
- // 3. 生成token
|
|
|
+
|
|
|
+ // 3. 保存sessionKey到Redis
|
|
|
+ await redisUtil.setSessionKey(user.id, openidInfo.session_key);
|
|
|
+
|
|
|
+ // 4. 生成token
|
|
|
const token = this.generateToken(user);
|
|
|
-
|
|
|
+
|
|
|
return { token, user, isNewUser };
|
|
|
}
|
|
|
|
|
|
@@ -135,17 +139,58 @@ export class MiniAuthService {
|
|
|
* 解密小程序加密的手机号
|
|
|
*/
|
|
|
async decryptPhoneNumber(encryptedData: string, iv: string, sessionKey: string): Promise<string> {
|
|
|
- // TODO: 集成微信小程序SDK进行实际的手机号解密
|
|
|
- // 这里返回模拟的手机号用于开发测试
|
|
|
console.debug('手机号解密请求:', { encryptedData, iv, sessionKey });
|
|
|
|
|
|
- // 模拟解密过程
|
|
|
+ // 参数验证
|
|
|
if (!encryptedData || !iv || !sessionKey) {
|
|
|
throw { code: 400, message: '加密数据或初始向量不能为空' };
|
|
|
}
|
|
|
|
|
|
- // 在实际环境中,这里应该调用微信SDK进行解密
|
|
|
- // 返回模拟的手机号
|
|
|
- return '13800138000';
|
|
|
+ try {
|
|
|
+ // 使用Node.js内置crypto模块进行AES-128-CBC解密
|
|
|
+ // 微信小程序手机号解密算法:AES-128-CBC,PKCS#7填充
|
|
|
+ const crypto = await import('node:crypto');
|
|
|
+
|
|
|
+ // 创建解密器
|
|
|
+ const decipher = crypto.createDecipheriv(
|
|
|
+ 'aes-128-cbc',
|
|
|
+ Buffer.from(sessionKey, 'base64'),
|
|
|
+ Buffer.from(iv, 'base64')
|
|
|
+ );
|
|
|
+
|
|
|
+ // 设置自动PKCS#7填充
|
|
|
+ decipher.setAutoPadding(true);
|
|
|
+
|
|
|
+ // 解密数据
|
|
|
+ let decrypted = decipher.update(Buffer.from(encryptedData, 'base64'));
|
|
|
+ decrypted = Buffer.concat([decrypted, decipher.final()]);
|
|
|
+
|
|
|
+ // 解析解密后的JSON数据
|
|
|
+ const decryptedStr = decrypted.toString('utf8');
|
|
|
+ const phoneData = JSON.parse(decryptedStr);
|
|
|
+
|
|
|
+ // 验证解密结果
|
|
|
+ if (!phoneData.phoneNumber || typeof phoneData.phoneNumber !== 'string') {
|
|
|
+ throw new Error('解密数据格式不正确');
|
|
|
+ }
|
|
|
+
|
|
|
+ console.debug('手机号解密成功:', { phoneNumber: phoneData.phoneNumber });
|
|
|
+ return phoneData.phoneNumber;
|
|
|
+
|
|
|
+ } catch (error) {
|
|
|
+ console.error('手机号解密失败:', error);
|
|
|
+
|
|
|
+ // 根据错误类型返回相应的错误信息
|
|
|
+ if (error instanceof SyntaxError) {
|
|
|
+ throw { code: 400, message: '解密数据格式错误' };
|
|
|
+ } else if (error instanceof Error && error.message?.includes('wrong final block length')) {
|
|
|
+ throw { code: 400, message: '解密数据长度不正确' };
|
|
|
+ } else if (error instanceof Error && error.message?.includes('bad decrypt')) {
|
|
|
+ throw { code: 400, message: '解密失败,请检查sessionKey是否正确' };
|
|
|
+ } else {
|
|
|
+ const errorMessage = error instanceof Error ? error.message : '未知错误';
|
|
|
+ throw { code: 400, message: '手机号解密失败: ' + errorMessage };
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
}
|