Skip to content

Commit 2a8e1e3

Browse files
authored
Merge pull request #114 from import-ai/feat/wechat
feat(wechat): support obtaining wechat user name
2 parents 5134cf1 + 0bd4b22 commit 2a8e1e3

File tree

4 files changed

+87
-16
lines changed

4 files changed

+87
-16
lines changed

src/auth/wechat.service.ts

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
1-
import { DataSource } from 'typeorm';
1+
import { DataSource, EntityManager } from 'typeorm';
22
import { JwtService } from '@nestjs/jwt';
33
import { ConfigService } from '@nestjs/config';
44
import generateId from 'omnibox-backend/utils/generate-id';
55
import { UserService } from 'omnibox-backend/user/user.service';
66
import { NamespacesService } from 'omnibox-backend/namespaces/namespaces.service';
77
import { WechatCheckResponseDto } from './dto/wechat-login.dto';
88
import {
9+
BadRequestException,
910
Injectable,
11+
InternalServerErrorException,
12+
Logger,
1013
UnauthorizedException,
11-
BadRequestException,
1214
} from '@nestjs/common';
1315

1416
@Injectable()
1517
export class WechatService {
18+
private readonly logger = new Logger(WechatService.name);
19+
1620
private readonly appId: string;
1721
private readonly appSecret: string;
1822
private readonly redirectUri: string;
@@ -28,6 +32,9 @@ export class WechatService {
2832
}
2933
>();
3034

35+
private readonly minUsernameLength = 2;
36+
private readonly maxUsernameLength = 32;
37+
3138
constructor(
3239
private readonly configService: ConfigService,
3340
private readonly jwtService: JwtService,
@@ -90,6 +97,46 @@ export class WechatService {
9097
return `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${this.appId}&redirect_uri=${encodeURIComponent(this.redirectUri)}&response_type=code&scope=snsapi_userinfo&state=${state}#wechat_redirect`;
9198
}
9299

100+
generateSuffix(): string {
101+
return (
102+
'_' +
103+
generateId(4, 'useandomTPXpxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict')
104+
);
105+
}
106+
107+
async getValidUsername(
108+
nickname: string,
109+
manager: EntityManager,
110+
): Promise<string> {
111+
let username = nickname;
112+
113+
if (username.length > this.maxUsernameLength) {
114+
username = nickname.slice(0, this.maxUsernameLength);
115+
}
116+
if (username.length >= this.minUsernameLength) {
117+
const user = await this.userService.findByUsername(username, manager);
118+
if (!user) {
119+
return username;
120+
}
121+
}
122+
123+
username = nickname.slice(0, this.maxUsernameLength - 5);
124+
for (let i = 0; i < 5; i++) {
125+
const suffix = this.generateSuffix();
126+
const user = await this.userService.findByUsername(
127+
username + suffix,
128+
manager,
129+
);
130+
if (!user) {
131+
return username + suffix;
132+
}
133+
}
134+
135+
throw new InternalServerErrorException(
136+
'Unable to generate a valid username',
137+
);
138+
}
139+
93140
async handleCallback(code: string, state: string): Promise<any> {
94141
const stateInfo = this.qrCodeStates.get(state);
95142
if (!stateInfo) {
@@ -98,22 +145,28 @@ export class WechatService {
98145
const isWeixin = stateInfo.type === 'weixin';
99146
const appId = isWeixin ? this.appId : this.openAppId;
100147
const appSecret = isWeixin ? this.appSecret : this.openAppSecret;
101-
const response = await fetch(
148+
const accessTokenResponse = await fetch(
102149
`https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appId}&secret=${appSecret}&code=${code}&grant_type=authorization_code`,
103150
);
104-
if (!response.ok) {
151+
if (!accessTokenResponse.ok) {
105152
throw new UnauthorizedException('Failed to get WeChat access token');
106153
}
107-
const userData = await response.json();
154+
const accessTokenData = await accessTokenResponse.json();
108155

109-
if (userData.errmsg) {
110-
throw new BadRequestException(userData.errmsg);
156+
if (accessTokenData.errmsg) {
157+
throw new BadRequestException(accessTokenData.errmsg);
111158
}
112159

113-
if (!userData.unionid) {
114-
throw new UnauthorizedException(
115-
'Failed to get WeChat UnionID, please make sure you have followed the official account',
116-
);
160+
const userDataResponse = await fetch(
161+
`https://api.weixin.qq.com/sns/userinfo?access_token=${accessTokenData.access_token}&openid=${accessTokenData.openid}&lang=zh_CN`,
162+
);
163+
if (!userDataResponse.ok) {
164+
throw new UnauthorizedException('Failed to get WeChat user info');
165+
}
166+
const userData = await userDataResponse.json();
167+
168+
if (userData.errmsg) {
169+
throw new BadRequestException(userData.errmsg);
117170
}
118171

119172
const wechatUser = await this.userService.findByLoginId(userData.unionid);
@@ -127,10 +180,13 @@ export class WechatService {
127180
stateInfo.userInfo = returnValue;
128181
return returnValue;
129182
}
130-
131183
return await this.dataSource.transaction(async (manager) => {
184+
const nickname: string = userData.nickname;
185+
const username: string = await this.getValidUsername(nickname, manager);
186+
this.logger.debug({ nickname, username });
132187
const wechatUser = await this.userService.createUserBinding(
133188
{
189+
username,
134190
loginType: 'wechat',
135191
loginId: userData.unionid,
136192
},

src/user/dto/create-user-binding.dto.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ export class CreateUserBindingDto {
66

77
@IsString()
88
loginType: string;
9+
10+
@IsString()
11+
username: string;
912
}

src/user/user.service.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,17 @@ export class UserService {
9393
return await this.find(binding.userId);
9494
}
9595

96+
async findByUsername(
97+
username: string,
98+
manager?: EntityManager,
99+
): Promise<User | null> {
100+
const repo = manager ? manager.getRepository(User) : this.userRepository;
101+
return await repo.findOne({
102+
where: { username },
103+
select: ['id', 'username', 'email'],
104+
});
105+
}
106+
96107
async createUserBinding(
97108
userData: CreateUserBindingDto,
98109
manager?: EntityManager,
@@ -101,7 +112,7 @@ export class UserService {
101112
const hash = await bcrypt.hash(Math.random().toString(36), 10);
102113
const newUser = repo.create({
103114
password: hash,
104-
username: userData.loginId,
115+
username: userData.username,
105116
});
106117

107118
// eslint-disable-next-line @typescript-eslint/no-unused-vars

src/utils/generate-id.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { customAlphabet } from 'nanoid';
22

3-
export default function generateId(size = 16) {
4-
const urlAlphabet =
5-
'useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict';
3+
export default function generateId(
4+
size = 16,
5+
urlAlphabet = 'useandom26T198340PX75pxJACKVERYMINDBUSHWOLFGQZbfghjklqvwyzrict',
6+
) {
67
const nanoid = customAlphabet(urlAlphabet, size);
78
return nanoid();
89
}

0 commit comments

Comments
 (0)