Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ee15db0
Scaffold DID Resolution HTTPS Binding server.
djscruggs May 26, 2026
2c04b0b
Complete HTTPS binding implementation with full test suite.
djscruggs May 26, 2026
299e49e
Add did:web test suite with nock mock and live tests.
djscruggs May 26, 2026
97285c9
Implement service endpoint dereferencing with HTTP 303 redirect.
djscruggs May 26, 2026
3f67811
Update README to match implementation.
djscruggs May 26, 2026
9584c02
Remove 'All rights reserved.' from copyright headers.
djscruggs May 26, 2026
17ce89b
Fix headers.js to use supported set rather than void hack.
djscruggs May 26, 2026
2f0bdba
Fix code smells: centralize classifyError, guard decodeURIComponent, …
djscruggs May 26, 2026
804740c
Remove CLAUDE.md from tracking; add working docs to .gitignore.
djscruggs May 26, 2026
2810889
Add Node.js CI workflow.
djscruggs May 26, 2026
59db58e
Address PR review feedback from davidlehn.
djscruggs May 27, 2026
be54e7d
Upgrade mocha to v11 for Node.js 26 compatibility.
djscruggs May 27, 2026
65d57eb
Align error format and DID validation with W3C test suite.
djscruggs May 28, 2026
1609431
Fix MUST-level spec gaps: 406 format and deactivated handling.
djscruggs May 28, 2026
c5ed539
Update README with error format, deactivation, and conformance table.
djscruggs May 28, 2026
60b5645
Update copyright year to 2026 across all source files.
djscruggs May 28, 2026
fa98f8f
Fix JSDoc @returns declarations and description sentences.
djscruggs May 28, 2026
b182754
Fix remaining lint errors: sentence period and line length.
djscruggs May 28, 2026
0deb7a7
Fix contentType in resolution and dereferencing metadata.
djscruggs May 28, 2026
8010f73
Update README: all 35 test suite tests pass.
djscruggs May 28, 2026
e81371c
Address PR review feedback from davidlehn and msporny.
djscruggs May 28, 2026
6b219e3
Fix CI failures: lint errors and test suite after spec changes.
djscruggs May 28, 2026
92e0148
Fix Accept header matching to use exact token comparison.
djscruggs May 28, 2026
742b62e
Link did-io typed error issue in classifyError comment.
djscruggs May 28, 2026
30e9a1f
Fix JSDoc sentence capitalization in dereferenceHandler.
djscruggs May 28, 2026
b6ff396
fix: Align driver-not-found error name with did-io NotSupportedError.
djscruggs May 29, 2026
3e7ac54
Use did-io typed NotSupportedError for driver-not-found.
djscruggs May 29, 2026
881eb18
Fix JSDoc complete-sentence lint warning in dereference.
djscruggs May 29, 2026
4630197
Remove service endpoint dereferencing to close SSRF surface.
djscruggs May 30, 2026
41cebee
docs: Align README content types with implementation.
djscruggs Jun 1, 2026
02e6508
Fix decoding, error mapping, and dereferencing format.
djscruggs Jun 12, 2026
ed45685
Move tests to test/mocha per DB convention.
djscruggs Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Port the server listens on
PORT=8080

# Host the server binds to
HOST=0.0.0.0
34 changes: 34 additions & 0 deletions .github/workflows/main.yaml
Comment thread
djscruggs marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Node.js CI

on: [push, pull_request]

Comment thread
djscruggs marked this conversation as resolved.
permissions: {}

jobs:
lint:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version: 22.x
- run: npm install
- name: Run eslint
run: npm run lint
test-node:
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
node-version: [22.x, 24.x, 26.x]
steps:
- uses: actions/checkout@v6
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- name: Run tests with Node.js ${{ matrix.node-version }}
run: npm test
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
SPEC.md
ARCHITECTURE.md
CLAUDE.md
SMELLS*.md
.env
node_modules/
dist/
coverage/
.fastembed_cache/
32 changes: 0 additions & 32 deletions CLAUDE.md

This file was deleted.

28 changes: 28 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
BSD 3-Clause License

Copyright (c) 2026 Digital Bazaar, Inc.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
110 changes: 90 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ object that describes the entity: its public keys, authentication methods, and s
endpoints.

This server exposes a single HTTP endpoint that accepts a DID (or DID URL), resolves
it to a DID Document using [`did-io`](https://github.com/digitalbazaar/did-io), and
it to a DID Document using [`@digitalbazaar/did-io`](https://github.com/digitalbazaar/did-io), and
returns the result in the format the client requests.

```
Expand All @@ -27,7 +27,7 @@ Client did-resolver did-io
| | |-- driver lookup
| | |-- fetch/derive doc
| |<-- DID Document --------------|
|<-- 200 application/did+ld+json|
|<-- 200 application/did ------|
```

### Endpoints
Expand All @@ -46,7 +46,7 @@ POST /1.0/identifiers/{did}

| Accept Header | Response |
|---|---|
| `application/did+ld+json` | DID Document only |
| `application/did` | DID Document only |
| `application/did-resolution` | Full result: document + resolution metadata + document metadata |
| _(default)_ | DID Document only |

Expand All @@ -58,14 +58,19 @@ GET /1.0/identifiers/{did-url}

A DID URL extends a DID with a path, query, or fragment:
- `did:key:z6Mk...#key-1` → returns a specific verification method
- `did:web:example.com/user/alice?service=files` → follows the service endpoint

Service endpoint dereferencing (`?service=`) is intentionally not supported:
resolving caller-supplied endpoints would turn the server into an outbound
HTTP request engine (SSRF / DDoS amplification surface). Requests including
`?service=` are rejected with `501 Not Implemented`. Read the service
endpoint from the resolved DID document directly instead.

**Response formats** (controlled by `Accept` header):

| Accept Header | Response |
|---|---|
| `application/did-url-dereferencing` | Full result: content + dereferencing metadata |
| `text/uri-list` | HTTP 303 redirect to the resource URL |
| `application/did-url-dereferencing` | Dereferencing result: `dereferencingMetadata` + `contentStream` + `contentMetadata` |
| `application/did-resolution` | Full result: content + resolution metadata |
| _(default)_ | The dereferenced resource directly |

### HTTP Status Codes
Expand All @@ -82,6 +87,38 @@ Per the [DID Resolution spec](https://w3c.github.io/did-resolution/#bindings-htt
| Internal resolver error | `500 Internal Server Error` |
| DID method not supported | `501 Not Implemented` |

### Error Format

When `Accept: application/did-resolution` is requested, error responses are
conformant resolution results with an RFC 9457-style error object in
`didResolutionMetadata`:

```json
{
"@context": "https://w3id.org/did-resolution/v1",
"didDocument": null,
"didDocumentMetadata": {},
"didResolutionMetadata": {
"error": {
"type": "https://www.w3.org/ns/did#METHOD_NOT_SUPPORTED"
}
}
}
```

Error type URIs follow the W3C DID namespace:
`https://www.w3.org/ns/did#INVALID_DID`, `#NOT_FOUND`, `#METHOD_NOT_SUPPORTED`,
`#REPRESENTATION_NOT_SUPPORTED`, `#INTERNAL_ERROR`, etc.

### Deactivated DIDs

If a resolved DID document contains `"deactivated": true`, the server returns
`410 Gone` with a null `didDocument` and `"deactivated": true` in
`didDocumentMetadata`. Neither `did:key` nor `did:web` support deactivation
(both are static/derived methods with no registry). Ledger-based methods such
as `did:veres-one` or `did:ion` do — register such a driver to exercise this
path.

### Supported DID Methods

| Method | Description |
Expand All @@ -95,7 +132,7 @@ Additional methods can be added by registering a driver with the `CachedResolver
### Architecture

```
src/
lib/
├── index.js # Entry point — wires server + resolver
├── server.js # HTTP server, route registration
├── resolver.js # did-io CachedResolver instance
Expand All @@ -110,8 +147,8 @@ src/
└── headers.js # Content-Type helpers
```

The server is built on Node.js with no framework dependencies beyond what's needed for
routing. It uses the DB-standard ESM module format throughout.
The server uses [Express](https://expressjs.com/) for routing and the DB-standard
ESM module format throughout.

**Resolution flow:**

Expand All @@ -132,17 +169,17 @@ npm install

```bash
# Start the server (default port 8080)
node src/index.js
node lib/index.js

# Resolve a DID
curl https://localhost:8080/1.0/identifiers/did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK
curl http://localhost:8080/1.0/identifiers/did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH

# Get full resolution result
curl -H "Accept: application/did-resolution" \
https://localhost:8080/1.0/identifiers/did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK
http://localhost:8080/1.0/identifiers/did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH

# Dereference a DID URL (specific verification method)
curl https://localhost:8080/1.0/identifiers/did:key:z6Mk...%23key-1
# Dereference a DID URL fragment (specific verification method)
curl http://localhost:8080/1.0/identifiers/did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH%23z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH
```

## Configuration
Expand All @@ -159,15 +196,26 @@ curl https://localhost:8080/1.0/identifiers/did:key:z6Mk...%23key-1
npm install @digitalbazaar/did-method-example
```

2. Create `src/drivers/example.js`:
2. Create `lib/drivers/example.js`:
```js
import {driver} from '@digitalbazaar/did-method-example';

export const exampleDriver = driver();
```
If the method uses multikey cryptography, also register the key suite:
```js
import * as ExampleDriver from '@digitalbazaar/did-method-example';
export const driver = ExampleDriver.driver();
import {Ed25519VerificationKey2020} from
'@digitalbazaar/ed25519-verification-key-2020';

exampleDriver.use({
multibaseMultikeyHeader: 'z6Mk',
fromMultibase: Ed25519VerificationKey2020.from
});
```

3. Register it in `src/resolver.js`:
3. Register it in `lib/resolver.js`:
```js
import {driver as exampleDriver} from './drivers/example.js';
import {exampleDriver} from './drivers/example.js';
resolver.use(exampleDriver);
```

Expand All @@ -180,11 +228,33 @@ npm test # Run test suite
npm run lint # Lint with @digitalbazaar/eslint-config
```

## Spec Conformance

All 35 tests in the
[w3c-ccg/did-resolution-mocha-test-suite](https://github.com/w3c-ccg/did-resolution-mocha-test-suite)
pass against this implementation.

| Requirement | Status |
|---|---|
| `GET /1.0/identifiers/{did}` binding | ✅ |
| `POST /1.0/identifiers/{did}` binding | ✅ |
| Resolution result shape (`didDocument`, `didResolutionMetadata`, `didDocumentMetadata`) | ✅ |
| `didResolutionMetadata.contentType` present on success | ✅ |
| `Content-Type` header matches `didResolutionMetadata.contentType` | ✅ |
| RFC 9457 error objects with W3C DID namespace URIs | ✅ |
| `INVALID_DID` + 400 for malformed DID input | ✅ |
| `NOT_FOUND` + 404 | ✅ |
| `METHOD_NOT_SUPPORTED` + 501 | ✅ |
| `REPRESENTATION_NOT_SUPPORTED` + 406 | ✅ |
| Deactivated DID → 410 + null document | ✅ (requires a method that supports deactivation) |
| DID URL dereferencing result shape | ✅ |

## Spec References

- [W3C DID Resolution](https://w3c.github.io/did-resolution/)
- [HTTPS Binding](https://w3c.github.io/did-resolution/#bindings-https)
- [did-io](https://github.com/digitalbazaar/did-io)
- [DID Resolution Mocha Test Suite](https://github.com/w3c-ccg/did-resolution-mocha-test-suite)
- [@digitalbazaar/did-io](https://github.com/digitalbazaar/did-io)
- [Danube Tech Universal Resolver](https://github.com/decentralized-identity/universal-resolver) (reference implementation)

## License
Expand Down
22 changes: 22 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*!
* Copyright (c) 2026 Digital Bazaar, Inc.
*/
import config from '@digitalbazaar/eslint-config/node-recommended';

export default [
...config,
{
// Mocha test globals
files: ['test/**/*.js'],
languageOptions: {
globals: {
before: 'readonly',
after: 'readonly',
beforeEach: 'readonly',
afterEach: 'readonly',
describe: 'readonly',
it: 'readonly'
}
}
}
];
14 changes: 14 additions & 0 deletions lib/drivers/key.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*!
* Copyright (c) 2026 Digital Bazaar, Inc.
*/
import {driver} from '@digitalbazaar/did-method-key';
import {Ed25519VerificationKey2020} from
'@digitalbazaar/ed25519-verification-key-2020';

export const keyDriver = driver();

// Register the Ed25519 2020 suite so z6Mk... keys can be resolved.
keyDriver.use({
multibaseMultikeyHeader: 'z6Mk',
fromMultibase: Ed25519VerificationKey2020.from
});
15 changes: 15 additions & 0 deletions lib/drivers/web.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*!
* Copyright (c) 2026 Digital Bazaar, Inc.
*/
import {driver} from '@digitalbazaar/did-method-web';
import {Ed25519VerificationKey2020} from
'@digitalbazaar/ed25519-verification-key-2020';

export const webDriver = driver();

// Register the Ed25519 2020 suite so z6Mk... keys in did:web documents
// can be resolved to their full key material.
webDriver.use({
multibaseMultikeyHeader: 'z6Mk',
fromMultibase: Ed25519VerificationKey2020.from
});
Loading