@bytescale/sdk 3.56.0 → 3.58.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.
@@ -0,0 +1,276 @@
1
+ import { jest } from "@jest/globals";
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { runInNewContext } from "node:vm";
5
+ import { Headers as NodeFetchHeaders, Request as NodeFetchRequest, Response as NodeFetchResponse } from "node-fetch";
6
+ import type { RequestInit as NodeFetchRequestInit } from "node-fetch";
7
+ import type { AuthSwConfigEntryDto, UrlRewriteRule } from "../src/index.browser";
8
+
9
+ const workerSource = readFileSync(resolve(process.cwd(), "src/index.auth-sw.js"), "utf8");
10
+
11
+ describe("Auth service-worker URL rewriting", () => {
12
+ test("preserves the remaining path, query string, and fragment", async () => {
13
+ const harness = new AuthServiceWorkerHarness();
14
+ const rules = [rewriteRule("download/", "account-a/")];
15
+ await harness.setConfig([], rules);
16
+
17
+ const result = await harness.dispatchFetch(
18
+ "https://dashboard.example.com/download/path/to/file.pdf?download=true&version=2"
19
+ );
20
+
21
+ expect(result.outboundRequest?.url).toBe("https://upcdn.io/account-a/path/to/file.pdf?download=true&version=2");
22
+ expect(harness.getRewrittenUrl("https://dashboard.example.com/download/file.pdf?download=true#page=2", rules)).toBe(
23
+ "https://upcdn.io/account-a/file.pdf?download=true#page=2"
24
+ );
25
+ });
26
+
27
+ test("matches authentication against the rewritten URL and supports navigation requests", async () => {
28
+ const upstreamResponse = new NodeFetchResponse("streamed-body", {
29
+ headers: {
30
+ "Accept-Ranges": "bytes",
31
+ "Content-Disposition": 'attachment; filename="file.pdf"',
32
+ "Content-Length": "13",
33
+ "Content-Range": "bytes 0-12/13",
34
+ "Content-Type": "application/pdf"
35
+ },
36
+ status: 206
37
+ });
38
+ const blob = jest.spyOn(upstreamResponse, "blob");
39
+ const arrayBuffer = jest.spyOn(upstreamResponse, "arrayBuffer");
40
+ const harness = new AuthServiceWorkerHarness(upstreamResponse);
41
+ await harness.setConfig([authConfig("account-a/", "token-a")], [rewriteRule("download/", "account-a/")]);
42
+
43
+ const result = await harness.dispatchFetch("https://dashboard.example.com/download/file.pdf", {
44
+ headers: {
45
+ "If-Modified-Since": "Wed, 21 Oct 2015 07:28:00 GMT",
46
+ "If-None-Match": '"etag"',
47
+ "If-Range": '"range-etag"',
48
+ "Range": "bytes=0-12"
49
+ },
50
+ navigation: true
51
+ });
52
+
53
+ expect(result.outboundRequest?.url).toBe("https://upcdn.io/account-a/file.pdf");
54
+ expect(result.outboundRequest?.mode).toBe("cors");
55
+ expect(result.outboundRequest?.headers.get("Authorization")).toBe("Bearer token-a");
56
+ expect(result.outboundRequest?.headers.get("Range")).toBe("bytes=0-12");
57
+ expect(result.outboundRequest?.headers.get("If-Range")).toBe('"range-etag"');
58
+ expect(result.outboundRequest?.headers.get("If-None-Match")).toBe('"etag"');
59
+ expect(result.outboundRequest?.headers.get("If-Modified-Since")).toBe("Wed, 21 Oct 2015 07:28:00 GMT");
60
+ expect(result.response).toBe(upstreamResponse);
61
+ expect(result.response?.status).toBe(206);
62
+ expect(result.response?.headers.get("Content-Disposition")).toBe('attachment; filename="file.pdf"');
63
+ expect(result.response?.headers.get("Accept-Ranges")).toBe("bytes");
64
+ expect(blob).not.toHaveBeenCalled();
65
+ expect(arrayBuffer).not.toHaveBeenCalled();
66
+ });
67
+
68
+ test("selects different authentication configurations for different rewrite destinations", async () => {
69
+ const harness = new AuthServiceWorkerHarness();
70
+ await harness.setConfig(
71
+ [authConfig("account-a/", "token-a"), authConfig("account-b/", "token-b")],
72
+ [rewriteRule("download-a/", "account-a/"), rewriteRule("download-b/", "account-b/")]
73
+ );
74
+
75
+ const first = await harness.dispatchFetch("https://dashboard.example.com/download-a/file.pdf");
76
+ const second = await harness.dispatchFetch("https://dashboard.example.com/download-b/file.pdf");
77
+
78
+ expect(first.outboundRequest?.headers.get("Authorization")).toBe("Bearer token-a");
79
+ expect(second.outboundRequest?.headers.get("Authorization")).toBe("Bearer token-b");
80
+ });
81
+
82
+ test("never selects a token from the original URL", async () => {
83
+ const harness = new AuthServiceWorkerHarness();
84
+ await harness.setConfig(
85
+ [authConfigForUrl("https://dashboard.example.com/download/", "original-token")],
86
+ [rewriteRule("download/", "unconfigured-account/")]
87
+ );
88
+
89
+ const result = await harness.dispatchFetch("https://dashboard.example.com/download/file.pdf");
90
+
91
+ expect(result.outboundRequest?.url).toBe("https://upcdn.io/unconfigured-account/file.pdf");
92
+ expect(result.outboundRequest?.headers.has("Authorization")).toBe(false);
93
+ });
94
+
95
+ test("fetches rewritten requests even when no authentication configuration matches", async () => {
96
+ const harness = new AuthServiceWorkerHarness();
97
+ await harness.setConfig([], [rewriteRule("download/", "public-account/")]);
98
+
99
+ const result = await harness.dispatchFetch("https://dashboard.example.com/download/public.pdf");
100
+
101
+ expect(result.responded).toBe(true);
102
+ expect(result.outboundRequest?.url).toBe("https://upcdn.io/public-account/public.pdf");
103
+ expect(result.outboundRequest?.headers.has("Authorization")).toBe(false);
104
+ });
105
+
106
+ test("uses only the first matching rewrite rule", async () => {
107
+ const harness = new AuthServiceWorkerHarness();
108
+ await harness.setConfig(
109
+ [authConfig("account-a/", "token-a"), authConfig("account-b/", "token-b")],
110
+ [rewriteRule("download/", "account-a/"), rewriteRule("download/", "account-b/")]
111
+ );
112
+
113
+ const result = await harness.dispatchFetch("https://dashboard.example.com/download/file.pdf");
114
+
115
+ expect(result.outboundRequest?.url).toBe("https://upcdn.io/account-a/file.pdf");
116
+ expect(result.outboundRequest?.headers.get("Authorization")).toBe("Bearer token-a");
117
+ });
118
+
119
+ test("does not recursively rewrite the rewritten URL", async () => {
120
+ const harness = new AuthServiceWorkerHarness();
121
+ await harness.setConfig(
122
+ [authConfig("account-a/", "token-a")],
123
+ [
124
+ {
125
+ fromUrlPrefix: "https://dashboard.example.com/download/",
126
+ toUrlPrefix: "https://proxy.example.com/download/"
127
+ },
128
+ {
129
+ fromUrlPrefix: "https://proxy.example.com/download/",
130
+ toUrlPrefix: "https://upcdn.io/account-a/"
131
+ }
132
+ ]
133
+ );
134
+
135
+ const result = await harness.dispatchFetch("https://dashboard.example.com/download/file.pdf");
136
+
137
+ expect(result.outboundRequest?.url).toBe("https://proxy.example.com/download/file.pdf");
138
+ expect(result.outboundRequest?.headers.has("Authorization")).toBe(false);
139
+ });
140
+
141
+ test("retains existing behavior when no rewrite rule matches", async () => {
142
+ const harness = new AuthServiceWorkerHarness();
143
+ await harness.setConfig([authConfig("account-a/", "token-a")], [rewriteRule("download/", "account-a/")]);
144
+
145
+ const authenticated = await harness.dispatchFetch("https://upcdn.io/account-a/file.pdf");
146
+ const untouched = await harness.dispatchFetch("https://dashboard.example.com/ordinary-page");
147
+
148
+ expect(authenticated.outboundRequest?.url).toBe("https://upcdn.io/account-a/file.pdf");
149
+ expect(authenticated.outboundRequest?.headers.get("Authorization")).toBe("Bearer token-a");
150
+ expect(untouched.responded).toBe(false);
151
+ });
152
+ });
153
+
154
+ interface FetchOptions {
155
+ headers?: HeadersInit;
156
+ navigation?: boolean;
157
+ }
158
+
159
+ interface FetchResult {
160
+ outboundRequest: TestRequest | undefined;
161
+ responded: boolean;
162
+ response: NodeFetchResponse | undefined;
163
+ }
164
+
165
+ type WorkerEventListener = (event: unknown) => void;
166
+
167
+ class AuthServiceWorkerHarness {
168
+ private readonly context: {
169
+ getRewrittenUrl: (url: string, rules: UrlRewriteRule[]) => string | undefined;
170
+ setConfig: (config: AuthSwConfigEntryDto[], rules?: UrlRewriteRule[]) => Promise<void>;
171
+ };
172
+
173
+ private readonly fetchListener: WorkerEventListener;
174
+ private readonly fetchMock: jest.MockedFunction<(request: TestRequest) => Promise<NodeFetchResponse>>;
175
+
176
+ constructor(private readonly upstreamResponse: NodeFetchResponse = new NodeFetchResponse("ok")) {
177
+ const listeners = new Map<string, WorkerEventListener>();
178
+ const cacheEntries = new Map<string, NodeFetchResponse>();
179
+ this.fetchMock = jest.fn(async (_request: TestRequest): Promise<NodeFetchResponse> => this.upstreamResponse);
180
+
181
+ const self = {
182
+ addEventListener: (type: string, listener: WorkerEventListener): void => {
183
+ listeners.set(type, listener);
184
+ },
185
+ clients: {
186
+ claim: async (): Promise<void> => {},
187
+ get: async (): Promise<undefined> => undefined
188
+ },
189
+ skipWaiting: async (): Promise<void> => {}
190
+ };
191
+ const cache = {
192
+ match: async (key: string): Promise<NodeFetchResponse | undefined> => cacheEntries.get(key)?.clone(),
193
+ put: async (key: string, value: NodeFetchResponse): Promise<void> => {
194
+ cacheEntries.set(key, value.clone());
195
+ }
196
+ };
197
+ const sandbox = {
198
+ caches: { open: async (): Promise<typeof cache> => cache },
199
+ console: { error: jest.fn(), log: jest.fn() },
200
+ fetch: this.fetchMock,
201
+ Headers: NodeFetchHeaders,
202
+ Promise,
203
+ Request: TestRequest,
204
+ Response: NodeFetchResponse,
205
+ self,
206
+ setTimeout
207
+ };
208
+ runInNewContext(workerSource, sandbox);
209
+
210
+ this.context = sandbox as typeof sandbox & AuthServiceWorkerHarness["context"];
211
+ const fetchListener = listeners.get("fetch");
212
+ if (fetchListener === undefined) {
213
+ throw new Error("Auth service worker did not register a fetch listener.");
214
+ }
215
+ this.fetchListener = fetchListener;
216
+ }
217
+
218
+ async setConfig(config: AuthSwConfigEntryDto[], rules?: UrlRewriteRule[]): Promise<void> {
219
+ await this.context.setConfig(config, rules);
220
+ }
221
+
222
+ getRewrittenUrl(url: string, rules: UrlRewriteRule[]): string | undefined {
223
+ return this.context.getRewrittenUrl(url, rules);
224
+ }
225
+
226
+ async dispatchFetch(url: string, options: FetchOptions = {}): Promise<FetchResult> {
227
+ const request = new TestRequest(url, { headers: options.headers as NodeFetchRequestInit["headers"] });
228
+ if (options.navigation === true) {
229
+ Object.defineProperty(request, "mode", { configurable: true, value: "navigate" });
230
+ }
231
+
232
+ let responsePromise: Promise<NodeFetchResponse> | undefined;
233
+ this.fetchListener({
234
+ clientId: "",
235
+ request,
236
+ respondWith: (response: NodeFetchResponse | Promise<NodeFetchResponse>): void => {
237
+ responsePromise = Promise.resolve(response);
238
+ }
239
+ });
240
+
241
+ const response = await responsePromise;
242
+ return {
243
+ outboundRequest: this.fetchMock.mock.calls.at(-1)?.[0],
244
+ responded: responsePromise !== undefined,
245
+ response
246
+ };
247
+ }
248
+ }
249
+
250
+ class TestRequest extends NodeFetchRequest {
251
+ readonly mode: RequestMode;
252
+
253
+ constructor(input: string | NodeFetchRequest, init: NodeFetchRequestInit & { mode?: RequestMode } = {}) {
254
+ super(input, init);
255
+ this.mode = init.mode ?? (input instanceof TestRequest ? input.mode : "cors");
256
+ }
257
+ }
258
+
259
+ function authConfig(accountPath: string, token: string): AuthSwConfigEntryDto {
260
+ return authConfigForUrl(`https://upcdn.io/${accountPath}`, token);
261
+ }
262
+
263
+ function authConfigForUrl(urlPrefix: string, token: string): AuthSwConfigEntryDto {
264
+ return {
265
+ expires: undefined,
266
+ headers: [{ key: "Authorization", value: `Bearer ${token}` }],
267
+ urlPrefix
268
+ };
269
+ }
270
+
271
+ function rewriteRule(fromPath: string, toPath: string): UrlRewriteRule {
272
+ return {
273
+ fromUrlPrefix: `https://dashboard.example.com/${fromPath}`,
274
+ toUrlPrefix: `https://upcdn.io/${toPath}`
275
+ };
276
+ }