@uzqw/shared 0.1.6 → 0.1.8

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.ts CHANGED
@@ -98,8 +98,18 @@ declare class OAuth {
98
98
  private logtoClientId;
99
99
  /** logto 模式端点校验:缺省抛错(prod 独立 Logto 未配置时明确报错,不静默连测试实例) */
100
100
  private logtoEndpoint;
101
- /** 构造授权请求(PKCE S256)。extraParams 用于本地联调注入 user_id(developer 模式,env=local 才接受)。 */
102
- createAuthorizationRequest(extraParams?: Record<string, string>): AuthorizationRequest;
101
+ /** 登出后「下次登录强制重新认证」标志文件(token 文件旁,按环境/实例隔离)。
102
+ * 解决:logout 只删本地 token,浏览器 Logto 会话仍在 → 下次 login SSO 秒过不弹页。
103
+ * 置标后下次 createAuthorizationRequest 自动带 prompt=login,登录成功(exchangeCode)自动清除。 */
104
+ forceLoginFlagPath(): string;
105
+ /** 是否存在待消费的强制认证标志 */
106
+ isForceLoginPending(): Promise<boolean>;
107
+ private setForceLoginPending;
108
+ private clearForceLoginFlag;
109
+ /** 构造授权请求(PKCE S256)。extraParams 用于本地联调注入 user_id(developer 模式,env=local 才接受)
110
+ * 或 logto 强制重新认证 prompt=login(force 登录换号)。
111
+ * 登出后遗留的强制认证标志也会自动加 prompt=login——用户无需知道 force。 */
112
+ createAuthorizationRequest(extraParams?: Record<string, string>): Promise<AuthorizationRequest>;
103
113
  /** 授权码换凭证并落盘;失败抛 Error(含状态码与响应体)。
104
114
  * developer 模式:直接换 developerOAuth JWT;
105
115
  * logto 模式:先换 Logto access token,再 POST /open/oauth/apikey 换 sk- 长期 key(落盘的是 sk- key)。 */
@@ -110,7 +120,7 @@ declare class OAuth {
110
120
  getAccessToken(): Promise<string | null>;
111
121
  /** 服务端撤销指定 token(RFC 7009:未知 token 也返回成功)。 */
112
122
  revoke(token: string, tokenTypeHint?: string): Promise<void>;
113
- /** 清空本地 token(logout 本地侧)。 */
123
+ /** 本地登出:删 token + 置「下次登录强制重新认证」标志(浏览器 Logto 会话由用户/换号时另处理)。 */
114
124
  logout(): Promise<void>;
115
125
  }
116
126
 
@@ -667,7 +677,13 @@ declare const FileDetailSchema: z.ZodObject<{
667
677
  } | undefined;
668
678
  }>;
669
679
  type RecolxFileDetail = z.infer<typeof FileDetailSchema>;
670
- declare const LoginInput: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
680
+ declare const LoginInput: z.ZodOptional<z.ZodObject<{
681
+ force: z.ZodOptional<z.ZodBoolean>;
682
+ }, "strip", z.ZodTypeAny, {
683
+ force?: boolean | undefined;
684
+ }, {
685
+ force?: boolean | undefined;
686
+ }>>;
671
687
  declare const LogoutInput: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
672
688
  declare const GetCurrentUserInput: z.ZodOptional<z.ZodObject<{}, "strip", z.ZodTypeAny, {}, {}>>;
673
689
  declare const SetEnvInput: z.ZodObject<{
package/dist/index.js CHANGED
@@ -109,6 +109,7 @@ var TOKENS_MCP_PATH = join2(homedir2(), ".recolx", "tokens-mcp.json");
109
109
 
110
110
  // src/oauth.ts
111
111
  import { createHash, randomBytes } from "crypto";
112
+ import { mkdir as mkdir2, readFile as readFile2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
112
113
  function generateCodeVerifier() {
113
114
  return randomBytes(32).toString("base64url");
114
115
  }
@@ -159,8 +160,38 @@ var OAuth = class {
159
160
  }
160
161
  return ep;
161
162
  }
162
- /** 构造授权请求(PKCE S256)。extraParams 用于本地联调注入 user_id(developer 模式,env=local 才接受)。 */
163
- createAuthorizationRequest(extraParams = {}) {
163
+ /** 登出后「下次登录强制重新认证」标志文件(token 文件旁,按环境/实例隔离)。
164
+ * 解决:logout 只删本地 token,浏览器 Logto 会话仍在 → 下次 login SSO 秒过不弹页。
165
+ * 置标后下次 createAuthorizationRequest 自动带 prompt=login,登录成功(exchangeCode)自动清除。 */
166
+ forceLoginFlagPath() {
167
+ return `${this.tokenStore.path}.force`;
168
+ }
169
+ /** 是否存在待消费的强制认证标志 */
170
+ async isForceLoginPending() {
171
+ try {
172
+ await readFile2(this.forceLoginFlagPath(), "utf8");
173
+ return true;
174
+ } catch {
175
+ return false;
176
+ }
177
+ }
178
+ async setForceLoginPending() {
179
+ await mkdir2(this.tokenStore.dir, { recursive: true });
180
+ await writeFile2(this.forceLoginFlagPath(), (/* @__PURE__ */ new Date()).toISOString() + "\n", "utf8");
181
+ }
182
+ async clearForceLoginFlag() {
183
+ try {
184
+ await rm2(this.forceLoginFlagPath());
185
+ } catch {
186
+ }
187
+ }
188
+ /** 构造授权请求(PKCE S256)。extraParams 用于本地联调注入 user_id(developer 模式,env=local 才接受)
189
+ * 或 logto 强制重新认证 prompt=login(force 登录换号)。
190
+ * 登出后遗留的强制认证标志也会自动加 prompt=login——用户无需知道 force。 */
191
+ async createAuthorizationRequest(extraParams = {}) {
192
+ if (!extraParams.prompt && await this.isForceLoginPending()) {
193
+ extraParams = { ...extraParams, prompt: "login" };
194
+ }
164
195
  const codeVerifier = generateCodeVerifier();
165
196
  const state = generateState();
166
197
  if (this.mode === "logto") {
@@ -174,7 +205,8 @@ var OAuth = class {
174
205
  scope: "openid profile email",
175
206
  code_challenge: generateCodeChallenge(codeVerifier),
176
207
  code_challenge_method: "S256",
177
- state
208
+ state,
209
+ ...extraParams
178
210
  });
179
211
  return { url: `${logtoEndpoint}/oidc/auth?${params2.toString()}`, codeVerifier, state };
180
212
  }
@@ -232,6 +264,7 @@ var OAuth = class {
232
264
  }
233
265
  const tokenSet2 = { access_token: apiKey, token_type: "Bearer" };
234
266
  await this.tokenStore.save(tokenSet2);
267
+ await this.clearForceLoginFlag();
235
268
  return tokenSet2;
236
269
  }
237
270
  const res = await this.fetchImpl(this.tokenUrl, {
@@ -244,6 +277,7 @@ var OAuth = class {
244
277
  }
245
278
  const tokenSet = toTokenSet(await res.json());
246
279
  await this.tokenStore.save(tokenSet);
280
+ await this.clearForceLoginFlag();
247
281
  return tokenSet;
248
282
  }
249
283
  /** 用 refresh_token 换新 token(后端滚动轮换);logto 模式无 refresh(sk- key 长期有效),恒返回 null。 */
@@ -286,9 +320,10 @@ var OAuth = class {
286
320
  body: new URLSearchParams(body)
287
321
  });
288
322
  }
289
- /** 清空本地 token(logout 本地侧)。 */
323
+ /** 本地登出:删 token + 置「下次登录强制重新认证」标志(浏览器 Logto 会话由用户/换号时另处理)。 */
290
324
  async logout() {
291
325
  await this.tokenStore.clear();
326
+ await this.setForceLoginPending();
292
327
  }
293
328
  };
294
329
 
@@ -458,7 +493,9 @@ var FileDetailSchema = FileSchema.extend({
458
493
  presigned_url_expires_at: z.number(),
459
494
  transcript: TranscriptSchema.optional()
460
495
  });
461
- var LoginInput = z.object({}).optional();
496
+ var LoginInput = z.object({
497
+ force: z.boolean().optional().describe("true \u65F6\u5F3A\u5236\u91CD\u65B0\u8BA4\u8BC1\uFF1A\u5373\u4FBF\u6D4F\u89C8\u5668\u5DF2\u6709 Logto \u4F1A\u8BDD\uFF08\u767B\u51FA\u540E\u4ECD\u79D2\u8FC7\uFF09\u4E5F\u5F39\u51FA\u767B\u5F55\u9875\uFF0C\u53EF\u5207\u6362\u8D26\u53F7\uFF1B\u7F3A\u7701 false \u590D\u7528\u6D4F\u89C8\u5668\u4F1A\u8BDD")
498
+ }).optional();
462
499
  var LogoutInput = z.object({}).optional();
463
500
  var GetCurrentUserInput = z.object({}).optional();
464
501
  var SetEnvInput = z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uzqw/shared",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Shared types, zod schemas, token store and API client for recolx MCP/CLI",
5
5
  "publishConfig": {
6
6
  "access": "public"