@undefineds.co/xpod 0.4.7 → 0.4.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/node_modules/@undefineds.co/solid-sdk/dist/index.cjs +178 -194
- package/node_modules/@undefineds.co/solid-sdk/dist/pod-runtime.js +6 -13
- package/node_modules/@undefineds.co/solid-sdk/dist/storage-selection.js +6 -21
- package/node_modules/@undefineds.co/solid-sdk/dist/webid-auth.d.ts +2 -0
- package/node_modules/@undefineds.co/solid-sdk/dist/webid-auth.js +8 -3
- package/package.json +8 -2
- package/static/app/assets/main.js +10 -10
- package/static/auth-callback/assets/{AiConfigContext-CRxnTyNS.js → AiConfigContext-DJKvxduw.js} +1 -1
- package/static/auth-callback/assets/{AiConfigPage-aidVtDAU.js → AiConfigPage-DgGmHb18.js} +1 -1
- package/static/auth-callback/assets/{DocumentProcessingPanel-C-lnRpEy.js → DocumentProcessingPanel-CJmxu8x1.js} +1 -1
- package/static/auth-callback/assets/{IndexLifecyclePanel-8oscxWj2.js → IndexLifecyclePanel-byCa7h7L.js} +1 -1
- package/static/auth-callback/assets/{IndexSubjectPanel-ybKkidfS.js → IndexSubjectPanel-CDwT4Yx1.js} +1 -1
- package/static/auth-callback/assets/{ModelAssignmentsPanel-DmxLA1Da.js → ModelAssignmentsPanel-CwM3k8w7.js} +1 -1
- package/static/auth-callback/assets/{ModelsPage-B-G4YfTv.js → ModelsPage-DapY2Dwf.js} +1 -1
- package/static/auth-callback/assets/{NetworkPage-W890Vczu.js → NetworkPage-DkVgxtNj.js} +1 -1
- package/static/auth-callback/assets/{PaneListHeader-Aj8ayvMU.js → PaneListHeader-DRI0hjmt.js} +1 -1
- package/static/auth-callback/assets/{SearchIndexingPanel-DTyOKrU1.js → SearchIndexingPanel-mbppDZFE.js} +1 -1
- package/static/auth-callback/assets/{StatusSubjectPanel-DT1Aw7YO.js → StatusSubjectPanel-DNsW76aC.js} +1 -1
- package/static/auth-callback/assets/{StatusWorkspace-BO7YcO6a.js → StatusWorkspace-DGcTbHT5.js} +1 -1
- package/static/auth-callback/assets/{SystemSettingsPage-DfU_qHHq.js → SystemSettingsPage-C6qklJ9k.js} +1 -1
- package/static/auth-callback/assets/{SystemSettingsSubjectPanel-nfcG67Tx.js → SystemSettingsSubjectPanel-B4VV9xkG.js} +1 -1
- package/static/auth-callback/assets/{activity-CPUh0Hwy.js → activity-Ck-Mfxxo.js} +1 -1
- package/static/auth-callback/assets/{auth-callback-rq-93B6I.js → auth-callback-DG4Ge40c.js} +6 -6
- package/static/auth-callback/assets/{badge-C8cHw8zP.js → badge-GSSnw1VV.js} +1 -1
- package/static/auth-callback/assets/{download-C7i9MWS2.js → download-BYyRX-K2.js} +1 -1
- package/static/auth-callback/assets/{external-link-BrwSigZD.js → external-link-WEC0Ki-0.js} +1 -1
- package/static/auth-callback/assets/{index-DSY0DCJV.js → index-DlsMCgo8.js} +1 -1
- package/static/auth-callback/assets/{index-browser-CMOmp0rf.js → index-browser-D8YW6coF.js} +1 -1
- package/static/auth-callback/assets/{index-Drnlv8Gw.js → index-kju2k4Dp.js} +1 -1
- package/static/auth-callback/assets/{key-round-DO8fiDNf.js → key-round-DJUT5Nh0.js} +1 -1
- package/static/auth-callback/assets/{rotate-ccw-DAOErQ0P.js → rotate-ccw-CXwfZUKU.js} +1 -1
- package/static/auth-callback/assets/{skeleton-poTd6JSC.js → skeleton-DWPtNhUB.js} +1 -1
- package/static/auth-callback/assets/{waypoints-Bg9TQvJS.js → waypoints-l1oDnRJ0.js} +1 -1
- package/static/auth-callback/assets/{workspace-layout-Dmb-DfHy.js → workspace-layout-B7iKpK-Q.js} +1 -1
- package/static/auth-callback/auth-callback.html +1 -1
- package/static/dashboard/assets/{AiConfigContext-Ds7MIAm6.js → AiConfigContext-U0cCSY28.js} +1 -1
- package/static/dashboard/assets/{AiConfigPage-CRrI_NnS.js → AiConfigPage-CMi0uJdo.js} +1 -1
- package/static/dashboard/assets/{DocumentProcessingPanel-BVFIT_Zl.js → DocumentProcessingPanel-DEZiEpft.js} +1 -1
- package/static/dashboard/assets/{IndexLifecyclePanel-BSzivp60.js → IndexLifecyclePanel-CGZfLUog.js} +1 -1
- package/static/dashboard/assets/{IndexSubjectPanel-B5zQeqWl.js → IndexSubjectPanel-DiDZauHk.js} +1 -1
- package/static/dashboard/assets/{ModelAssignmentsPanel-B-1FWxn0.js → ModelAssignmentsPanel-DaGGZKuf.js} +1 -1
- package/static/dashboard/assets/{ModelsPage-B3VYhX9h.js → ModelsPage-DZgAOdxO.js} +1 -1
- package/static/dashboard/assets/{NetworkPage-HJqcYjfO.js → NetworkPage-DI5-O7GT.js} +1 -1
- package/static/dashboard/assets/{PaneListHeader-DiFB-pBl.js → PaneListHeader-4oa7uf59.js} +1 -1
- package/static/dashboard/assets/{SearchIndexingPanel-BnA0kOD9.js → SearchIndexingPanel-DoqEwocD.js} +1 -1
- package/static/dashboard/assets/{StatusSubjectPanel-tAB7Scqn.js → StatusSubjectPanel-CLzBC5fu.js} +1 -1
- package/static/dashboard/assets/{StatusWorkspace-kNMe8nOB.js → StatusWorkspace-UyKADZoM.js} +1 -1
- package/static/dashboard/assets/{SystemSettingsPage-8U1VrI_f.js → SystemSettingsPage-qP0hLHNV.js} +1 -1
- package/static/dashboard/assets/{SystemSettingsSubjectPanel-goh78mjo.js → SystemSettingsSubjectPanel-EaWax9Ot.js} +1 -1
- package/static/dashboard/assets/{activity-DNXbcx1t.js → activity-CjIoNU4S.js} +1 -1
- package/static/dashboard/assets/{badge-DR3YwIcl.js → badge-dcDZpKh3.js} +1 -1
- package/static/dashboard/assets/{dashboard-C64rEkaL.js → dashboard-BY8GdnEI.js} +5 -5
- package/static/dashboard/assets/{download-BwyvySqB.js → download-BeYCrc-_.js} +1 -1
- package/static/dashboard/assets/{external-link-Dmez6yTy.js → external-link-626-5KVx.js} +1 -1
- package/static/dashboard/assets/{index-N1onOEV6.js → index-CBbk6hx-.js} +1 -1
- package/static/dashboard/assets/{index-BAkEY7c3.js → index-CsxH6Hmz.js} +1 -1
- package/static/dashboard/assets/{key-round-BM6zS_FJ.js → key-round-B9IL4Wua.js} +1 -1
- package/static/dashboard/assets/{rotate-ccw-D6BDXyhE.js → rotate-ccw-DM4vCfiI.js} +1 -1
- package/static/dashboard/assets/{skeleton-Dy9nFkSL.js → skeleton-ChlFAPvP.js} +1 -1
- package/static/dashboard/assets/{waypoints-GzW4F2Ei.js → waypoints-CvpZzf_I.js} +1 -1
- package/static/dashboard/assets/{workspace-layout-D6jwN-1q.js → workspace-layout-Bxv7V-tt.js} +1 -1
- package/static/dashboard/dashboard.html +1 -1
- package/static/settings/assets/{AiConfigContext-CwPOEIt2.js → AiConfigContext-CDVIHLBb.js} +1 -1
- package/static/settings/assets/{AiConfigPage-BSUTkt12.js → AiConfigPage-C7qfiiaJ.js} +1 -1
- package/static/settings/assets/{DocumentProcessingPanel-Bsrmh_jc.js → DocumentProcessingPanel-B59QTe-V.js} +1 -1
- package/static/settings/assets/{IndexLifecyclePanel-CqG9oal4.js → IndexLifecyclePanel-C-e-3eVi.js} +1 -1
- package/static/settings/assets/{IndexSubjectPanel-C81n07Gj.js → IndexSubjectPanel-B-scRZ1u.js} +1 -1
- package/static/settings/assets/{ModelAssignmentsPanel-CwUbqhcc.js → ModelAssignmentsPanel-Wy8jzVg4.js} +1 -1
- package/static/settings/assets/{ModelsPage-BMs6BLrm.js → ModelsPage-DHNiaodk.js} +1 -1
- package/static/settings/assets/{NetworkPage-DtjeHtL4.js → NetworkPage-tBB-4Ox8.js} +1 -1
- package/static/settings/assets/{PaneListHeader-CO0oaVu8.js → PaneListHeader-DuD6glzn.js} +1 -1
- package/static/settings/assets/{SearchIndexingPanel-BgQfRVsf.js → SearchIndexingPanel-CiDHc0nx.js} +1 -1
- package/static/settings/assets/{StatusSubjectPanel-DJn3ZOvK.js → StatusSubjectPanel-OF5cgWwt.js} +1 -1
- package/static/settings/assets/{StatusWorkspace-C_CSF842.js → StatusWorkspace-CZ8Oa0gV.js} +1 -1
- package/static/settings/assets/{SystemSettingsPage-Oigv0uvH.js → SystemSettingsPage-DT2tC0F_.js} +1 -1
- package/static/settings/assets/{SystemSettingsSubjectPanel-Bc8qCKlD.js → SystemSettingsSubjectPanel-Bkta1oDw.js} +1 -1
- package/static/settings/assets/{activity-D0ZeHnaN.js → activity-2M1f5eBS.js} +1 -1
- package/static/settings/assets/{badge-qUNztkFM.js → badge-CRKan0qX.js} +1 -1
- package/static/settings/assets/{download-Cqf-BduE.js → download-PCHldCdu.js} +1 -1
- package/static/settings/assets/{external-link-IuWJ6OjY.js → external-link-tmKe7eYO.js} +1 -1
- package/static/settings/assets/{index-D3o0Tuj2.js → index-BDCNTwWz.js} +1 -1
- package/static/settings/assets/{index-DRjSKNcs.js → index-Cek02M79.js} +1 -1
- package/static/settings/assets/{index-browser-D9qFZqYE.js → index-browser-EVDVm9T5.js} +1 -1
- package/static/settings/assets/{key-round-KxL3N2jU.js → key-round-CUGvQCkt.js} +1 -1
- package/static/settings/assets/{rotate-ccw-BiW1Onno.js → rotate-ccw-BRA3lIHM.js} +1 -1
- package/static/settings/assets/{settings-DH2ML86C.js → settings-BdlKrzJ3.js} +6 -6
- package/static/settings/assets/{skeleton-CsUPnZPX.js → skeleton-CuDeifWb.js} +1 -1
- package/static/settings/assets/{waypoints-xuODoy-1.js → waypoints-w6LxsFY1.js} +1 -1
- package/static/settings/assets/{workspace-layout-O7swKpQ6.js → workspace-layout-TN-BWmT5.js} +1 -1
- package/static/settings/settings.html +1 -1
|
@@ -39,6 +39,7 @@ var __export = (target, all) => {
|
|
|
39
39
|
// src/index.ts
|
|
40
40
|
var exports_src = {};
|
|
41
41
|
__export(exports_src, {
|
|
42
|
+
validateWebId: () => validateWebId,
|
|
42
43
|
useSolidSessionSnapshot: () => useSolidSessionSnapshot,
|
|
43
44
|
useSolidRuntime: () => useSolidRuntime,
|
|
44
45
|
useLoginStore: () => useLoginStore,
|
|
@@ -228,16 +229,178 @@ function createSolidSessionRuntime(options = {}) {
|
|
|
228
229
|
}
|
|
229
230
|
};
|
|
230
231
|
}
|
|
231
|
-
// src/
|
|
232
|
-
var
|
|
233
|
-
|
|
234
|
-
|
|
232
|
+
// src/webid-auth.ts
|
|
233
|
+
var ENDPOINT_PROTOCOLS = new Set(["http:", "https:"]);
|
|
234
|
+
var RESERVED_AUTHORIZATION_PARAMETERS = new Set([
|
|
235
|
+
"state",
|
|
236
|
+
"redirect_uri",
|
|
237
|
+
"client_id",
|
|
238
|
+
"response_type",
|
|
239
|
+
"code_challenge",
|
|
240
|
+
"code_challenge_method"
|
|
241
|
+
]);
|
|
242
|
+
function normalizeEndpoint(endpoint, fieldName) {
|
|
243
|
+
if (!endpoint || typeof endpoint.url !== "string" || endpoint.url.trim() === "") {
|
|
244
|
+
throw new TypeError(`${fieldName}.url must be a non-empty absolute URL`);
|
|
245
|
+
}
|
|
246
|
+
if (typeof endpoint.label !== "string") {
|
|
247
|
+
throw new TypeError(`${fieldName}.label must be a string`);
|
|
248
|
+
}
|
|
249
|
+
let parsed;
|
|
250
|
+
try {
|
|
251
|
+
parsed = new URL(endpoint.url.trim());
|
|
252
|
+
} catch {
|
|
253
|
+
throw new TypeError(`${fieldName}.url must be a valid absolute URL`);
|
|
254
|
+
}
|
|
255
|
+
if (!ENDPOINT_PROTOCOLS.has(parsed.protocol)) {
|
|
256
|
+
throw new TypeError(`${fieldName}.url must use http or https`);
|
|
257
|
+
}
|
|
258
|
+
if (parsed.username || parsed.password) {
|
|
259
|
+
throw new TypeError(`${fieldName}.url must not contain credentials`);
|
|
260
|
+
}
|
|
261
|
+
if (parsed.hash) {
|
|
262
|
+
throw new TypeError(`${fieldName}.url must not contain a fragment`);
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
url: parsed.href,
|
|
266
|
+
label: endpoint.label
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function validateWebId(value) {
|
|
270
|
+
if (typeof value !== "string" || value === "" || /[\s\u0000-\u001f\u007f-\u009f]/u.test(value)) {
|
|
271
|
+
throw new TypeError("webId must be a non-empty identifier without whitespace or control characters");
|
|
272
|
+
}
|
|
273
|
+
return value;
|
|
274
|
+
}
|
|
275
|
+
function normalizeStorageBinding(binding) {
|
|
276
|
+
if (!binding || typeof binding.storageUrl !== "string" || binding.storageUrl.trim() === "") {
|
|
277
|
+
throw new TypeError("selectedStorage.storageUrl must be a non-empty absolute URL");
|
|
278
|
+
}
|
|
279
|
+
validateWebId(binding.webId);
|
|
280
|
+
let parsed;
|
|
235
281
|
try {
|
|
236
|
-
|
|
282
|
+
parsed = new URL(binding.storageUrl.trim());
|
|
237
283
|
} catch {
|
|
238
|
-
|
|
284
|
+
throw new TypeError("selectedStorage.storageUrl must be a valid absolute URL");
|
|
285
|
+
}
|
|
286
|
+
if (!ENDPOINT_PROTOCOLS.has(parsed.protocol)) {
|
|
287
|
+
throw new TypeError("selectedStorage.storageUrl must use http or https");
|
|
288
|
+
}
|
|
289
|
+
if (parsed.username || parsed.password) {
|
|
290
|
+
throw new TypeError("selectedStorage.storageUrl must not contain credentials");
|
|
291
|
+
}
|
|
292
|
+
if (parsed.hash) {
|
|
293
|
+
throw new TypeError("selectedStorage.storageUrl must not contain a fragment");
|
|
294
|
+
}
|
|
295
|
+
return {
|
|
296
|
+
storageUrl: parsed.href,
|
|
297
|
+
webId: binding.webId,
|
|
298
|
+
...binding.label === undefined ? {} : { label: binding.label }
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function ensureEnum(value, values, fieldName) {
|
|
302
|
+
if (!values.includes(value)) {
|
|
303
|
+
throw new TypeError(`${fieldName} has an unsupported value`);
|
|
304
|
+
}
|
|
305
|
+
return value;
|
|
306
|
+
}
|
|
307
|
+
function normalizeWebIdLoginRoute(input) {
|
|
308
|
+
if (!input || typeof input.id !== "string" || input.id.trim() === "") {
|
|
309
|
+
throw new TypeError("route.id must be a non-empty opaque identifier");
|
|
310
|
+
}
|
|
311
|
+
if (typeof input.label !== "string") {
|
|
312
|
+
throw new TypeError("route.label must be a string");
|
|
313
|
+
}
|
|
314
|
+
const normalized = {
|
|
315
|
+
id: input.id.trim(),
|
|
316
|
+
label: input.label,
|
|
317
|
+
...input.description === undefined ? {} : { description: input.description },
|
|
318
|
+
...input.badge === undefined ? {} : {
|
|
319
|
+
badge: {
|
|
320
|
+
label: input.badge.label,
|
|
321
|
+
tone: ensureEnum(input.badge.tone, ["neutral", "primary", "success", "warning", "danger"], "route.badge.tone")
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
identityProvider: normalizeEndpoint(input.identityProvider, "route.identityProvider"),
|
|
325
|
+
...input.storageProvider === undefined ? {} : {
|
|
326
|
+
storageProvider: normalizeEndpoint(input.storageProvider, "route.storageProvider")
|
|
327
|
+
},
|
|
328
|
+
availability: ensureEnum(input.availability, ["ready", "starting", "unavailable"], "route.availability"),
|
|
329
|
+
...input.unavailableReason === undefined ? {} : { unavailableReason: input.unavailableReason }
|
|
330
|
+
};
|
|
331
|
+
return normalized;
|
|
332
|
+
}
|
|
333
|
+
function decodeForValidation(value) {
|
|
334
|
+
let decoded = value;
|
|
335
|
+
for (let index = 0;index < 4; index += 1) {
|
|
336
|
+
let next;
|
|
337
|
+
try {
|
|
338
|
+
next = decodeURIComponent(decoded);
|
|
339
|
+
} catch {
|
|
340
|
+
throw new TypeError("returnTo contains malformed percent encoding");
|
|
341
|
+
}
|
|
342
|
+
if (next === decoded) {
|
|
343
|
+
return decoded;
|
|
344
|
+
}
|
|
345
|
+
decoded = next;
|
|
346
|
+
}
|
|
347
|
+
return decoded;
|
|
348
|
+
}
|
|
349
|
+
function matchesAllowedPrefix(pathname, allowedPrefixes) {
|
|
350
|
+
return allowedPrefixes.some((prefix) => {
|
|
351
|
+
if (prefix === "/") {
|
|
352
|
+
return pathname.startsWith("/");
|
|
353
|
+
}
|
|
354
|
+
const normalizedPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
355
|
+
return pathname === normalizedPrefix || pathname.startsWith(`${normalizedPrefix}/`);
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
function normalizeApplicationReturnTo(value, allowedPrefixes) {
|
|
359
|
+
if (value === undefined) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
363
|
+
throw new TypeError("returnTo must be a non-empty application path");
|
|
364
|
+
}
|
|
365
|
+
const normalized = value.trim();
|
|
366
|
+
const decoded = decodeForValidation(normalized);
|
|
367
|
+
const pathname = decoded.split(/[?#]/, 1)[0];
|
|
368
|
+
if (!normalized.startsWith("/") || normalized.startsWith("//") || normalized.includes("\\") || decoded.includes("\\") || /^[a-z][a-z\d+.-]*:/i.test(decoded) || decoded.startsWith("//") || pathname.split("/").some((segment) => segment === "..") || !matchesAllowedPrefix(pathname, allowedPrefixes)) {
|
|
369
|
+
throw new TypeError("returnTo must be a safe path within the application allow-list");
|
|
370
|
+
}
|
|
371
|
+
return normalized;
|
|
372
|
+
}
|
|
373
|
+
function normalizeWebIdLoginTransaction(input) {
|
|
374
|
+
if (!input || typeof input.id !== "string" || input.id.trim() === "") {
|
|
375
|
+
throw new TypeError("transaction.id must be a non-empty opaque identifier");
|
|
376
|
+
}
|
|
377
|
+
const authorizationParameters = input.authorizationParameters === undefined ? undefined : { ...input.authorizationParameters };
|
|
378
|
+
if (authorizationParameters) {
|
|
379
|
+
for (const key of Object.keys(authorizationParameters)) {
|
|
380
|
+
if (RESERVED_AUTHORIZATION_PARAMETERS.has(key.toLowerCase())) {
|
|
381
|
+
throw new TypeError(`authorizationParameters cannot override ${key}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
239
384
|
}
|
|
385
|
+
const prompt = input.prompt === undefined ? undefined : ensureEnum(input.prompt, ["login", "consent", "select_account"], "transaction.prompt");
|
|
386
|
+
return {
|
|
387
|
+
id: input.id,
|
|
388
|
+
route: normalizeWebIdLoginRoute(input.route),
|
|
389
|
+
...input.selectedStorage === undefined ? {} : {
|
|
390
|
+
selectedStorage: normalizeStorageBinding(input.selectedStorage)
|
|
391
|
+
},
|
|
392
|
+
authorizationSurface: ensureEnum(input.authorizationSurface, ["redirect", "popup", "embedded", "external"], "transaction.authorizationSurface"),
|
|
393
|
+
...prompt === undefined ? {} : { prompt },
|
|
394
|
+
discovery: ensureEnum(input.discovery, ["standard", "strict"], "transaction.discovery"),
|
|
395
|
+
...authorizationParameters === undefined ? {} : { authorizationParameters },
|
|
396
|
+
...input.returnTo === undefined ? {} : {
|
|
397
|
+
returnTo: normalizeApplicationReturnTo(input.returnTo, ["/"])
|
|
398
|
+
}
|
|
399
|
+
};
|
|
240
400
|
}
|
|
401
|
+
|
|
402
|
+
// src/pod-runtime.ts
|
|
403
|
+
var KEY_SEPARATOR = "\x00";
|
|
241
404
|
function normalizePodUrl(value) {
|
|
242
405
|
if (typeof value !== "string" || value.trim() === "") {
|
|
243
406
|
throw new TypeError("podUrl must be a non-empty absolute URL");
|
|
@@ -257,7 +420,7 @@ function normalizePodUrl(value) {
|
|
|
257
420
|
return parsed.href;
|
|
258
421
|
}
|
|
259
422
|
function compositeKey(webId, podUrl) {
|
|
260
|
-
return `${
|
|
423
|
+
return `${webId}${KEY_SEPARATOR}${podUrl}`;
|
|
261
424
|
}
|
|
262
425
|
function nextGeneration(generations, key) {
|
|
263
426
|
const generation = (generations.get(key) ?? 0) + 1;
|
|
@@ -278,7 +441,7 @@ function createPodRuntime(options) {
|
|
|
278
441
|
const webIdGenerations = new Map;
|
|
279
442
|
const compositeGenerations = new Map;
|
|
280
443
|
const clearWebId = (webId, podUrl) => {
|
|
281
|
-
const webIdKey =
|
|
444
|
+
const webIdKey = validateWebId(webId);
|
|
282
445
|
if (podUrl !== undefined) {
|
|
283
446
|
const normalizedPodUrl = normalizePodUrl(podUrl);
|
|
284
447
|
const key = compositeKey(webId, normalizedPodUrl);
|
|
@@ -315,7 +478,7 @@ function createPodRuntime(options) {
|
|
|
315
478
|
}
|
|
316
479
|
};
|
|
317
480
|
const clear = (identity) => {
|
|
318
|
-
if (
|
|
481
|
+
if (identity === undefined) {
|
|
319
482
|
readyByWebId.clear();
|
|
320
483
|
readyByComposite.clear();
|
|
321
484
|
for (const pending of pendingByWebId.values()) {
|
|
@@ -342,7 +505,7 @@ function createPodRuntime(options) {
|
|
|
342
505
|
};
|
|
343
506
|
return {
|
|
344
507
|
open(args) {
|
|
345
|
-
const webIdKey =
|
|
508
|
+
const webIdKey = validateWebId(args.webId);
|
|
346
509
|
const explicitPodUrl = args.podUrl === undefined ? undefined : normalizePodUrl(args.podUrl);
|
|
347
510
|
if (explicitPodUrl !== undefined) {
|
|
348
511
|
const key = compositeKey(args.webId, explicitPodUrl);
|
|
@@ -492,189 +655,10 @@ function useSolidRuntime() {
|
|
|
492
655
|
function useSolidSessionSnapshot() {
|
|
493
656
|
return useSolidRuntime().session.getSnapshot();
|
|
494
657
|
}
|
|
495
|
-
// src/webid-auth.ts
|
|
496
|
-
var ENDPOINT_PROTOCOLS = new Set(["http:", "https:"]);
|
|
497
|
-
var RESERVED_AUTHORIZATION_PARAMETERS = new Set([
|
|
498
|
-
"state",
|
|
499
|
-
"redirect_uri",
|
|
500
|
-
"client_id",
|
|
501
|
-
"response_type",
|
|
502
|
-
"code_challenge",
|
|
503
|
-
"code_challenge_method"
|
|
504
|
-
]);
|
|
505
|
-
function normalizeEndpoint(endpoint, fieldName) {
|
|
506
|
-
if (!endpoint || typeof endpoint.url !== "string" || endpoint.url.trim() === "") {
|
|
507
|
-
throw new TypeError(`${fieldName}.url must be a non-empty absolute URL`);
|
|
508
|
-
}
|
|
509
|
-
if (typeof endpoint.label !== "string") {
|
|
510
|
-
throw new TypeError(`${fieldName}.label must be a string`);
|
|
511
|
-
}
|
|
512
|
-
let parsed;
|
|
513
|
-
try {
|
|
514
|
-
parsed = new URL(endpoint.url.trim());
|
|
515
|
-
} catch {
|
|
516
|
-
throw new TypeError(`${fieldName}.url must be a valid absolute URL`);
|
|
517
|
-
}
|
|
518
|
-
if (!ENDPOINT_PROTOCOLS.has(parsed.protocol)) {
|
|
519
|
-
throw new TypeError(`${fieldName}.url must use http or https`);
|
|
520
|
-
}
|
|
521
|
-
if (parsed.username || parsed.password) {
|
|
522
|
-
throw new TypeError(`${fieldName}.url must not contain credentials`);
|
|
523
|
-
}
|
|
524
|
-
if (parsed.hash) {
|
|
525
|
-
throw new TypeError(`${fieldName}.url must not contain a fragment`);
|
|
526
|
-
}
|
|
527
|
-
return {
|
|
528
|
-
url: parsed.href,
|
|
529
|
-
label: endpoint.label
|
|
530
|
-
};
|
|
531
|
-
}
|
|
532
|
-
function normalizeStorageBinding(binding) {
|
|
533
|
-
if (!binding || typeof binding.storageUrl !== "string" || binding.storageUrl.trim() === "") {
|
|
534
|
-
throw new TypeError("selectedStorage.storageUrl must be a non-empty absolute URL");
|
|
535
|
-
}
|
|
536
|
-
if (typeof binding.webId !== "string" || binding.webId.trim() === "") {
|
|
537
|
-
throw new TypeError("selectedStorage.webId must be a non-empty identifier URL");
|
|
538
|
-
}
|
|
539
|
-
let parsed;
|
|
540
|
-
try {
|
|
541
|
-
parsed = new URL(binding.storageUrl.trim());
|
|
542
|
-
} catch {
|
|
543
|
-
throw new TypeError("selectedStorage.storageUrl must be a valid absolute URL");
|
|
544
|
-
}
|
|
545
|
-
if (!ENDPOINT_PROTOCOLS.has(parsed.protocol)) {
|
|
546
|
-
throw new TypeError("selectedStorage.storageUrl must use http or https");
|
|
547
|
-
}
|
|
548
|
-
if (parsed.username || parsed.password) {
|
|
549
|
-
throw new TypeError("selectedStorage.storageUrl must not contain credentials");
|
|
550
|
-
}
|
|
551
|
-
if (parsed.hash) {
|
|
552
|
-
throw new TypeError("selectedStorage.storageUrl must not contain a fragment");
|
|
553
|
-
}
|
|
554
|
-
return {
|
|
555
|
-
storageUrl: parsed.href,
|
|
556
|
-
webId: binding.webId,
|
|
557
|
-
...binding.label === undefined ? {} : { label: binding.label }
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
function ensureEnum(value, values, fieldName) {
|
|
561
|
-
if (!values.includes(value)) {
|
|
562
|
-
throw new TypeError(`${fieldName} has an unsupported value`);
|
|
563
|
-
}
|
|
564
|
-
return value;
|
|
565
|
-
}
|
|
566
|
-
function normalizeWebIdLoginRoute(input) {
|
|
567
|
-
if (!input || typeof input.id !== "string" || input.id.trim() === "") {
|
|
568
|
-
throw new TypeError("route.id must be a non-empty opaque identifier");
|
|
569
|
-
}
|
|
570
|
-
if (typeof input.label !== "string") {
|
|
571
|
-
throw new TypeError("route.label must be a string");
|
|
572
|
-
}
|
|
573
|
-
const normalized = {
|
|
574
|
-
id: input.id.trim(),
|
|
575
|
-
label: input.label,
|
|
576
|
-
...input.description === undefined ? {} : { description: input.description },
|
|
577
|
-
...input.badge === undefined ? {} : {
|
|
578
|
-
badge: {
|
|
579
|
-
label: input.badge.label,
|
|
580
|
-
tone: ensureEnum(input.badge.tone, ["neutral", "primary", "success", "warning", "danger"], "route.badge.tone")
|
|
581
|
-
}
|
|
582
|
-
},
|
|
583
|
-
identityProvider: normalizeEndpoint(input.identityProvider, "route.identityProvider"),
|
|
584
|
-
...input.storageProvider === undefined ? {} : {
|
|
585
|
-
storageProvider: normalizeEndpoint(input.storageProvider, "route.storageProvider")
|
|
586
|
-
},
|
|
587
|
-
availability: ensureEnum(input.availability, ["ready", "starting", "unavailable"], "route.availability"),
|
|
588
|
-
...input.unavailableReason === undefined ? {} : { unavailableReason: input.unavailableReason }
|
|
589
|
-
};
|
|
590
|
-
return normalized;
|
|
591
|
-
}
|
|
592
|
-
function decodeForValidation(value) {
|
|
593
|
-
let decoded = value;
|
|
594
|
-
for (let index = 0;index < 4; index += 1) {
|
|
595
|
-
let next;
|
|
596
|
-
try {
|
|
597
|
-
next = decodeURIComponent(decoded);
|
|
598
|
-
} catch {
|
|
599
|
-
throw new TypeError("returnTo contains malformed percent encoding");
|
|
600
|
-
}
|
|
601
|
-
if (next === decoded) {
|
|
602
|
-
return decoded;
|
|
603
|
-
}
|
|
604
|
-
decoded = next;
|
|
605
|
-
}
|
|
606
|
-
return decoded;
|
|
607
|
-
}
|
|
608
|
-
function matchesAllowedPrefix(pathname, allowedPrefixes) {
|
|
609
|
-
return allowedPrefixes.some((prefix) => {
|
|
610
|
-
if (prefix === "/") {
|
|
611
|
-
return pathname.startsWith("/");
|
|
612
|
-
}
|
|
613
|
-
const normalizedPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
614
|
-
return pathname === normalizedPrefix || pathname.startsWith(`${normalizedPrefix}/`);
|
|
615
|
-
});
|
|
616
|
-
}
|
|
617
|
-
function normalizeApplicationReturnTo(value, allowedPrefixes) {
|
|
618
|
-
if (value === undefined) {
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
622
|
-
throw new TypeError("returnTo must be a non-empty application path");
|
|
623
|
-
}
|
|
624
|
-
const normalized = value.trim();
|
|
625
|
-
const decoded = decodeForValidation(normalized);
|
|
626
|
-
const pathname = decoded.split(/[?#]/, 1)[0];
|
|
627
|
-
if (!normalized.startsWith("/") || normalized.startsWith("//") || normalized.includes("\\") || decoded.includes("\\") || /^[a-z][a-z\d+.-]*:/i.test(decoded) || decoded.startsWith("//") || pathname.split("/").some((segment) => segment === "..") || !matchesAllowedPrefix(pathname, allowedPrefixes)) {
|
|
628
|
-
throw new TypeError("returnTo must be a safe path within the application allow-list");
|
|
629
|
-
}
|
|
630
|
-
return normalized;
|
|
631
|
-
}
|
|
632
|
-
function normalizeWebIdLoginTransaction(input) {
|
|
633
|
-
if (!input || typeof input.id !== "string" || input.id.trim() === "") {
|
|
634
|
-
throw new TypeError("transaction.id must be a non-empty opaque identifier");
|
|
635
|
-
}
|
|
636
|
-
const authorizationParameters = input.authorizationParameters === undefined ? undefined : { ...input.authorizationParameters };
|
|
637
|
-
if (authorizationParameters) {
|
|
638
|
-
for (const key of Object.keys(authorizationParameters)) {
|
|
639
|
-
if (RESERVED_AUTHORIZATION_PARAMETERS.has(key.toLowerCase())) {
|
|
640
|
-
throw new TypeError(`authorizationParameters cannot override ${key}`);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
const prompt = input.prompt === undefined ? undefined : ensureEnum(input.prompt, ["login", "consent", "select_account"], "transaction.prompt");
|
|
645
|
-
return {
|
|
646
|
-
id: input.id,
|
|
647
|
-
route: normalizeWebIdLoginRoute(input.route),
|
|
648
|
-
...input.selectedStorage === undefined ? {} : {
|
|
649
|
-
selectedStorage: normalizeStorageBinding(input.selectedStorage)
|
|
650
|
-
},
|
|
651
|
-
authorizationSurface: ensureEnum(input.authorizationSurface, ["redirect", "popup", "embedded", "external"], "transaction.authorizationSurface"),
|
|
652
|
-
...prompt === undefined ? {} : { prompt },
|
|
653
|
-
discovery: ensureEnum(input.discovery, ["standard", "strict"], "transaction.discovery"),
|
|
654
|
-
...authorizationParameters === undefined ? {} : { authorizationParameters },
|
|
655
|
-
...input.returnTo === undefined ? {} : {
|
|
656
|
-
returnTo: normalizeApplicationReturnTo(input.returnTo, ["/"])
|
|
657
|
-
}
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
658
|
// src/storage-selection.ts
|
|
661
|
-
function normalizeIdentity(value) {
|
|
662
|
-
const trimmed = value.trim();
|
|
663
|
-
try {
|
|
664
|
-
return new URL(trimmed).href;
|
|
665
|
-
} catch {
|
|
666
|
-
return trimmed;
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
function normalizedBinding(binding) {
|
|
670
|
-
return {
|
|
671
|
-
...normalizeStorageBinding(binding),
|
|
672
|
-
webId: normalizeIdentity(binding.webId)
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
659
|
function storageBindingMatches(expected, actual) {
|
|
676
|
-
const left =
|
|
677
|
-
const right =
|
|
660
|
+
const left = normalizeStorageBinding(expected);
|
|
661
|
+
const right = normalizeStorageBinding(actual);
|
|
678
662
|
return left.storageUrl === right.storageUrl && left.webId === right.webId;
|
|
679
663
|
}
|
|
680
664
|
function reconcileStorageSelection(optionsOrWebId, positionalCandidates = [], positionalRemembered) {
|
|
@@ -692,8 +676,8 @@ function reconcileStorageSelection(optionsOrWebId, positionalCandidates = [], po
|
|
|
692
676
|
if (options.webId === undefined || options.candidates === undefined) {
|
|
693
677
|
return;
|
|
694
678
|
}
|
|
695
|
-
const webId =
|
|
696
|
-
const candidates = options.candidates.map(
|
|
679
|
+
const webId = validateWebId(options.webId);
|
|
680
|
+
const candidates = options.candidates.map(normalizeStorageBinding);
|
|
697
681
|
const seenByStorage = new Map;
|
|
698
682
|
const uniqueCandidates = [];
|
|
699
683
|
for (const candidate of candidates) {
|
|
@@ -711,7 +695,7 @@ function reconcileStorageSelection(optionsOrWebId, positionalCandidates = [], po
|
|
|
711
695
|
}
|
|
712
696
|
const eligible = uniqueCandidates.filter((candidate) => candidate.webId === webId);
|
|
713
697
|
if (options.remembered) {
|
|
714
|
-
const remembered =
|
|
698
|
+
const remembered = normalizeStorageBinding(options.remembered);
|
|
715
699
|
const exact = eligible.find((candidate) => storageBindingMatches(candidate, remembered));
|
|
716
700
|
if (exact) {
|
|
717
701
|
return { status: "ready", selected: { ...exact } };
|
|
@@ -1,13 +1,5 @@
|
|
|
1
|
+
import { validateWebId } from './webid-auth.js';
|
|
1
2
|
const KEY_SEPARATOR = '\u0000';
|
|
2
|
-
function normalizeIdentityPart(value) {
|
|
3
|
-
const trimmed = value.trim();
|
|
4
|
-
try {
|
|
5
|
-
return new URL(trimmed).href;
|
|
6
|
-
}
|
|
7
|
-
catch {
|
|
8
|
-
return trimmed;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
3
|
function normalizePodUrl(value) {
|
|
12
4
|
if (typeof value !== 'string' || value.trim() === '') {
|
|
13
5
|
throw new TypeError('podUrl must be a non-empty absolute URL');
|
|
@@ -28,7 +20,8 @@ function normalizePodUrl(value) {
|
|
|
28
20
|
return parsed.href;
|
|
29
21
|
}
|
|
30
22
|
function compositeKey(webId, podUrl) {
|
|
31
|
-
|
|
23
|
+
// Only Pod addresses are normalized. The complete WebID is the identity.
|
|
24
|
+
return `${webId}${KEY_SEPARATOR}${podUrl}`;
|
|
32
25
|
}
|
|
33
26
|
function nextGeneration(generations, key) {
|
|
34
27
|
const generation = (generations.get(key) ?? 0) + 1;
|
|
@@ -51,7 +44,7 @@ export function createPodRuntime(options) {
|
|
|
51
44
|
const webIdGenerations = new Map();
|
|
52
45
|
const compositeGenerations = new Map();
|
|
53
46
|
const clearWebId = (webId, podUrl) => {
|
|
54
|
-
const webIdKey =
|
|
47
|
+
const webIdKey = validateWebId(webId);
|
|
55
48
|
if (podUrl !== undefined) {
|
|
56
49
|
const normalizedPodUrl = normalizePodUrl(podUrl);
|
|
57
50
|
const key = compositeKey(webId, normalizedPodUrl);
|
|
@@ -91,7 +84,7 @@ export function createPodRuntime(options) {
|
|
|
91
84
|
}
|
|
92
85
|
};
|
|
93
86
|
const clear = (identity) => {
|
|
94
|
-
if (
|
|
87
|
+
if (identity === undefined) {
|
|
95
88
|
readyByWebId.clear();
|
|
96
89
|
readyByComposite.clear();
|
|
97
90
|
for (const pending of pendingByWebId.values()) {
|
|
@@ -118,7 +111,7 @@ export function createPodRuntime(options) {
|
|
|
118
111
|
};
|
|
119
112
|
return {
|
|
120
113
|
open(args) {
|
|
121
|
-
const webIdKey =
|
|
114
|
+
const webIdKey = validateWebId(args.webId);
|
|
122
115
|
const explicitPodUrl = args.podUrl === undefined ? undefined : normalizePodUrl(args.podUrl);
|
|
123
116
|
if (explicitPodUrl !== undefined) {
|
|
124
117
|
const key = compositeKey(args.webId, explicitPodUrl);
|
|
@@ -1,22 +1,7 @@
|
|
|
1
|
-
import { normalizeStorageBinding } from './webid-auth.js';
|
|
2
|
-
function normalizeIdentity(value) {
|
|
3
|
-
const trimmed = value.trim();
|
|
4
|
-
try {
|
|
5
|
-
return new URL(trimmed).href;
|
|
6
|
-
}
|
|
7
|
-
catch {
|
|
8
|
-
return trimmed;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
function normalizedBinding(binding) {
|
|
12
|
-
return {
|
|
13
|
-
...normalizeStorageBinding(binding),
|
|
14
|
-
webId: normalizeIdentity(binding.webId),
|
|
15
|
-
};
|
|
16
|
-
}
|
|
1
|
+
import { normalizeStorageBinding, validateWebId } from './webid-auth.js';
|
|
17
2
|
export function storageBindingMatches(expected, actual) {
|
|
18
|
-
const left =
|
|
19
|
-
const right =
|
|
3
|
+
const left = normalizeStorageBinding(expected);
|
|
4
|
+
const right = normalizeStorageBinding(actual);
|
|
20
5
|
return left.storageUrl === right.storageUrl && left.webId === right.webId;
|
|
21
6
|
}
|
|
22
7
|
export function reconcileStorageSelection(optionsOrWebId, positionalCandidates = [], positionalRemembered) {
|
|
@@ -38,8 +23,8 @@ export function reconcileStorageSelection(optionsOrWebId, positionalCandidates =
|
|
|
38
23
|
if (options.webId === undefined || options.candidates === undefined) {
|
|
39
24
|
return undefined;
|
|
40
25
|
}
|
|
41
|
-
const webId =
|
|
42
|
-
const candidates = options.candidates.map(
|
|
26
|
+
const webId = validateWebId(options.webId);
|
|
27
|
+
const candidates = options.candidates.map(normalizeStorageBinding);
|
|
43
28
|
const seenByStorage = new Map();
|
|
44
29
|
const uniqueCandidates = [];
|
|
45
30
|
for (const candidate of candidates) {
|
|
@@ -57,7 +42,7 @@ export function reconcileStorageSelection(optionsOrWebId, positionalCandidates =
|
|
|
57
42
|
}
|
|
58
43
|
const eligible = uniqueCandidates.filter((candidate) => candidate.webId === webId);
|
|
59
44
|
if (options.remembered) {
|
|
60
|
-
const remembered =
|
|
45
|
+
const remembered = normalizeStorageBinding(options.remembered);
|
|
61
46
|
const exact = eligible.find((candidate) => storageBindingMatches(candidate, remembered));
|
|
62
47
|
if (exact) {
|
|
63
48
|
return { status: 'ready', selected: { ...exact } };
|
|
@@ -72,6 +72,8 @@ export interface WebIdLoginActions {
|
|
|
72
72
|
retry?: (transaction?: WebIdLoginTransaction) => Promise<void> | void;
|
|
73
73
|
logout(options?: unknown): Promise<void> | void;
|
|
74
74
|
}
|
|
75
|
+
/** Validate the identifier without rewriting the WebID used for identity equality. */
|
|
76
|
+
export declare function validateWebId(value: string): string;
|
|
75
77
|
declare function normalizeStorageBinding(binding: StorageBinding): StorageBinding;
|
|
76
78
|
export declare function normalizeWebIdLoginRoute(input: WebIdLoginRouteDescriptor): WebIdLoginRouteDescriptor;
|
|
77
79
|
export declare function normalizeApplicationReturnTo(value: string | undefined, allowedPrefixes: readonly string[]): string | undefined;
|
|
@@ -35,13 +35,18 @@ function normalizeEndpoint(endpoint, fieldName) {
|
|
|
35
35
|
label: endpoint.label,
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
|
+
/** Validate the identifier without rewriting the WebID used for identity equality. */
|
|
39
|
+
export function validateWebId(value) {
|
|
40
|
+
if (typeof value !== 'string' || value === '' || /[\s\u0000-\u001f\u007f-\u009f]/u.test(value)) {
|
|
41
|
+
throw new TypeError('webId must be a non-empty identifier without whitespace or control characters');
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
}
|
|
38
45
|
function normalizeStorageBinding(binding) {
|
|
39
46
|
if (!binding || typeof binding.storageUrl !== 'string' || binding.storageUrl.trim() === '') {
|
|
40
47
|
throw new TypeError('selectedStorage.storageUrl must be a non-empty absolute URL');
|
|
41
48
|
}
|
|
42
|
-
|
|
43
|
-
throw new TypeError('selectedStorage.webId must be a non-empty identifier URL');
|
|
44
|
-
}
|
|
49
|
+
validateWebId(binding.webId);
|
|
45
50
|
let parsed;
|
|
46
51
|
try {
|
|
47
52
|
parsed = new URL(binding.storageUrl.trim());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@undefineds.co/xpod",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "Xpod is an extended Community Solid Server, offering rich-feature, production-level Solid Pod and identity management.",
|
|
5
5
|
"repository": "https://github.com/undefinedsco/xpod",
|
|
6
6
|
"author": "developer@undefineds.co",
|
|
@@ -370,7 +370,7 @@
|
|
|
370
370
|
}
|
|
371
371
|
},
|
|
372
372
|
"optionalDependencies": {
|
|
373
|
-
"@undefineds.co/xpod-darwin-arm64": "0.4.
|
|
373
|
+
"@undefineds.co/xpod-darwin-arm64": "0.4.8"
|
|
374
374
|
},
|
|
375
375
|
"resolutions": {
|
|
376
376
|
"@inrupt/solid-client-authn-browser": "^3.1.1",
|
|
@@ -392,6 +392,12 @@
|
|
|
392
392
|
},
|
|
393
393
|
"packageManager": "bun@1.3.12",
|
|
394
394
|
"bundledDependencies": [
|
|
395
|
+
"@undefineds.co/drizzle-solid",
|
|
396
|
+
"@undefineds.co/extensions",
|
|
397
|
+
"@undefineds.co/ai-connections",
|
|
398
|
+
"@undefineds.co/extension-sdk",
|
|
399
|
+
"@undefineds.co/shared-ui",
|
|
400
|
+
"@undefineds.co/solid-sdk",
|
|
395
401
|
"@solid/community-server",
|
|
396
402
|
"oidc-provider",
|
|
397
403
|
"@undefineds.co/models",
|