@metalabel/dfos-web-relay 0.8.1 → 0.9.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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +49 -42
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -49,14 +49,14 @@ serve({ port: 4444 });
49
49
  | `GET` | `/countersignatures/:cid` | Get countersignatures for an operation |
50
50
  | `GET` | `/operations/:cid/countersignatures` | Same as above (alias) |
51
51
  | `PUT` | `/content/:contentId/blob/:operationCID` | Upload blob (auth required) |
52
- | `GET` | `/content/:contentId/blob` | Download blob at head (auth + credential) |
52
+ | `GET` | `/content/:contentId/blob` | Download blob at head (standing auth, or auth + credential) |
53
53
  | `GET` | `/content/:contentId/blob/:ref` | Download blob at specific operation ref |
54
54
 
55
55
  ## Blob Authorization
56
56
 
57
57
  **Upload**: Auth token required. Caller must be the chain creator or the signer of the referenced operation (enables delegated upload).
58
58
 
59
- **Download**: Auth token required. Chain creator can download directly. Other identities must present a DFOS read credential (issued by the creator) in the `X-Credential` header.
59
+ **Download**: If a public credential (`aud: *`) exists as a standing authorization, the blob is served without authentication. Otherwise, auth token required chain creator can download directly, other identities must present a DFOS read credential (issued by the creator) in the `X-Credential` header.
60
60
 
61
61
  ## Peering
62
62
 
package/dist/index.js CHANGED
@@ -912,6 +912,33 @@ var authenticateRequest = async (authHeader, relayDID, store) => {
912
912
  return null;
913
913
  }
914
914
  };
915
+ var hasPublicStandingAuth = async (contentId, action, store) => {
916
+ const resource = `chain:${contentId}`;
917
+ const publicCreds = await store.getPublicCredentials(resource);
918
+ if (publicCreds.length === 0) return false;
919
+ const chain = await store.getContentChain(contentId);
920
+ if (!chain) return false;
921
+ const resolveIdentity = createHistoricalIdentityResolver(store);
922
+ const isRevoked = async (issuerDID, credentialCID) => store.isCredentialRevoked(issuerDID, credentialCID);
923
+ for (const credJws of publicCreds) {
924
+ try {
925
+ const cred = await verifyDFOSCredential2(credJws, { resolveIdentity });
926
+ const leafRevoked = await isRevoked(cred.iss, cred.credentialCID);
927
+ if (leafRevoked) continue;
928
+ const covers = await matchesResource(cred.att, resource, action);
929
+ if (!covers) continue;
930
+ await verifyDelegationChain(cred, {
931
+ resolveIdentity,
932
+ rootDID: chain.state.creatorDID,
933
+ isRevoked
934
+ });
935
+ return true;
936
+ } catch {
937
+ continue;
938
+ }
939
+ }
940
+ return false;
941
+ };
915
942
  var verifyContentAccess = async (options) => {
916
943
  const { credentialJWS, requestedResource, action, store, creatorDID, requesterDID } = options;
917
944
  if (requesterDID && requesterDID === creatorDID) {
@@ -919,33 +946,13 @@ var verifyContentAccess = async (options) => {
919
946
  }
920
947
  const resolveIdentity = createHistoricalIdentityResolver(store);
921
948
  const isRevoked = async (issuerDID, credentialCID) => store.isCredentialRevoked(issuerDID, credentialCID);
922
- const manifestLookup = async (manifestContentId) => {
923
- const chain = await store.getContentChain(manifestContentId);
924
- if (!chain) return [];
925
- const docCID = chain.state.currentDocumentCID;
926
- if (!docCID) return [];
927
- const blob = await store.getBlob({ creatorDID: chain.state.creatorDID, documentCID: docCID });
928
- if (!blob) return [];
929
- try {
930
- const doc = JSON.parse(new TextDecoder().decode(blob));
931
- const entries = doc["entries"];
932
- if (!entries || typeof entries !== "object") return [];
933
- return Object.values(entries).filter(
934
- (v) => typeof v === "string" && !v.startsWith("did:") && !v.startsWith("bafyrei")
935
- );
936
- } catch {
937
- return [];
938
- }
939
- };
940
949
  const publicCreds = await store.getPublicCredentials(requestedResource);
941
950
  for (const credJws of publicCreds) {
942
951
  try {
943
952
  const cred = await verifyDFOSCredential2(credJws, { resolveIdentity });
944
953
  const leafRevoked = await isRevoked(cred.iss, cred.credentialCID);
945
954
  if (leafRevoked) continue;
946
- const covers = await matchesResource(cred.att, requestedResource, action, {
947
- manifestLookup
948
- });
955
+ const covers = await matchesResource(cred.att, requestedResource, action);
949
956
  if (!covers) continue;
950
957
  await verifyDelegationChain(cred, { resolveIdentity, rootDID: creatorDID, isRevoked });
951
958
  return { granted: true, source: "public-credential", credential: cred };
@@ -966,9 +973,7 @@ var verifyContentAccess = async (options) => {
966
973
  return { granted: false, source: "none" };
967
974
  }
968
975
  }
969
- const covers = await matchesResource(cred.att, requestedResource, action, {
970
- manifestLookup
971
- });
976
+ const covers = await matchesResource(cred.att, requestedResource, action);
972
977
  if (!covers) {
973
978
  return { granted: false, source: "none" };
974
979
  }
@@ -1188,18 +1193,21 @@ var createRelay = async (options) => {
1188
1193
  app.get("/content/:contentId/documents", async (c) => {
1189
1194
  if (!contentEnabled) return c.json({ error: "content plane not available" }, 501);
1190
1195
  const contentId = c.req.param("contentId");
1191
- const auth = await authenticateRequest(c.req.header("authorization"), relayDID, store);
1192
- if (!auth) return c.json({ error: "authentication required" }, 401);
1193
1196
  const chain = await store.getContentChain(contentId);
1194
1197
  if (!chain) return c.json({ error: "not found" }, 404);
1195
- const accessError = await verifyReadAccess(
1196
- auth,
1197
- chain,
1198
- contentId,
1199
- c.req.header("x-credential"),
1200
- store
1201
- );
1202
- if (accessError) return accessError;
1198
+ const publicAccess = await hasPublicStandingAuth(contentId, "read", store);
1199
+ if (!publicAccess) {
1200
+ const auth = await authenticateRequest(c.req.header("authorization"), relayDID, store);
1201
+ if (!auth) return c.json({ error: "authentication required" }, 401);
1202
+ const accessError = await verifyReadAccess(
1203
+ auth,
1204
+ chain,
1205
+ contentId,
1206
+ c.req.header("x-credential"),
1207
+ store
1208
+ );
1209
+ if (accessError) return accessError;
1210
+ }
1203
1211
  const after = c.req.query("after");
1204
1212
  const limit = Math.min(Number(c.req.query("limit") || 100), 1e3);
1205
1213
  const result = await store.getDocuments(contentId, {
@@ -1362,12 +1370,15 @@ var jsonResponse = (body, status = 200) => new Response(JSON.stringify(body), {
1362
1370
  });
1363
1371
  var readBlob = async (params) => {
1364
1372
  const { contentId, ref, authHeader, credHeader, relayDID, store } = params;
1365
- const auth = await authenticateRequest(authHeader, relayDID, store);
1366
- if (!auth) return jsonResponse({ error: "authentication required" }, 401);
1367
1373
  const chain = await store.getContentChain(contentId);
1368
1374
  if (!chain) return jsonResponse({ error: "content chain not found" }, 404);
1369
- const credError = await verifyReadAccess(auth, chain, contentId, credHeader, store);
1370
- if (credError) return credError;
1375
+ const publicAccess = await hasPublicStandingAuth(contentId, "read", store);
1376
+ if (!publicAccess) {
1377
+ const auth = await authenticateRequest(authHeader, relayDID, store);
1378
+ if (!auth) return jsonResponse({ error: "authentication required" }, 401);
1379
+ const credError = await verifyReadAccess(auth, chain, contentId, credHeader, store);
1380
+ if (credError) return credError;
1381
+ }
1371
1382
  let documentCID = null;
1372
1383
  let operationFound = ref === "head";
1373
1384
  if (ref === "head") {
@@ -1504,10 +1515,6 @@ var MemoryRelayStore = class {
1504
1515
  tokens.push(cred.jwsToken);
1505
1516
  break;
1506
1517
  }
1507
- if (isChainRequest && att.resource.startsWith("manifest:")) {
1508
- tokens.push(cred.jwsToken);
1509
- break;
1510
- }
1511
1518
  }
1512
1519
  }
1513
1520
  return tokens;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metalabel/dfos-web-relay",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "DFOS Web Relay — verifying HTTP relay for identity chains, content chains, beacons, and content blobs",
6
6
  "license": "MIT",
@@ -52,7 +52,7 @@
52
52
  "tsup": "^8.5.1",
53
53
  "tsx": "^4.20.3",
54
54
  "vitest": "^4.1.2",
55
- "@metalabel/dfos-protocol": "0.8.1"
55
+ "@metalabel/dfos-protocol": "0.9.0"
56
56
  },
57
57
  "scripts": {
58
58
  "build": "tsup",