Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 13 additions & 1 deletion src/webid/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,19 @@ export function generateProfileJsonLd({ webId, name, podUri, issuer }) {
'authentication': { '@id': 'cid:authentication', '@type': '@id', '@container': '@set' },
'assertionMethod': { '@id': 'cid:assertionMethod', '@type': '@id', '@container': '@set' },
'publicKeyJwk': { '@id': 'cid:publicKeyJwk', '@type': '@json' },
'publicKeyMultibase': { '@id': 'cid:publicKeyMultibase' }
'publicKeyMultibase': { '@id': 'cid:publicKeyMultibase' },
// CID v1 verificationMethod *class* names (#417). Without these
// term mappings, an app PATCHing in a VM with `type: "Multikey"`
// (the spec-example shape) emits a bare relative-IRI `<Multikey>`
// when JSS conneg-converts the profile to Turtle — which then
// resolves against the document's base URL to a fictional class
// like `<pod>/profile/Multikey`. Mapping the class names here
// means the bare term `"Multikey"` in JSON-LD expands correctly
// (cid:Multikey → https://www.w3.org/ns/cid/v1#Multikey) for both
// JSON-LD processors AND our Turtle conneg layer. Naive JSON
// readers comparing `type === "Multikey"` continue to work.
'Multikey': 'cid:Multikey',
'JsonWebKey': 'cid:JsonWebKey'
},
'@id': webId,
'@type': ['foaf:Person', 'schema:Person'],
Expand Down
77 changes: 77 additions & 0 deletions test/webid.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,30 @@ describe('WebID Profile', () => {
assert.strictEqual(ctx.publicKeyJwk['@type'], '@json');
});

it('declares CID v1 class names (Multikey, JsonWebKey) as flat aliases (#417)', async () => {
// Without these mappings, an app PATCHing in a VM with the
// spec-example shape `{type: "Multikey", ...}` produces a bare
// relative-IRI `<Multikey>` in the Turtle conneg output, which
// resolves to a fictional class on the pod's own host (e.g.
// `<pod>/profile/Multikey` instead of `cid:Multikey`).
//
// The flat-alias shape (`"Multikey": "cid:Multikey"`) makes
// bare-term emission work correctly through both JSON-LD
// expansion AND our Turtle conneg layer — and matches the
// "JSON-LD with flat context aliases" pattern consumers like
// LOSOS / LION rely on.
const res = await request(profilePath);
const jsonLd = await res.json();
const ctx = jsonLd['@context'];
for (const cls of ['Multikey', 'JsonWebKey']) {
const mapping = ctx[cls];
assert.ok(mapping, `@context must define class alias \`${cls}\``);
const id = typeof mapping === 'string' ? mapping : mapping['@id'];
assert.match(id, new RegExp(`^(cid:${cls}|https://www\\.w3\\.org/ns/cid/v1#${cls})$`),
`${cls} must map to the CID v1 namespace`);
}
});

it('declares self-control via controller === @id (#386 Phase A)', async () => {
const res = await request(profilePath);
const jsonLd = await res.json();
Expand Down Expand Up @@ -216,6 +240,59 @@ describe('WebID Profile — Turtle conneg (#320)', () => {
await stopTestServer();
});

it('Turtle conneg: generated profile @context expands bare Multikey/JsonWebKey terms (#417)', async () => {
// Combine the production profile generator's @context with a
// synthetic VM (the spec-example shape `{type: "Multikey", ...}`)
// and run it through the same conneg path the live profile
// would. Asserts: the bare-term type expands to the CID v1
// namespace, not to a relative IRI that resolves to a fake
// class on the pod's host.
const { generateProfile } = await import('../src/webid/profile.js');
const { fromJsonLd } = await import('../src/rdf/conneg.js');
const webId = 'https://example.test/profile/card.jsonld#me';
const profile = generateProfile({
webId,
name: 'mk-test',
podUri: 'https://example.test/',
issuer: 'https://example.test/',
});
// Inject a Multikey VM authored with the spec-example bare-term
// type — this is the shape the bug surfaces on.
const vmId = webId.replace('#me', '#nostr-key-1');
profile.verificationMethod = [{
id: vmId,
type: 'Multikey',
controller: webId,
publicKeyMultibase: 'fe70102de7ec0123456789abcdef0123456789abcdef0123456789abcdef0123456789ab',
}];
profile.authentication = [vmId];

const { content: ttl } = await fromJsonLd(profile, 'text/turtle', 'https://example.test/', true);

// Pre-#417: would emit `a <Multikey>` (relative — resolves to
// `https://example.test/profile/Multikey`).
// After #417: the @context maps `Multikey -> cid:Multikey`, so
// the converter expands the bare term to the CID v1 IRI.
assert.ok(
ttl.includes('cid:Multikey') || ttl.includes('cid/v1#Multikey'),
`Turtle must emit a CID-namespaced Multikey class, got:\n${ttl}`,
);
assert.ok(
!/\ba\s+<Multikey>\s*[;.]/.test(ttl),
`Turtle must NOT emit bare <Multikey> (resolves to fictional class), got:\n${ttl}`,
);
// Don't regress #416: the VM block + publicKeyMultibase must
// still survive the conversion.
assert.ok(
ttl.includes('publicKeyMultibase') || ttl.includes('cid/v1#publicKeyMultibase'),
`Turtle must include cid:publicKeyMultibase, got:\n${ttl}`,
);
assert.ok(
ttl.includes('fe70102de7ec'),
`Turtle must include the publicKeyMultibase value, got:\n${ttl}`,
);
});

it('Turtle variant includes cid:service with lws:OpenIdProvider and serviceEndpoint', async () => {
const res = await request('/webidturtletest/profile/card.jsonld', {
headers: { Accept: 'text/turtle' }
Expand Down