Skip to content

Commit

Permalink
ProtocolURI path component in DRLs MUST be base64url encoded (#168)
Browse files Browse the repository at this point in the history
Through testing apps and DRLs we've concluded that base64url encoding
the protocol URI in the path segments is best.

We particularly ran into this issue with `nginx`
https://trac.nginx.org/nginx/ticket/786 where nginx decides to decode
then re-encode the path segment which removes one of the slashes from
the protocol URI.
  • Loading branch information
LiranCohen authored Sep 19, 2024
1 parent 295be4a commit 1e931e5
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 69 deletions.
103 changes: 94 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@web5/dwn-server",
"type": "module",
"version": "0.4.10",
"version": "0.5.0",
"files": [
"dist",
"src"
Expand All @@ -28,7 +28,9 @@
"dependencies": {
"@tbd54566975/dwn-sdk-js": "0.4.7",
"@tbd54566975/dwn-sql-store": "0.6.7",
"@web5/common": "^1.0.2",
"@web5/crypto": "^1.0.3",
"@web5/dids": "^1.1.3",
"better-sqlite3": "^8.5.0",
"body-parser": "^1.20.2",
"bytes": "3.1.2",
Expand Down Expand Up @@ -61,7 +63,6 @@
"@types/ws": "8.5.10",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"@web5/dids": "^1.1.3",
"c8": "8.0.1",
"chai": "4.3.6",
"chai-as-promised": "7.1.1",
Expand Down Expand Up @@ -108,7 +109,7 @@
},
"overrides": {
"express": {
"serve-static": "^1.16.2"
"serve-static": "^1.16.2"
}
}
}
102 changes: 61 additions & 41 deletions src/http-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { jsonRpcRouter } from './json-rpc-api.js';
import { Web5ConnectServer } from './web5-connect/web5-connect-server.js';
import { createJsonRpcErrorResponse, JsonRpcErrorCodes } from './lib/json-rpc.js';
import { requestCounter, responseHistogram } from './metrics.js';
import { Convert } from '@web5/common';


export class HttpApi {
Expand Down Expand Up @@ -49,7 +50,7 @@ export class HttpApi {
httpApi.#packageInfo.version = packageJson.version;
httpApi.#packageInfo.sdkVersion = packageJson.dependencies ? packageJson.dependencies['@tbd54566975/dwn-sdk-js'] : undefined;
} catch (error: any) {
log.error('could not read `package.json` for version info', error);
log.info('could not read `package.json` for version info', error);
}

httpApi.#config = config;
Expand Down Expand Up @@ -150,58 +151,77 @@ export class HttpApi {
return res.status(400).send('protocol path is required');
}

const queryOptions = { filter: {} } as any;
for (const param in req.query) {
const keys = param.split('.');
const lastKey = keys.pop();
const lastLevelObject = keys.reduce((obj, key) => obj[key] = obj[key] || {}, queryOptions)
lastLevelObject[lastKey] = req.query[param];
}
// wrap request in a try-catch block to handle any unexpected errors
try {
const queryOptions = { filter: {} } as any;
for (const param in req.query) {
const keys = param.split('.');
const lastKey = keys.pop();
const lastLevelObject = keys.reduce((obj, key) => obj[key] = obj[key] || {}, queryOptions)
lastLevelObject[lastKey] = req.query[param];
}

queryOptions.filter.protocol = req.params.protocol;
queryOptions.filter.protocolPath = req.params[0].replace(leadTailSlashRegex, '');
// the protocol path segment is base64url encoded, as the actual protocol is a URL
// we decode it here in order to filter for the correct protocol
const protocol = Convert.base64Url(req.params.protocol).toString()
queryOptions.filter.protocol = protocol;
queryOptions.filter.protocolPath = req.params[0].replace(leadTailSlashRegex, '');

const query = await RecordsQuery.create({
filter: queryOptions.filter,
pagination: { limit: 1 },
dateSort: DateSort.PublishedDescending
});
const query = await RecordsQuery.create({
filter: queryOptions.filter,
pagination: { limit: 1 },
dateSort: DateSort.PublishedDescending
});

const { entries, status } = await this.dwn.processMessage(req.params.did, query.message);
const { entries, status } = await this.dwn.processMessage(req.params.did, query.message);

if (status.code === 200) {
if (entries[0]) {
const record = await RecordsRead.create({
filter: { recordId: entries[0].recordId },
});
const reply = await this.dwn.processMessage(req.params.did, record.toJSON());
return readReplyHandler(res, reply);
} else {
if (status.code === 200) {
if (entries[0]) {
const record = await RecordsRead.create({
filter: { recordId: entries[0].recordId },
});
const reply = await this.dwn.processMessage(req.params.did, record.toJSON());
return readReplyHandler(res, reply);
} else {
return res.sendStatus(404);
}
} else if (status.code === 401) {
return res.sendStatus(404);
} else {
return res.sendStatus(status.code);
}
} else if (status.code === 401) {
return res.sendStatus(404);
} else {
return res.sendStatus(status.code);
} catch(error) {
log.error(`Error processing request: ${decodeURI(req.url)}`, error);
return res.sendStatus(400);
}
})

this.#api.get('/:did/read/protocols/:protocol', async (req, res) => {
const query = await ProtocolsQuery.create({
filter: { protocol: req.params.protocol }
});
const { entries, status } = await this.dwn.processMessage(req.params.did, query.message);
if (status.code === 200) {
if (entries.length) {
res.status(status.code);
res.json(entries[0]);
} else {
// wrap request in a try-catch block to handle any unexpected errors
try {

// the protocol segment is base64url encoded, as the actual protocol is a URL
// we decode it here in order to filter for the correct protocol
const protocol = Convert.base64Url(req.params.protocol).toString()
const query = await ProtocolsQuery.create({
filter: { protocol }
});
const { entries, status } = await this.dwn.processMessage(req.params.did, query.message);
if (status.code === 200) {
if (entries.length) {
res.status(status.code);
res.json(entries[0]);
} else {
return res.sendStatus(404);
}
} else if (status.code === 401) {
return res.sendStatus(404);
} else {
return res.sendStatus(status.code);
}
} else if (status.code === 401) {
return res.sendStatus(404);
} else {
return res.sendStatus(status.code);
} catch(error) {
log.error(`Error processing request: ${decodeURI(req.url)}`, error);
return res.sendStatus(400);
}
})

Expand Down
Loading

0 comments on commit 1e931e5

Please sign in to comment.