perf: 支持微信扫码登录

This commit is contained in:
xiaojunnuo
2025-11-30 01:13:55 +08:00
parent 0adcc6a8d1
commit 73325aaefb
14 changed files with 237 additions and 41 deletions
@@ -37,7 +37,13 @@ export type LoginUrlReply = {
ticketValue: any;
}
export type BuildLoginUrlReq = {
redirectUri: string;
forType?: string;
from?:string;
}
export interface IOauthProvider {
buildLoginUrl: (params: { redirectUri: string, forType?: string }) => Promise<LoginUrlReply>;
buildLoginUrl: (params: BuildLoginUrlReq) => Promise<LoginUrlReply>;
onCallback: (params: OnCallbackReq) => Promise<OauthToken>;
}
@@ -1,2 +1,3 @@
export * from './api.js'
export * from './oidc/plugin-oidc.js'
export * from './wx/plugin-wx.js'
@@ -1,5 +1,5 @@
import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server";
import { IOauthProvider, OnCallbackReq } from "../api.js";
import { BuildLoginUrlReq, IOauthProvider, OnCallbackReq } from "../api.js";
@IsAddon({
addonType: "oauth",
@@ -56,7 +56,7 @@ export class OidcOauthProvider extends BaseAddon implements IOauthProvider {
}
}
async buildLoginUrl(params: { redirectUri: string, forType?: string }) {
async buildLoginUrl(params: BuildLoginUrlReq) {
const { config, client } = await this.getClient()
let redirect_uri = new URL(params.redirectUri)
@@ -0,0 +1,128 @@
import { AddonInput, BaseAddon, IsAddon } from "@certd/lib-server";
import { BuildLoginUrlReq, IOauthProvider, OnCallbackReq } from "../api.js";
@IsAddon({
addonType: "oauth",
name: 'wx',
title: '微信登录',
desc: '微信网站应用登录',
icon: "mdi:wechat",
showTest: false,
})
export class WxOauthProvider extends BaseAddon implements IOauthProvider {
@AddonInput({
title: "AppId",
required: true,
helper: "在[微信开放平台](https://open.weixin.qq.com/cgi-bin/index)注册网站应用后获取",
})
appId = "";
@AddonInput({
title: "AppSecretKey",
component: {
placeholder: "AppSecretKey",
},
required: true,
})
appSecretKey = "";
wxAccessToken?: { access_token: string, expires_at: number }
async buildLoginUrl(params: BuildLoginUrlReq) {
const from = params.from || "web";
const appId = this.appId;
const redirect_uri = encodeURIComponent(params.redirectUri);
let state: any = {
forType: params.forType,
from
}
state = this.ctx.utils.hash.base64(JSON.stringify(state))
let scope = "snsapi_userinfo";
let loginUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirect_uri}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`
if (from === "web") {
scope = "snsapi_login";
loginUrl = `https://open.weixin.qq.com/connect/qrconnect?appid=${appId}&redirect_uri=${redirect_uri}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`
}
return {
loginUrl,
ticketValue: {
state,
},
};
}
// async getWxAccessToken() {
// if (this.wxAccessToken && this.wxAccessToken.expires_at > Date.now()) {
// return this.wxAccessToken
// }
// const res = await this.http.request({
// url: "https://api.weixin.qq.com/cgi-bin/token",
// method: "GET",
// params: {
// appid: this.appId,
// secret: this.appSecretKey,
// grant_type: "client_credential",
// },
// })
// this.checkRet(res)
// this.wxAccessToken = {
// access_token: res.access_token,
// expires_at: Date.now() + res.expires_in * 1000,
// }
// return this.wxAccessToken
// }
checkRet(res: any) {
if (res.errcode) {
throw new Error(res.errmsg)
}
}
async onCallback(req: OnCallbackReq) {
// GET https://api.weixin.qq.com/sns/oauth2/access_token?appid=wx520c15f417810387&secret=SECRET&code=CODE&grant_type=authorization_code
const res = await this.http.request({
url: "https://api.weixin.qq.com/sns/oauth2/access_token",
method: "GET",
params: {
appid: this.appId,
secret: this.appSecretKey,
code: req.code,
grant_type: "authorization_code",
},
})
this.checkRet(res)
const accessToken = res.access_token
// GET https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
const userInfoRes = await this.http.request({
url: "https://api.weixin.qq.com/sns/userinfo",
method: "GET",
params: {
access_token:accessToken,
openid: res.openid,
lang: "zh_CN",
},
})
this.checkRet(userInfoRes)
return {
token: {
accessToken: res.access_token,
refreshToken: res.refresh_token,
expiresIn: res.expires_in,
},
userInfo: {
openId: res.unionid || res.openid,
nickName: userInfoRes.nickname || "",
avatar: userInfoRes.headimgurl,
},
}
};
}