@pylonsync/sync 0.3.133 → 0.3.134
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/package.json +1 -1
- package/src/index.ts +8 -0
- package/src/reconcile.test.ts +57 -0
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -2004,9 +2004,17 @@ export class SyncEngine {
|
|
|
2004
2004
|
undefined;
|
|
2005
2005
|
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
2006
2006
|
|
|
2007
|
+
// credentials: "include" so cookie-auth apps (Yapless and any other
|
|
2008
|
+
// app relying on the `<app>_session` cookie pylon sets at login)
|
|
2009
|
+
// actually authenticate on /api/sync/pull + /api/entities/<E>/cursor.
|
|
2010
|
+
// Without it the pull goes anonymous, every policy default-denies,
|
|
2011
|
+
// and the response is `{changes: []}` even when the same browser
|
|
2012
|
+
// session can read every row via the entity API. Reported in
|
|
2013
|
+
// Repro C against v0.3.131; closed in v0.3.134.
|
|
2007
2014
|
const res = await fetch(`${this.config.baseUrl}${path}`, {
|
|
2008
2015
|
method,
|
|
2009
2016
|
headers,
|
|
2017
|
+
credentials: "include",
|
|
2010
2018
|
body: body ? JSON.stringify(body) : undefined,
|
|
2011
2019
|
});
|
|
2012
2020
|
|
package/src/reconcile.test.ts
CHANGED
|
@@ -294,3 +294,60 @@ describe("LocalStore.entityNames", () => {
|
|
|
294
294
|
expect(names).toEqual(["A", "B"]);
|
|
295
295
|
});
|
|
296
296
|
});
|
|
297
|
+
|
|
298
|
+
describe("SyncEngine cookie auth", () => {
|
|
299
|
+
// Regression test for Repro C (v0.3.131): the SyncEngine's `request`
|
|
300
|
+
// method must send `credentials: "include"` on every HTTP call so
|
|
301
|
+
// cookie-authenticated browser sessions reach the server with their
|
|
302
|
+
// session cookie. Without it, /api/sync/pull and the entity
|
|
303
|
+
// reconcile endpoint go anonymous, the default-deny policy returns
|
|
304
|
+
// nothing, and the local replica stays empty forever — even when
|
|
305
|
+
// the same browser session can read every row via the entity API.
|
|
306
|
+
test("pull request sends cookies (credentials: include)", async () => {
|
|
307
|
+
let capturedInit: RequestInit | undefined;
|
|
308
|
+
const restore = installFetch(async (url, init) => {
|
|
309
|
+
capturedInit = init;
|
|
310
|
+
if (url.includes("/api/sync/pull")) {
|
|
311
|
+
return {
|
|
312
|
+
status: 200,
|
|
313
|
+
body: { changes: [], cursor: { last_seq: 0 }, has_more: false },
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
return { status: 404, body: {} };
|
|
317
|
+
});
|
|
318
|
+
try {
|
|
319
|
+
const engine = makeEngine();
|
|
320
|
+
await engine.pull();
|
|
321
|
+
expect(capturedInit).toBeDefined();
|
|
322
|
+
expect(capturedInit!.credentials).toBe("include");
|
|
323
|
+
} finally {
|
|
324
|
+
restore();
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("reconcile entity fetch sends cookies (credentials: include)", async () => {
|
|
329
|
+
let capturedInit: RequestInit | undefined;
|
|
330
|
+
const restore = installFetch(async (url, init) => {
|
|
331
|
+
if (url.includes("/api/entities/")) {
|
|
332
|
+
capturedInit = init;
|
|
333
|
+
}
|
|
334
|
+
return {
|
|
335
|
+
status: 200,
|
|
336
|
+
body: {
|
|
337
|
+
data: [{ id: "r1", title: "alive" }],
|
|
338
|
+
next_cursor: null,
|
|
339
|
+
has_more: false,
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
});
|
|
343
|
+
try {
|
|
344
|
+
const engine = makeEngine();
|
|
345
|
+
seedStore(engine, "Recording", [{ id: "r1", title: "alive" }]);
|
|
346
|
+
await engine.reconcile(["Recording"]);
|
|
347
|
+
expect(capturedInit).toBeDefined();
|
|
348
|
+
expect(capturedInit!.credentials).toBe("include");
|
|
349
|
+
} finally {
|
|
350
|
+
restore();
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
});
|