Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add qq login callback #711

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions config/.env.defaults
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ WEB_WECHAT_APP_SECRET=
MOBILE_WECHAT_APP_ID=
MOBILE_WECHAT_APP_SECRET=

QQ_APP_ID=
QQ_APP_SECRET=

GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=

Expand Down
4 changes: 4 additions & 0 deletions config/defaults.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ oauth:
- jpeg

login:
qq:
enable: false
app_id:
app_secret:
wechat:
web:
enable: false
Expand Down
4 changes: 4 additions & 0 deletions config/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ oauth:
- jpeg

login:
qq:
enable: false
app_id:
app_secret:
wechat:
web:
enable: true
Expand Down
6 changes: 6 additions & 0 deletions src/constants/Config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ export const WeChat = {
},
};

export const QQ = {
enable: config.login.qq.enable,
appId: config.login.qq.app_id,
appSecret: config.login.qq.app_secret,
};

export const Github = {
enable: config.login.github.enable,
clientId: config.login.github.client_id,
Expand Down
1 change: 1 addition & 0 deletions src/constants/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export enum Status {
}

export enum LoginPlatform {
QQ = "QQ",
WeChat = "WeChat",
Github = "Github",
Apple = "Apple",
Expand Down
3 changes: 3 additions & 0 deletions src/dao/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { RoomModel } from "../model/room/Room";
import { DAO } from "./Type";
import { RoomUserModel } from "../model/room/RoomUser";
import { UserModel } from "../model/user/User";
import { UserQQModel } from "../model/user/QQ";
import { UserWeChatModel } from "../model/user/WeChat";
import { RoomPeriodicConfigModel } from "../model/room/RoomPeriodicConfig";
import { RoomPeriodicModel } from "../model/room/RoomPeriodic";
Expand All @@ -19,6 +20,8 @@ import { UserPhoneModel } from "../model/user/Phone";

export const UserDAO = DAOImplement(UserModel) as ReturnType<DAO<UserModel>>;

export const UserQQDAO = DAOImplement(UserQQModel) as ReturnType<DAO<UserQQModel>>;

export const UserWeChatDAO = DAOImplement(UserWeChatModel) as ReturnType<DAO<UserWeChatModel>>;

export const UserGithubDAO = DAOImplement(UserGithubModel) as ReturnType<DAO<UserGithubModel>>;
Expand Down
2 changes: 2 additions & 0 deletions src/model/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { UserModel } from "./user/User";
import { UserQQModel } from "./user/QQ";
import { UserWeChatModel } from "./user/WeChat";
import { UserGithubModel } from "./user/Github";
import { UserAppleModel } from "./user/Apple";
Expand All @@ -20,6 +21,7 @@ import { OAuthUsersModel } from "./oauth/oauth-users";

export type Model =
| UserModel
| UserQQModel
| UserWeChatModel
| UserGithubModel
| UserAppleModel
Expand Down
39 changes: 39 additions & 0 deletions src/model/user/QQ.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Column, Entity, Index } from "typeorm";
import { Content } from "../Content";

@Entity({
name: "user_qq",
})
export class UserQQModel extends Content {
@Index("user_qq_user_uuid_uindex", {
unique: true,
})
@Column({
length: 40,
})
user_uuid: string;

@Column({
length: 40,
comment: "qq nickname",
})
user_name: string;

@Column({
length: 40,
comment: "qq open id",
})
open_uuid: string;

@Column({
length: 40,
comment: "qq union id",
})
union_uuid: string;

@Index("user_qq_is_delete_index")
@Column({
default: false,
})
is_delete: boolean;
}
2 changes: 2 additions & 0 deletions src/thirdPartyService/TypeORMService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { RoomPeriodicUserModel } from "../model/room/RoomPeriodicUser";
import { RoomRecordModel } from "../model/room/RoomRecord";
import { RoomUserModel } from "../model/room/RoomUser";
import { UserModel } from "../model/user/User";
import { UserQQModel } from "../model/user/QQ";
import { UserWeChatModel } from "../model/user/WeChat";
import { UserGithubModel } from "../model/user/Github";
import { loggerServer, parseError } from "../logger";
Expand All @@ -30,6 +31,7 @@ export const dataSource = new DataSource({
port: MySQL.port,
entities: [
UserModel,
UserQQModel,
UserWeChatModel,
UserGithubModel,
UserAppleModel,
Expand Down
5 changes: 5 additions & 0 deletions src/utils/ParseConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ type Config = {
app_secret: string;
};
};
qq: {
enable: boolean;
app_id: string;
app_secret: string;
};
github: {
enable: boolean;
client_id: string;
Expand Down
1 change: 1 addition & 0 deletions src/utils/registryRoutersV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ interface R<O> {
auth?: boolean;
schema: S;
autoHandle?: O;
enable?: boolean;
},
): void;
}
Expand Down
2 changes: 2 additions & 0 deletions src/v1/controller/login/Router.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { QQWebCallback } from "./qq/Callback";
import { WechatWebCallback } from "./weChat/web/Callback";
import { WechatMobileCallback } from "./weChat/mobile/Callback";
import { GithubCallback } from "./github/Callback";
Expand All @@ -14,6 +15,7 @@ import { PhoneLogin } from "./phone/Phone";
export const loginRouters: Readonly<Array<ControllerClass<any, any>>> = Object.freeze([
SetAuthUUID,
LoginProcess,
QQWebCallback,
WechatWebCallback,
WechatMobileCallback,
AppleJWT,
Expand Down
153 changes: 153 additions & 0 deletions src/v1/controller/login/platforms/LoginQQ.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { LoginClassParams } from "abstract/login/Type";
import { AbstractLogin } from "../../../../abstract/login";
import { QQ } from "../../../../constants/Config";
import { Gender } from "../../../../constants/Project";
import { dataSource } from "../../../../thirdPartyService/TypeORMService";
import { ServiceUser } from "../../../service/user/User";
import { ServiceUserQQ } from "../../../service/user/UserQQ";
import { ServiceCloudStorageFiles } from "../../../service/cloudStorage/CloudStorageFiles";
import { ServiceCloudStorageConfigs } from "../../../service/cloudStorage/CloudStorageConfigs";
import { ServiceCloudStorageUserFiles } from "../../../service/cloudStorage/CloudStorageUserFiles";
import { ax } from "../../../utils/Axios";

export class LoginQQ extends AbstractLogin {
public readonly svc: RegisterService;

constructor(params: LoginClassParams) {
super(params);

this.svc = {
user: new ServiceUser(this.userUUID),
userQQ: new ServiceUserQQ(this.userUUID),
cloudStorageFiles: new ServiceCloudStorageFiles(),
cloudStorageConfigs: new ServiceCloudStorageConfigs(this.userUUID),
cloudStorageUserFiles: new ServiceCloudStorageUserFiles(this.userUUID),
};
}

public async register(info: RegisterInfo): Promise<void> {
await dataSource.transaction(async t => {
const createUser = this.svc.user.create(info, t);

const createUserWeChat = this.svc.userQQ.create(info, t);

return await Promise.all([
createUser,
createUserWeChat,
this.setGuidePPTX(this.svc, t),
]);
});
}

public static async getUserInfoAndToken(
code: string,
): Promise<RegisterInfo & { accessToken: string }> {
const accessToken = await LoginQQ.getToken(code);
const uuidInfo = await LoginQQ.getUUIDByAPI(accessToken);
const userInfo = await LoginQQ.getUserInfoByAPI(accessToken, uuidInfo.openUUID);

return {
accessToken,
...uuidInfo,
...userInfo,
};
}

public static async getToken(code: string): Promise<string> {
const QQ_CALLBACK = "https://flat-web.whiteboard.agora.io/qq/callback";
const response = await ax.get<AccessToken>(
`https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=${QQ.appId}&client_secret=${QQ.appSecret}&code=${code}&redirect_uri=${QQ_CALLBACK}&fmt=json`,
);

return response.data.access_token;
}

public static async getUUIDByAPI(accessToken: string): Promise<QQUUIDInfo> {
const response = await ax.get<QQUUIDResponse | RequestFailed>(
`https://graph.qq.com/oauth2.0/me?access_token=${accessToken}&unionid=1&fmt=json`,
);

if ("error" in response.data) {
throw new Error(String(response.data.error));
}

return {
openUUID: response.data.openid,
unionUUID: response.data.unionid,
};
}

public static async getUserInfoByAPI(
accessToken: string,
openUUID: string,
): Promise<QQUserInfo> {
const response = await ax.get<QQUserResponse>(
`https://graph.qq.com/user/get_user_info?access_token=${accessToken}&oauth_consumer_key=${QQ.appId}&openid=${openUUID}`,
);

const { ret, nickname, figureurl_qq_1, figureurl_qq_2, gender } = response.data;

if (ret !== 0) {
throw new Error(String(ret));
}

return {
userName: nickname,
avatarURL: figureurl_qq_2 || figureurl_qq_1,
gender: gender === "男" ? Gender["Man"] : Gender["Woman"],
};
}
}

interface RegisterService {
user: ServiceUser;
userQQ: ServiceUserQQ;
cloudStorageFiles: ServiceCloudStorageFiles;
cloudStorageUserFiles: ServiceCloudStorageUserFiles;
cloudStorageConfigs: ServiceCloudStorageConfigs;
}

interface RegisterInfo {
userName: string;
avatarURL: string;
openUUID: string;
unionUUID: string;
gender: Gender;
}

interface AccessToken {
readonly access_token: string;
readonly expires_in: string;
readonly refresh_token: string;
}

interface QQUUIDResponse {
readonly client_id: string;
readonly openid: string;
readonly unionid: string;
}

interface QQUUIDInfo {
readonly openUUID: string;
readonly unionUUID: string;
}

interface QQUserInfo {
readonly userName: string;
readonly avatarURL: string;
readonly gender: Gender;
}

interface QQUserResponse {
readonly ret: number;
readonly msg: string;
readonly nickname: string;
readonly figureurl_qq_1: string;
readonly figureurl_qq_2: string;
readonly gender: "男" | "女";
}

interface RequestFailed {
readonly error: number;
readonly error_description: string;
}
Loading