loginbase 1.5.0 → 1.6.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.
@@ -37,8 +37,12 @@ function redirectAllowed(redirect, github) {
37
37
  target.pathname.startsWith(base.pathname));
38
38
  });
39
39
  }
40
+ // 回跳地址允许自带 query 与 fragment(redirectAllowed 只比对 protocol/host/pathname),
41
+ // 文本拼接会把参数追加进 fragment、或让已有的同名键遮蔽新值,客户端 searchParams 取不到
40
42
  function withParam(url, key, value) {
41
- return `${url}${url.includes("?") ? "&" : "?"}${key}=${encodeURIComponent(value)}`;
43
+ const target = new URL(url);
44
+ target.searchParams.set(key, value);
45
+ return target.toString();
42
46
  }
43
47
  // providerProfile 只透传建档需要的公开档案白名单——GET /user 的认证视图含
44
48
  // two_factor_authentication、私有仓库统计等敏感字段,不该流出库边界。
@@ -194,10 +198,11 @@ export function registerGithubOauth(auth, getConfig, basePath) {
194
198
  if (!gh)
195
199
  return c.json({ error: "not_configured" }, 404);
196
200
  const code = c.req.query("code") ?? "";
201
+ const providerError = c.req.query("error") ?? "";
197
202
  const state = c.req.query("state") ?? "";
198
203
  const stateKey = `oauth:state:${state}`;
199
204
  const rawState = state ? await cfg(c).kv.get(stateKey) : null;
200
- if (!code || !rawState) {
205
+ if (!rawState) {
201
206
  // state 无效即回跳地址不可信,只能就地报错。
202
207
  // 此分支拿不到 flowId——它就存在读不出来的那条 state 记录里。
203
208
  track(c, {
@@ -217,6 +222,12 @@ export function registerGithubOauth(auth, getConfig, basePath) {
217
222
  ...(userId ? { userId } : {}),
218
223
  meta: { mode: mode ?? "login", ...meta },
219
224
  });
225
+ // GitHub 用 ?error= 回报用户拒绝授权,此时没有 code;state 已验过,回跳地址可信
226
+ if (!code) {
227
+ const reason = REASON_PATTERN.test(providerError) ? providerError : "no_code";
228
+ trackCallback(reason);
229
+ return c.redirect(withParam(redirect, "error", reason), 302);
230
+ }
220
231
  const identity = await fetchGithubIdentity(gh, code);
221
232
  if (!identity) {
222
233
  trackCallback("oauth_failed");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loginbase",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Shared login foundation for Cloudflare Workers: email OTP + social OAuth + session management, as a Hono sub-app factory.",
5
5
  "type": "module",
6
6
  "license": "MIT",