2025-12-01 00:40:46 +08:00
|
|
|
import { logger, simpleNanoId, utils } from "@certd/basic";
|
2025-11-30 01:13:55 +08:00
|
|
|
import { addonRegistry, AddonService, BaseController, Constants, SysInstallInfo, SysSettingsService } from "@certd/lib-server";
|
2025-12-01 00:40:46 +08:00
|
|
|
import { checkPlus } from "@certd/plus-core";
|
2025-11-28 01:42:42 +08:00
|
|
|
import { ALL, Body, Controller, Get, Inject, Param, Post, Provide, Query } from "@midwayjs/core";
|
2025-11-27 01:59:22 +08:00
|
|
|
import { CodeService } from "../../../modules/basic/service/code-service.js";
|
|
|
|
|
import { OauthBoundEntity } from "../../../modules/login/entity/oauth-bound.js";
|
2025-12-01 00:40:46 +08:00
|
|
|
import { LoginService } from "../../../modules/login/service/login-service.js";
|
|
|
|
|
import { OauthBoundService } from "../../../modules/login/service/oauth-bound-service.js";
|
|
|
|
|
import { AddonGetterService } from "../../../modules/pipeline/service/addon-getter-service.js";
|
|
|
|
|
import { UserEntity } from "../../../modules/sys/authority/entity/user.js";
|
|
|
|
|
import { UserService } from "../../../modules/sys/authority/service/user-service.js";
|
|
|
|
|
import { IOauthProvider } from "../../../plugins/plugin-oauth/api.js";
|
2025-11-27 01:59:22 +08:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
*/
|
|
|
|
|
@Provide()
|
|
|
|
|
@Controller('/api/oauth')
|
|
|
|
|
export class ConnectController extends BaseController {
|
|
|
|
|
|
|
|
|
|
@Inject()
|
|
|
|
|
addonGetterService: AddonGetterService;
|
|
|
|
|
@Inject()
|
|
|
|
|
sysSettingsService: SysSettingsService;
|
|
|
|
|
@Inject()
|
|
|
|
|
loginService: LoginService;
|
|
|
|
|
@Inject()
|
|
|
|
|
codeService: CodeService;
|
|
|
|
|
@Inject()
|
|
|
|
|
userService: UserService;
|
|
|
|
|
|
|
|
|
|
@Inject()
|
|
|
|
|
oauthBoundService: OauthBoundService;
|
|
|
|
|
|
2025-11-30 01:13:55 +08:00
|
|
|
@Inject()
|
|
|
|
|
addonService: AddonService;
|
|
|
|
|
|
2025-11-27 01:59:22 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
private async getOauthProvider(type: string) {
|
|
|
|
|
const publicSettings = await this.sysSettingsService.getPublicSettings()
|
|
|
|
|
if (!publicSettings?.oauthEnabled) {
|
|
|
|
|
throw new Error("OAuth功能未启用");
|
|
|
|
|
}
|
|
|
|
|
const setting = publicSettings?.oauthProviders?.[type || ""]
|
|
|
|
|
if (!setting) {
|
|
|
|
|
throw new Error(`未配置该OAuth类型:${type}`);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-13 23:51:27 +08:00
|
|
|
const addon = await this.addonGetterService.getAddonById(setting.addonId, true, 0,null);
|
2025-11-27 01:59:22 +08:00
|
|
|
if (!addon) {
|
|
|
|
|
throw new Error("初始化OAuth插件失败");
|
|
|
|
|
}
|
|
|
|
|
return addon as IOauthProvider;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Post('/login', { summary: Constants.per.guest })
|
2025-11-30 01:13:55 +08:00
|
|
|
public async login(@Body(ALL) body: { type: string, forType?:string ,from?:string }) {
|
2025-11-27 01:59:22 +08:00
|
|
|
|
|
|
|
|
const addon = await this.getOauthProvider(body.type);
|
|
|
|
|
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo);
|
|
|
|
|
const bindUrl = installInfo?.bindUrl || "";
|
|
|
|
|
//构造登录url
|
2025-11-28 01:42:42 +08:00
|
|
|
const redirectUrl = `${bindUrl}api/oauth/callback/${body.type}`;
|
2025-11-30 01:13:55 +08:00
|
|
|
const { loginUrl, ticketValue } = await addon.buildLoginUrl({ redirectUri: redirectUrl, forType: body.forType ,from: body.from || "web" });
|
2025-11-28 01:42:42 +08:00
|
|
|
const ticket = this.codeService.setValidationValue(ticketValue)
|
|
|
|
|
this.ctx.cookies.set("oauth_ticket", ticket, {
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
// secure: true,
|
|
|
|
|
// sameSite: "strict",
|
|
|
|
|
})
|
|
|
|
|
return this.ok({ loginUrl, ticket });
|
2025-11-27 01:59:22 +08:00
|
|
|
}
|
2026-02-27 00:37:24 +08:00
|
|
|
|
|
|
|
|
|
2025-11-28 01:42:42 +08:00
|
|
|
@Get('/callback/:type', { summary: Constants.per.guest })
|
|
|
|
|
public async callback(@Param('type') type: string, @Query() query: Record<string, string>) {
|
2025-11-29 03:25:21 +08:00
|
|
|
|
|
|
|
|
checkPlus()
|
|
|
|
|
|
2025-11-27 01:59:22 +08:00
|
|
|
//处理登录回调
|
2025-11-28 01:42:42 +08:00
|
|
|
const addon = await this.getOauthProvider(type);
|
|
|
|
|
const request = this.ctx.request;
|
|
|
|
|
// const ticketValue = this.codeService.getValidationValue(ticket);
|
|
|
|
|
// if (!ticketValue) {
|
|
|
|
|
// throw new Error("登录ticket已过期");
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
const ticket = this.ctx.cookies.get("oauth_ticket");
|
|
|
|
|
if (!ticket) {
|
|
|
|
|
throw new Error("ticket已过期");
|
|
|
|
|
}
|
|
|
|
|
const ticketValue = this.codeService.getValidationValue(ticket);
|
|
|
|
|
if (!ticketValue) {
|
|
|
|
|
throw new Error("ticketValue已过期");
|
|
|
|
|
}
|
2025-11-27 01:59:22 +08:00
|
|
|
|
2025-11-28 01:42:42 +08:00
|
|
|
const installInfo = await this.sysSettingsService.getSetting<SysInstallInfo>(SysInstallInfo);
|
|
|
|
|
const bindUrl = installInfo?.bindUrl || "";
|
|
|
|
|
const currentUrl = `${bindUrl}api/oauth/callback/${type}?${request.querystring}`
|
|
|
|
|
try {
|
|
|
|
|
const tokenRes = await addon.onCallback({
|
|
|
|
|
code: query.code,
|
|
|
|
|
state: query.state,
|
|
|
|
|
ticketValue,
|
|
|
|
|
currentURL: new URL(currentUrl)
|
|
|
|
|
});
|
2025-11-27 01:59:22 +08:00
|
|
|
|
2025-11-28 01:42:42 +08:00
|
|
|
const userInfo = tokenRes.userInfo;
|
2025-11-27 01:59:22 +08:00
|
|
|
|
|
|
|
|
const validationCode = await this.codeService.setValidationValue({
|
2025-11-28 01:42:42 +08:00
|
|
|
type,
|
2025-11-27 01:59:22 +08:00
|
|
|
userInfo,
|
|
|
|
|
});
|
2025-11-28 01:42:42 +08:00
|
|
|
|
2025-11-29 03:25:21 +08:00
|
|
|
const state = JSON.parse(utils.hash.base64Decode(query.state));
|
|
|
|
|
|
|
|
|
|
const redirectUrl = `${bindUrl}#/oauth/callback/${type}?validationCode=${validationCode}&forType=${state.forType}`;
|
2025-11-28 01:42:42 +08:00
|
|
|
this.ctx.redirect(redirectUrl);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
logger.error(err);
|
|
|
|
|
this.ctx.redirect(`${bindUrl}#/oauth/callback/${type}?error=${err.error_description || err.message}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-01 00:40:46 +08:00
|
|
|
@Post('/getLogoutUrl', { summary: Constants.per.guest })
|
|
|
|
|
public async logout(@Body(ALL) body: any) {
|
|
|
|
|
checkPlus()
|
|
|
|
|
const addon = await this.getOauthProvider(body.type);
|
|
|
|
|
const { logoutUrl } = await addon.buildLogoutUrl(body);
|
|
|
|
|
return this.ok({ logoutUrl });
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-28 01:42:42 +08:00
|
|
|
|
|
|
|
|
@Post('/token', { summary: Constants.per.guest })
|
|
|
|
|
public async token(@Body(ALL) body: { validationCode: string, type: string }) {
|
2025-11-29 03:25:21 +08:00
|
|
|
checkPlus()
|
2025-11-28 01:42:42 +08:00
|
|
|
const validationValue = await this.codeService.getValidationValue(body.validationCode);
|
|
|
|
|
if (!validationValue) {
|
|
|
|
|
throw new Error("校验码错误");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const type = validationValue.type;
|
|
|
|
|
if (type !== body.type) {
|
|
|
|
|
throw new Error("校验码错误");
|
|
|
|
|
}
|
|
|
|
|
const userInfo = validationValue.userInfo;
|
|
|
|
|
const openId = userInfo.openId;
|
|
|
|
|
|
|
|
|
|
const loginRes = await this.loginService.loginByOpenId({ openId, type });
|
|
|
|
|
if (loginRes == null) {
|
|
|
|
|
|
2025-11-27 01:59:22 +08:00
|
|
|
return this.ok({
|
|
|
|
|
bindRequired: true,
|
2025-11-28 01:42:42 +08:00
|
|
|
validationCode: body.validationCode,
|
2025-11-27 01:59:22 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 00:37:24 +08:00
|
|
|
this.writeTokenCookie(loginRes);
|
2025-11-27 01:59:22 +08:00
|
|
|
//返回登录成功token
|
|
|
|
|
return this.ok(loginRes);
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-27 00:37:24 +08:00
|
|
|
private writeTokenCookie(token: { expire: any; token: any }) {
|
|
|
|
|
// this.loginService.writeTokenCookie(this.ctx,token);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-27 01:59:22 +08:00
|
|
|
|
|
|
|
|
@Post('/autoRegister', { summary: Constants.per.guest })
|
|
|
|
|
public async autoRegister(@Body(ALL) body: { validationCode: string, type: string }) {
|
|
|
|
|
|
|
|
|
|
const validationValue = this.codeService.getValidationValue(body.validationCode);
|
|
|
|
|
if (!validationValue) {
|
|
|
|
|
throw new Error("第三方认证授权已过期");
|
|
|
|
|
}
|
|
|
|
|
const userInfo = validationValue.userInfo;
|
|
|
|
|
const oauthType = validationValue.type;
|
|
|
|
|
let newUser = new UserEntity()
|
2025-12-15 00:19:55 +08:00
|
|
|
newUser.username = `${userInfo.nickName}_${simpleNanoId(6)}_${oauthType}`;
|
2025-11-27 01:59:22 +08:00
|
|
|
newUser.avatar = userInfo.avatar;
|
2025-11-28 01:42:42 +08:00
|
|
|
newUser.nickName = userInfo.nickName || simpleNanoId(6);
|
2025-12-15 00:23:35 +08:00
|
|
|
newUser.email = userInfo.email || "";
|
2025-11-27 01:59:22 +08:00
|
|
|
|
|
|
|
|
newUser = await this.userService.register("username", newUser, async (txManager) => {
|
2025-11-28 01:42:42 +08:00
|
|
|
const oauthBound: OauthBoundEntity = new OauthBoundEntity()
|
2025-11-27 01:59:22 +08:00
|
|
|
oauthBound.userId = newUser.id;
|
|
|
|
|
oauthBound.type = oauthType;
|
|
|
|
|
oauthBound.openId = userInfo.openId;
|
|
|
|
|
await txManager.save(oauthBound);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const loginRes = await this.loginService.generateToken(newUser);
|
2026-02-27 00:37:24 +08:00
|
|
|
this.writeTokenCookie(loginRes);
|
2025-11-27 01:59:22 +08:00
|
|
|
return this.ok(loginRes);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-29 03:25:21 +08:00
|
|
|
|
|
|
|
|
@Post('/bind', { summary: Constants.per.loginOnly })
|
|
|
|
|
public async bind(@Body(ALL) body: any) {
|
|
|
|
|
//需要已登录
|
|
|
|
|
const userId = this.getUserId();
|
|
|
|
|
const validationValue = this.codeService.getValidationValue(body.validationCode);
|
|
|
|
|
if (!validationValue) {
|
|
|
|
|
throw new Error("校验码错误");
|
|
|
|
|
}
|
|
|
|
|
const type = validationValue.type;
|
|
|
|
|
const userInfo = validationValue.userInfo;
|
|
|
|
|
const openId = userInfo.openId;
|
|
|
|
|
await this.oauthBoundService.bind({
|
|
|
|
|
userId,
|
|
|
|
|
type,
|
|
|
|
|
openId,
|
|
|
|
|
});
|
|
|
|
|
return this.ok(1);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-27 01:59:22 +08:00
|
|
|
@Post('/unbind', { summary: Constants.per.loginOnly })
|
|
|
|
|
public async unbind(@Body(ALL) body: any) {
|
|
|
|
|
//需要已登录
|
|
|
|
|
const userId = this.getUserId();
|
|
|
|
|
await this.oauthBoundService.unbind({
|
|
|
|
|
userId,
|
|
|
|
|
type: body.type,
|
|
|
|
|
});
|
|
|
|
|
return this.ok(1);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-29 03:25:21 +08:00
|
|
|
@Post('/bounds', { summary: Constants.per.loginOnly })
|
|
|
|
|
public async bounds(@Body(ALL) body: any) {
|
|
|
|
|
//需要已登录
|
|
|
|
|
const userId = this.getUserId();
|
|
|
|
|
const bounds = await this.oauthBoundService.find({
|
|
|
|
|
where :{
|
|
|
|
|
userId,
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return this.ok(bounds);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-30 01:13:55 +08:00
|
|
|
|
|
|
|
|
@Post('/providers', { summary: Constants.per.guest })
|
2025-11-27 01:59:22 +08:00
|
|
|
public async providers() {
|
2025-11-30 01:13:55 +08:00
|
|
|
const defineList = addonRegistry.getDefineList("oauth");
|
|
|
|
|
|
|
|
|
|
const publicSetting = await this.sysSettingsService.getPublicSettings();
|
|
|
|
|
const oauthProviders = publicSetting.oauthProviders || {};
|
|
|
|
|
const list = [];
|
|
|
|
|
|
|
|
|
|
for (const item of defineList) {
|
|
|
|
|
const type = item.name
|
|
|
|
|
const conf = oauthProviders[type];
|
|
|
|
|
const provider:any = {
|
|
|
|
|
...item,
|
|
|
|
|
}
|
|
|
|
|
delete provider.input
|
|
|
|
|
if (conf && conf.addonId) {
|
|
|
|
|
const addonEntity = await this.addonService.info(conf.addonId);
|
|
|
|
|
if (addonEntity) {
|
|
|
|
|
provider.addonId = conf.addonId;
|
|
|
|
|
provider.addonTitle = addonEntity.name;
|
2025-12-01 00:40:46 +08:00
|
|
|
|
2026-02-13 23:51:27 +08:00
|
|
|
const addon = await this.addonGetterService.getAddonById(conf.addonId,true,0,null);
|
2025-12-01 00:40:46 +08:00
|
|
|
const {logoutUrl} = await addon.buildLogoutUrl();
|
|
|
|
|
if (logoutUrl){
|
|
|
|
|
provider.logoutUrl = logoutUrl;
|
|
|
|
|
}
|
2025-12-03 00:35:17 +08:00
|
|
|
if(addon.icon){
|
|
|
|
|
provider.icon = addon.icon;
|
|
|
|
|
}
|
2025-11-30 01:13:55 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
list.push(provider);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-27 01:59:22 +08:00
|
|
|
return this.ok(list);
|
|
|
|
|
}
|
|
|
|
|
}
|