@nvwa-app/auth-plugins 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +40 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +252 -0
- package/dist/index.mjs +224 -0
- package/package.json +52 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { BetterAuthPlugin } from 'better-auth';
|
|
2
|
+
|
|
3
|
+
interface WechatMiniProgramDirectOptions {
|
|
4
|
+
/** 小程序自身的 AppID(直连 jscode2session) */
|
|
5
|
+
appId: string;
|
|
6
|
+
/** 小程序自身的 AppSecret */
|
|
7
|
+
appSecret: string;
|
|
8
|
+
/** better-auth account.providerId,默认 "wechat-mp" */
|
|
9
|
+
providerId?: string;
|
|
10
|
+
}
|
|
11
|
+
declare function wechatMiniProgramDirectPlugin(options: WechatMiniProgramDirectOptions): BetterAuthPlugin;
|
|
12
|
+
|
|
13
|
+
interface WechatMiniProgramThirdPartyOptions {
|
|
14
|
+
/** 第三方平台 component_appid */
|
|
15
|
+
componentAppId: string;
|
|
16
|
+
/** 第三方平台 component_appsecret */
|
|
17
|
+
componentAppSecret: string;
|
|
18
|
+
/**
|
|
19
|
+
* 获取最新的 component_verify_ticket。
|
|
20
|
+
* 调用方负责从自身存储中返回有效 ticket。
|
|
21
|
+
*/
|
|
22
|
+
getComponentVerifyTicket: () => Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* 读取已缓存的 component_access_token。
|
|
25
|
+
* 未命中时应返回 null。
|
|
26
|
+
*/
|
|
27
|
+
getCachedComponentAccessToken?: () => Promise<string | null>;
|
|
28
|
+
/**
|
|
29
|
+
* 写入 component_access_token 缓存。
|
|
30
|
+
* 由调用方决定存储位置及失效策略。
|
|
31
|
+
*/
|
|
32
|
+
setCachedComponentAccessToken?: (token: string, expiresIn: number) => Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* better-auth account.providerId,默认 "wechat-mp"
|
|
35
|
+
*/
|
|
36
|
+
providerId?: string;
|
|
37
|
+
}
|
|
38
|
+
declare function wechatMiniProgramThirdPartyPlugin(options: WechatMiniProgramThirdPartyOptions): BetterAuthPlugin;
|
|
39
|
+
|
|
40
|
+
export { type WechatMiniProgramDirectOptions, type WechatMiniProgramThirdPartyOptions, wechatMiniProgramDirectPlugin, wechatMiniProgramThirdPartyPlugin };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { BetterAuthPlugin } from 'better-auth';
|
|
2
|
+
|
|
3
|
+
interface WechatMiniProgramDirectOptions {
|
|
4
|
+
/** 小程序自身的 AppID(直连 jscode2session) */
|
|
5
|
+
appId: string;
|
|
6
|
+
/** 小程序自身的 AppSecret */
|
|
7
|
+
appSecret: string;
|
|
8
|
+
/** better-auth account.providerId,默认 "wechat-mp" */
|
|
9
|
+
providerId?: string;
|
|
10
|
+
}
|
|
11
|
+
declare function wechatMiniProgramDirectPlugin(options: WechatMiniProgramDirectOptions): BetterAuthPlugin;
|
|
12
|
+
|
|
13
|
+
interface WechatMiniProgramThirdPartyOptions {
|
|
14
|
+
/** 第三方平台 component_appid */
|
|
15
|
+
componentAppId: string;
|
|
16
|
+
/** 第三方平台 component_appsecret */
|
|
17
|
+
componentAppSecret: string;
|
|
18
|
+
/**
|
|
19
|
+
* 获取最新的 component_verify_ticket。
|
|
20
|
+
* 调用方负责从自身存储中返回有效 ticket。
|
|
21
|
+
*/
|
|
22
|
+
getComponentVerifyTicket: () => Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* 读取已缓存的 component_access_token。
|
|
25
|
+
* 未命中时应返回 null。
|
|
26
|
+
*/
|
|
27
|
+
getCachedComponentAccessToken?: () => Promise<string | null>;
|
|
28
|
+
/**
|
|
29
|
+
* 写入 component_access_token 缓存。
|
|
30
|
+
* 由调用方决定存储位置及失效策略。
|
|
31
|
+
*/
|
|
32
|
+
setCachedComponentAccessToken?: (token: string, expiresIn: number) => Promise<void>;
|
|
33
|
+
/**
|
|
34
|
+
* better-auth account.providerId,默认 "wechat-mp"
|
|
35
|
+
*/
|
|
36
|
+
providerId?: string;
|
|
37
|
+
}
|
|
38
|
+
declare function wechatMiniProgramThirdPartyPlugin(options: WechatMiniProgramThirdPartyOptions): BetterAuthPlugin;
|
|
39
|
+
|
|
40
|
+
export { type WechatMiniProgramDirectOptions, type WechatMiniProgramThirdPartyOptions, wechatMiniProgramDirectPlugin, wechatMiniProgramThirdPartyPlugin };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
wechatMiniProgramDirectPlugin: () => wechatMiniProgramDirectPlugin,
|
|
24
|
+
wechatMiniProgramThirdPartyPlugin: () => wechatMiniProgramThirdPartyPlugin
|
|
25
|
+
});
|
|
26
|
+
module.exports = __toCommonJS(index_exports);
|
|
27
|
+
|
|
28
|
+
// src/wechat-mini-program-direct.ts
|
|
29
|
+
var import_api2 = require("better-auth/api");
|
|
30
|
+
|
|
31
|
+
// src/wechat-mini-program-shared.ts
|
|
32
|
+
var import_api = require("better-auth/api");
|
|
33
|
+
async function ensureUserAndSession(ctx, providerId, openid) {
|
|
34
|
+
const { adapter, internalAdapter, logger } = ctx.context;
|
|
35
|
+
logger?.info?.("[wechat-mini-program] code2session success", {
|
|
36
|
+
openid: openid.substring(0, 10)
|
|
37
|
+
});
|
|
38
|
+
const accountTable = ctx.context.tables.account;
|
|
39
|
+
const userTable = ctx.context.tables.user;
|
|
40
|
+
const providerIdCol = accountTable.fields.providerId.name;
|
|
41
|
+
const accountIdCol = accountTable.fields.accountId.name;
|
|
42
|
+
const userIdCol = accountTable.fields.userId.name;
|
|
43
|
+
const existingAccounts = await adapter.selectFrom(accountTable.name).selectAll().where(providerIdCol, "=", providerId).where(accountIdCol, "=", openid).execute();
|
|
44
|
+
let userId;
|
|
45
|
+
if (existingAccounts.length > 0) {
|
|
46
|
+
userId = existingAccounts[0][userIdCol];
|
|
47
|
+
} else {
|
|
48
|
+
const email = `${openid}@wechat-mp.nvwa.app`;
|
|
49
|
+
const name = "\u5FAE\u4FE1\u7528\u6237";
|
|
50
|
+
const newUserId = crypto.randomUUID();
|
|
51
|
+
const idCol = userTable.fields.id.name;
|
|
52
|
+
const emailCol = userTable.fields.email.name;
|
|
53
|
+
const nameCol = userTable.fields.name.name;
|
|
54
|
+
await adapter.insertInto(userTable.name).values({
|
|
55
|
+
[idCol]: newUserId,
|
|
56
|
+
[emailCol]: email,
|
|
57
|
+
[nameCol]: name
|
|
58
|
+
}).execute();
|
|
59
|
+
userId = newUserId;
|
|
60
|
+
await adapter.insertInto(accountTable.name).values({
|
|
61
|
+
[accountTable.fields.id.name]: crypto.randomUUID(),
|
|
62
|
+
[accountTable.fields.userId.name]: userId,
|
|
63
|
+
[accountTable.fields.providerId.name]: providerId,
|
|
64
|
+
[accountTable.fields.accountId.name]: openid
|
|
65
|
+
}).execute();
|
|
66
|
+
}
|
|
67
|
+
const session = await internalAdapter.createSession({
|
|
68
|
+
userId,
|
|
69
|
+
ipAddress: ctx.request.ip ?? void 0,
|
|
70
|
+
userAgent: ctx.request.headers.get("user-agent") ?? void 0
|
|
71
|
+
});
|
|
72
|
+
return { userId, sessionToken: session.token };
|
|
73
|
+
}
|
|
74
|
+
function setSessionCookie(ctx, token) {
|
|
75
|
+
const cookie = ctx.context.createAuthCookie("sessionToken", {
|
|
76
|
+
value: token,
|
|
77
|
+
expiresIn: ctx.context.session.expiresIn
|
|
78
|
+
});
|
|
79
|
+
ctx.setCookie(cookie.name, cookie.value, cookie.options);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// src/wechat-mini-program-direct.ts
|
|
83
|
+
async function code2SessionDirect(opts, code) {
|
|
84
|
+
const url = new URL("https://api.weixin.qq.com/sns/jscode2session");
|
|
85
|
+
url.searchParams.set("appid", opts.appId);
|
|
86
|
+
url.searchParams.set("secret", opts.appSecret);
|
|
87
|
+
url.searchParams.set("js_code", code);
|
|
88
|
+
url.searchParams.set("grant_type", "authorization_code");
|
|
89
|
+
const response = await fetch(url.toString(), { method: "GET" });
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`Failed to call jscode2session: ${response.status} ${response.statusText}`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
const json = await response.json();
|
|
96
|
+
if (!json.openid) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`jscode2session error: ${json.errcode ?? ""} ${json.errmsg ?? "unknown error"}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
openid: json.openid,
|
|
103
|
+
unionid: json.unionid,
|
|
104
|
+
session_key: json.session_key
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function wechatMiniProgramDirectPlugin(options) {
|
|
108
|
+
const providerId = options.providerId ?? "wechat-mp";
|
|
109
|
+
return {
|
|
110
|
+
id: "wechat-mini-program-direct",
|
|
111
|
+
endpoints: {
|
|
112
|
+
miniProgramLogin: (0, import_api2.createAuthEndpoint)(
|
|
113
|
+
"/wechat-mp/login",
|
|
114
|
+
{
|
|
115
|
+
method: "POST"
|
|
116
|
+
},
|
|
117
|
+
async (ctx) => {
|
|
118
|
+
if (!ctx.request) throw new import_api.APIError("BAD_REQUEST", { message: "Missing request" });
|
|
119
|
+
const body = await ctx.request.json().catch(() => null);
|
|
120
|
+
const code = body?.code;
|
|
121
|
+
if (!code) {
|
|
122
|
+
throw new import_api.APIError("BAD_REQUEST", {
|
|
123
|
+
message: "code is required"
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
const sessionData = await code2SessionDirect(options, code);
|
|
127
|
+
const { userId, sessionToken } = await ensureUserAndSession(
|
|
128
|
+
ctx,
|
|
129
|
+
providerId,
|
|
130
|
+
sessionData.openid
|
|
131
|
+
);
|
|
132
|
+
setSessionCookie(ctx, sessionToken);
|
|
133
|
+
return ctx.json({
|
|
134
|
+
success: true,
|
|
135
|
+
userId
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/wechat-mini-program-thirdparty.ts
|
|
144
|
+
var import_api3 = require("better-auth/api");
|
|
145
|
+
async function fetchComponentAccessToken(opts) {
|
|
146
|
+
const cached = await opts.getCachedComponentAccessToken?.().catch(() => null) ?? null;
|
|
147
|
+
if (cached) return cached;
|
|
148
|
+
const ticket = await opts.getComponentVerifyTicket();
|
|
149
|
+
const response = await fetch(
|
|
150
|
+
"https://api.weixin.qq.com/cgi-bin/component/api_component_token",
|
|
151
|
+
{
|
|
152
|
+
method: "POST",
|
|
153
|
+
headers: {
|
|
154
|
+
"Content-Type": "application/json"
|
|
155
|
+
},
|
|
156
|
+
body: JSON.stringify({
|
|
157
|
+
component_appid: opts.componentAppId,
|
|
158
|
+
component_appsecret: opts.componentAppSecret,
|
|
159
|
+
component_verify_ticket: ticket
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
if (!response.ok) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Failed to get component_access_token: ${response.status} ${response.statusText}`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
const json = await response.json();
|
|
169
|
+
if (!json.component_access_token) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
`Get component_access_token error: ${json.errcode ?? ""} ${json.errmsg ?? "unknown error"}`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
if (opts.setCachedComponentAccessToken && json.expires_in) {
|
|
175
|
+
await opts.setCachedComponentAccessToken(
|
|
176
|
+
json.component_access_token,
|
|
177
|
+
json.expires_in
|
|
178
|
+
).catch(() => {
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
return json.component_access_token;
|
|
182
|
+
}
|
|
183
|
+
async function code2Session(opts, code, appId) {
|
|
184
|
+
const componentAccessToken = await fetchComponentAccessToken(opts);
|
|
185
|
+
const miniAppId = appId ?? opts.componentAppId;
|
|
186
|
+
const url = new URL(
|
|
187
|
+
"https://api.weixin.qq.com/sns/component/jscode2session"
|
|
188
|
+
);
|
|
189
|
+
url.searchParams.set("appid", miniAppId);
|
|
190
|
+
url.searchParams.set("js_code", code);
|
|
191
|
+
url.searchParams.set("grant_type", "authorization_code");
|
|
192
|
+
url.searchParams.set("component_appid", opts.componentAppId);
|
|
193
|
+
url.searchParams.set("component_access_token", componentAccessToken);
|
|
194
|
+
const response = await fetch(url.toString(), { method: "GET" });
|
|
195
|
+
if (!response.ok) {
|
|
196
|
+
throw new Error(
|
|
197
|
+
`Failed to call jscode2session: ${response.status} ${response.statusText}`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
const json = await response.json();
|
|
201
|
+
if (!json.openid) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`jscode2session error: ${json.errcode ?? ""} ${json.errmsg ?? "unknown error"}`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
openid: json.openid,
|
|
208
|
+
unionid: json.unionid,
|
|
209
|
+
session_key: json.session_key
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
function wechatMiniProgramThirdPartyPlugin(options) {
|
|
213
|
+
const providerId = options.providerId ?? "wechat-mp";
|
|
214
|
+
return {
|
|
215
|
+
id: "wechat-mini-program-thirdparty",
|
|
216
|
+
endpoints: {
|
|
217
|
+
miniProgramThirdpartyLogin: (0, import_api3.createAuthEndpoint)(
|
|
218
|
+
"/wechat-mp/thirdparty-login",
|
|
219
|
+
{
|
|
220
|
+
method: "POST"
|
|
221
|
+
},
|
|
222
|
+
async (ctx) => {
|
|
223
|
+
if (!ctx.request) throw new import_api.APIError("BAD_REQUEST", { message: "Missing request" });
|
|
224
|
+
const body = await ctx.request.json().catch(() => null);
|
|
225
|
+
const code = body?.code;
|
|
226
|
+
const appId = body?.appId;
|
|
227
|
+
if (!code) {
|
|
228
|
+
throw new import_api.APIError("BAD_REQUEST", {
|
|
229
|
+
message: "code is required"
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
const sessionData = await code2Session(options, code, appId);
|
|
233
|
+
const { userId, sessionToken } = await ensureUserAndSession(
|
|
234
|
+
ctx,
|
|
235
|
+
providerId,
|
|
236
|
+
sessionData.openid
|
|
237
|
+
);
|
|
238
|
+
setSessionCookie(ctx, sessionToken);
|
|
239
|
+
return ctx.json({
|
|
240
|
+
success: true,
|
|
241
|
+
userId
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
249
|
+
0 && (module.exports = {
|
|
250
|
+
wechatMiniProgramDirectPlugin,
|
|
251
|
+
wechatMiniProgramThirdPartyPlugin
|
|
252
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// src/wechat-mini-program-direct.ts
|
|
2
|
+
import { createAuthEndpoint } from "better-auth/api";
|
|
3
|
+
|
|
4
|
+
// src/wechat-mini-program-shared.ts
|
|
5
|
+
import { APIError } from "better-auth/api";
|
|
6
|
+
async function ensureUserAndSession(ctx, providerId, openid) {
|
|
7
|
+
const { adapter, internalAdapter, logger } = ctx.context;
|
|
8
|
+
logger?.info?.("[wechat-mini-program] code2session success", {
|
|
9
|
+
openid: openid.substring(0, 10)
|
|
10
|
+
});
|
|
11
|
+
const accountTable = ctx.context.tables.account;
|
|
12
|
+
const userTable = ctx.context.tables.user;
|
|
13
|
+
const providerIdCol = accountTable.fields.providerId.name;
|
|
14
|
+
const accountIdCol = accountTable.fields.accountId.name;
|
|
15
|
+
const userIdCol = accountTable.fields.userId.name;
|
|
16
|
+
const existingAccounts = await adapter.selectFrom(accountTable.name).selectAll().where(providerIdCol, "=", providerId).where(accountIdCol, "=", openid).execute();
|
|
17
|
+
let userId;
|
|
18
|
+
if (existingAccounts.length > 0) {
|
|
19
|
+
userId = existingAccounts[0][userIdCol];
|
|
20
|
+
} else {
|
|
21
|
+
const email = `${openid}@wechat-mp.nvwa.app`;
|
|
22
|
+
const name = "\u5FAE\u4FE1\u7528\u6237";
|
|
23
|
+
const newUserId = crypto.randomUUID();
|
|
24
|
+
const idCol = userTable.fields.id.name;
|
|
25
|
+
const emailCol = userTable.fields.email.name;
|
|
26
|
+
const nameCol = userTable.fields.name.name;
|
|
27
|
+
await adapter.insertInto(userTable.name).values({
|
|
28
|
+
[idCol]: newUserId,
|
|
29
|
+
[emailCol]: email,
|
|
30
|
+
[nameCol]: name
|
|
31
|
+
}).execute();
|
|
32
|
+
userId = newUserId;
|
|
33
|
+
await adapter.insertInto(accountTable.name).values({
|
|
34
|
+
[accountTable.fields.id.name]: crypto.randomUUID(),
|
|
35
|
+
[accountTable.fields.userId.name]: userId,
|
|
36
|
+
[accountTable.fields.providerId.name]: providerId,
|
|
37
|
+
[accountTable.fields.accountId.name]: openid
|
|
38
|
+
}).execute();
|
|
39
|
+
}
|
|
40
|
+
const session = await internalAdapter.createSession({
|
|
41
|
+
userId,
|
|
42
|
+
ipAddress: ctx.request.ip ?? void 0,
|
|
43
|
+
userAgent: ctx.request.headers.get("user-agent") ?? void 0
|
|
44
|
+
});
|
|
45
|
+
return { userId, sessionToken: session.token };
|
|
46
|
+
}
|
|
47
|
+
function setSessionCookie(ctx, token) {
|
|
48
|
+
const cookie = ctx.context.createAuthCookie("sessionToken", {
|
|
49
|
+
value: token,
|
|
50
|
+
expiresIn: ctx.context.session.expiresIn
|
|
51
|
+
});
|
|
52
|
+
ctx.setCookie(cookie.name, cookie.value, cookie.options);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// src/wechat-mini-program-direct.ts
|
|
56
|
+
async function code2SessionDirect(opts, code) {
|
|
57
|
+
const url = new URL("https://api.weixin.qq.com/sns/jscode2session");
|
|
58
|
+
url.searchParams.set("appid", opts.appId);
|
|
59
|
+
url.searchParams.set("secret", opts.appSecret);
|
|
60
|
+
url.searchParams.set("js_code", code);
|
|
61
|
+
url.searchParams.set("grant_type", "authorization_code");
|
|
62
|
+
const response = await fetch(url.toString(), { method: "GET" });
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Failed to call jscode2session: ${response.status} ${response.statusText}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
const json = await response.json();
|
|
69
|
+
if (!json.openid) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`jscode2session error: ${json.errcode ?? ""} ${json.errmsg ?? "unknown error"}`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
openid: json.openid,
|
|
76
|
+
unionid: json.unionid,
|
|
77
|
+
session_key: json.session_key
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function wechatMiniProgramDirectPlugin(options) {
|
|
81
|
+
const providerId = options.providerId ?? "wechat-mp";
|
|
82
|
+
return {
|
|
83
|
+
id: "wechat-mini-program-direct",
|
|
84
|
+
endpoints: {
|
|
85
|
+
miniProgramLogin: createAuthEndpoint(
|
|
86
|
+
"/wechat-mp/login",
|
|
87
|
+
{
|
|
88
|
+
method: "POST"
|
|
89
|
+
},
|
|
90
|
+
async (ctx) => {
|
|
91
|
+
if (!ctx.request) throw new APIError("BAD_REQUEST", { message: "Missing request" });
|
|
92
|
+
const body = await ctx.request.json().catch(() => null);
|
|
93
|
+
const code = body?.code;
|
|
94
|
+
if (!code) {
|
|
95
|
+
throw new APIError("BAD_REQUEST", {
|
|
96
|
+
message: "code is required"
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const sessionData = await code2SessionDirect(options, code);
|
|
100
|
+
const { userId, sessionToken } = await ensureUserAndSession(
|
|
101
|
+
ctx,
|
|
102
|
+
providerId,
|
|
103
|
+
sessionData.openid
|
|
104
|
+
);
|
|
105
|
+
setSessionCookie(ctx, sessionToken);
|
|
106
|
+
return ctx.json({
|
|
107
|
+
success: true,
|
|
108
|
+
userId
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/wechat-mini-program-thirdparty.ts
|
|
117
|
+
import { createAuthEndpoint as createAuthEndpoint2 } from "better-auth/api";
|
|
118
|
+
async function fetchComponentAccessToken(opts) {
|
|
119
|
+
const cached = await opts.getCachedComponentAccessToken?.().catch(() => null) ?? null;
|
|
120
|
+
if (cached) return cached;
|
|
121
|
+
const ticket = await opts.getComponentVerifyTicket();
|
|
122
|
+
const response = await fetch(
|
|
123
|
+
"https://api.weixin.qq.com/cgi-bin/component/api_component_token",
|
|
124
|
+
{
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: {
|
|
127
|
+
"Content-Type": "application/json"
|
|
128
|
+
},
|
|
129
|
+
body: JSON.stringify({
|
|
130
|
+
component_appid: opts.componentAppId,
|
|
131
|
+
component_appsecret: opts.componentAppSecret,
|
|
132
|
+
component_verify_ticket: ticket
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Failed to get component_access_token: ${response.status} ${response.statusText}`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
const json = await response.json();
|
|
142
|
+
if (!json.component_access_token) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Get component_access_token error: ${json.errcode ?? ""} ${json.errmsg ?? "unknown error"}`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
if (opts.setCachedComponentAccessToken && json.expires_in) {
|
|
148
|
+
await opts.setCachedComponentAccessToken(
|
|
149
|
+
json.component_access_token,
|
|
150
|
+
json.expires_in
|
|
151
|
+
).catch(() => {
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return json.component_access_token;
|
|
155
|
+
}
|
|
156
|
+
async function code2Session(opts, code, appId) {
|
|
157
|
+
const componentAccessToken = await fetchComponentAccessToken(opts);
|
|
158
|
+
const miniAppId = appId ?? opts.componentAppId;
|
|
159
|
+
const url = new URL(
|
|
160
|
+
"https://api.weixin.qq.com/sns/component/jscode2session"
|
|
161
|
+
);
|
|
162
|
+
url.searchParams.set("appid", miniAppId);
|
|
163
|
+
url.searchParams.set("js_code", code);
|
|
164
|
+
url.searchParams.set("grant_type", "authorization_code");
|
|
165
|
+
url.searchParams.set("component_appid", opts.componentAppId);
|
|
166
|
+
url.searchParams.set("component_access_token", componentAccessToken);
|
|
167
|
+
const response = await fetch(url.toString(), { method: "GET" });
|
|
168
|
+
if (!response.ok) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Failed to call jscode2session: ${response.status} ${response.statusText}`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const json = await response.json();
|
|
174
|
+
if (!json.openid) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`jscode2session error: ${json.errcode ?? ""} ${json.errmsg ?? "unknown error"}`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
openid: json.openid,
|
|
181
|
+
unionid: json.unionid,
|
|
182
|
+
session_key: json.session_key
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function wechatMiniProgramThirdPartyPlugin(options) {
|
|
186
|
+
const providerId = options.providerId ?? "wechat-mp";
|
|
187
|
+
return {
|
|
188
|
+
id: "wechat-mini-program-thirdparty",
|
|
189
|
+
endpoints: {
|
|
190
|
+
miniProgramThirdpartyLogin: createAuthEndpoint2(
|
|
191
|
+
"/wechat-mp/thirdparty-login",
|
|
192
|
+
{
|
|
193
|
+
method: "POST"
|
|
194
|
+
},
|
|
195
|
+
async (ctx) => {
|
|
196
|
+
if (!ctx.request) throw new APIError("BAD_REQUEST", { message: "Missing request" });
|
|
197
|
+
const body = await ctx.request.json().catch(() => null);
|
|
198
|
+
const code = body?.code;
|
|
199
|
+
const appId = body?.appId;
|
|
200
|
+
if (!code) {
|
|
201
|
+
throw new APIError("BAD_REQUEST", {
|
|
202
|
+
message: "code is required"
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
const sessionData = await code2Session(options, code, appId);
|
|
206
|
+
const { userId, sessionToken } = await ensureUserAndSession(
|
|
207
|
+
ctx,
|
|
208
|
+
providerId,
|
|
209
|
+
sessionData.openid
|
|
210
|
+
);
|
|
211
|
+
setSessionCookie(ctx, sessionToken);
|
|
212
|
+
return ctx.json({
|
|
213
|
+
success: true,
|
|
214
|
+
userId
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
export {
|
|
222
|
+
wechatMiniProgramDirectPlugin,
|
|
223
|
+
wechatMiniProgramThirdPartyPlugin
|
|
224
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nvwa-app/auth-plugins",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Better-auth plugins for WeChat mini-program login (direct and third-party).",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup",
|
|
20
|
+
"dev": "tsup --watch",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"better-auth": ">=1.4.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"better-auth": "1.4.4",
|
|
31
|
+
"tsup": "^8.1.0",
|
|
32
|
+
"typescript": "^5.6.0"
|
|
33
|
+
},
|
|
34
|
+
"tsup": {
|
|
35
|
+
"entry": ["src/index.ts"],
|
|
36
|
+
"outDir": "dist",
|
|
37
|
+
"clean": true,
|
|
38
|
+
"dts": true,
|
|
39
|
+
"format": ["esm", "cjs"],
|
|
40
|
+
"minify": false,
|
|
41
|
+
"splitting": false,
|
|
42
|
+
"external": ["better-auth", "better-auth/api"]
|
|
43
|
+
},
|
|
44
|
+
"keywords": [
|
|
45
|
+
"better-auth",
|
|
46
|
+
"wechat",
|
|
47
|
+
"miniprogram",
|
|
48
|
+
"auth",
|
|
49
|
+
"plugin"
|
|
50
|
+
],
|
|
51
|
+
"license": "ISC"
|
|
52
|
+
}
|