@mtreeai/msapling-cli 2.3.6-beta.8 → 2.3.6-beta.9

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.
Files changed (2) hide show
  1. package/dist/index.js +45 -6
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -79,6 +79,11 @@ var init_src = __esm({
79
79
  MSaplingClient = class {
80
80
  apiUrl;
81
81
  token;
82
+ // CLI-AUTH-CSRF-01: simple cookie jar so we can implement the backend's
83
+ // double-submit CSRF pattern (msapling_csrftoken cookie ↔ X-CSRF-Token header).
84
+ // Without this, every authenticated POST/PATCH/DELETE returns 403
85
+ // CSRF_COOKIE_MISSING even with a valid Bearer token.
86
+ cookies = /* @__PURE__ */ new Map();
82
87
  constructor(options = {}) {
83
88
  this.apiUrl = (options.apiUrl || "https://api.msapling.com").replace(/\/$/, "");
84
89
  this.token = options.token || null;
@@ -89,12 +94,38 @@ var init_src = __esm({
89
94
  getApiUrl() {
90
95
  return this.apiUrl;
91
96
  }
97
+ serializeCookies() {
98
+ return Array.from(this.cookies.entries()).map(([k, v]) => `${k}=${v}`).join("; ");
99
+ }
100
+ updateCookiesFromResponse(response) {
101
+ const setCookieArr = response.headers.getSetCookie?.() ?? [];
102
+ for (const line of setCookieArr) {
103
+ const firstSemi = line.indexOf(";");
104
+ const pair = firstSemi >= 0 ? line.slice(0, firstSemi) : line;
105
+ const eq = pair.indexOf("=");
106
+ if (eq <= 0) continue;
107
+ const name = pair.slice(0, eq).trim();
108
+ const value = pair.slice(eq + 1).trim();
109
+ if (!name || !value) continue;
110
+ this.cookies.set(name, value);
111
+ }
112
+ }
92
113
  async request(path2, options = {}) {
93
114
  const headers = new Headers(options.headers);
94
115
  if (this.token) {
95
116
  headers.set("Authorization", `Bearer ${this.token}`);
96
117
  }
97
- headers.set("Content-Type", "application/json");
118
+ if (!headers.has("Content-Type")) {
119
+ headers.set("Content-Type", "application/json");
120
+ }
121
+ if (this.cookies.size > 0) {
122
+ headers.set("Cookie", this.serializeCookies());
123
+ }
124
+ const method = (options.method || "GET").toUpperCase();
125
+ if (method !== "GET" && method !== "HEAD") {
126
+ const csrf = this.cookies.get("msapling_csrftoken");
127
+ if (csrf) headers.set("X-CSRF-Token", csrf);
128
+ }
98
129
  const controller = new AbortController();
99
130
  const timeout = setTimeout(() => controller.abort(), 3e4);
100
131
  try {
@@ -106,6 +137,7 @@ var init_src = __esm({
106
137
  keepalive: true
107
138
  });
108
139
  clearTimeout(timeout);
140
+ this.updateCookiesFromResponse(response);
109
141
  if (!response.ok) {
110
142
  let detail = "Unknown error";
111
143
  let code = "unknown";
@@ -314,6 +346,7 @@ var init_src = __esm({
314
346
  signal: controller.signal
315
347
  });
316
348
  clearTimeout(timeout);
349
+ this.updateCookiesFromResponse(response);
317
350
  if (response.ok) {
318
351
  const data = await response.json();
319
352
  return { kind: "success", token: data.access_token };
@@ -352,6 +385,7 @@ var init_src = __esm({
352
385
  signal: controller.signal
353
386
  });
354
387
  clearTimeout(timeout);
388
+ this.updateCookiesFromResponse(response);
355
389
  if (response.ok) {
356
390
  const data = await response.json();
357
391
  return { kind: "success", token: data.access_token };
@@ -381,16 +415,21 @@ var init_src = __esm({
381
415
  const controller = new AbortController();
382
416
  const timeout = setTimeout(() => controller.abort(), 3e4);
383
417
  try {
418
+ const csrf = this.cookies.get("msapling_csrftoken");
419
+ const verifyHeaders = {
420
+ "Authorization": `Bearer ${this.token}`,
421
+ "Content-Type": "application/json"
422
+ };
423
+ if (this.cookies.size > 0) verifyHeaders["Cookie"] = this.serializeCookies();
424
+ if (csrf) verifyHeaders["X-CSRF-Token"] = csrf;
384
425
  const response = await fetch(`${this.apiUrl}/api/mfa/login-verify`, {
385
426
  method: "POST",
386
- headers: {
387
- "Authorization": `Bearer ${this.token}`,
388
- "Content-Type": "application/json"
389
- },
427
+ headers: verifyHeaders,
390
428
  body: JSON.stringify({ code }),
391
429
  signal: controller.signal
392
430
  });
393
431
  clearTimeout(timeout);
432
+ this.updateCookiesFromResponse(response);
394
433
  if (response.ok) {
395
434
  const data = await response.json();
396
435
  return { kind: "success", token: data.access_token };
@@ -13134,7 +13173,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
13134
13173
  var Header = () => /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, marginBottom: 1, children: [
13135
13174
  /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
13136
13175
  "\u25CF MSapling CLI v",
13137
- "2.3.6-beta.8"
13176
+ "2.3.6-beta.9"
13138
13177
  ] }),
13139
13178
  /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Platinum Tier Architecture" }) })
13140
13179
  ] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mtreeai/msapling-cli",
3
- "version": "2.3.6-beta.8",
3
+ "version": "2.3.6-beta.9",
4
4
  "description": "MSapling CLI — React/Ink terminal client for the MSapling backend (chat, projects, MDrive, agent tools). Proprietary; redistribution prohibited.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "MSapling Team",