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
34 changes: 33 additions & 1 deletion src/webid/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ export function generateProfileJsonLd({ webId, name, podUri, issuer }) {
const docUrl = webId.split('#')[0];

return {
// CID v1 vocabulary is declared inline (rather than via an imported
// context URL) so JSS's JSON-LD → Turtle conneg layer can expand
// every term without fetching external contexts. Semantically
// equivalent to importing https://www.w3.org/ns/cid/v1: the IRIs
// each term expands to are the same. This keeps the profile a valid
// W3C Controlled Identifier document per LWS 1.0 (#386 Phase A).
'@context': {
'foaf': FOAF,
'solid': SOLID,
Expand All @@ -48,13 +54,39 @@ export function generateProfileJsonLd({ webId, name, podUri, issuer }) {
'isPrimaryTopicOf': { '@id': 'foaf:isPrimaryTopicOf', '@type': '@id' },
'mainEntityOfPage': { '@id': 'schema:mainEntityOfPage', '@type': '@id' },
'service': { '@id': 'cid:service', '@container': '@set' },
'serviceEndpoint': { '@id': 'cid:serviceEndpoint', '@type': '@id' }
'serviceEndpoint': { '@id': 'cid:serviceEndpoint', '@type': '@id' },
// CID v1 terms used by Phase A and prepped for Phase B (the
// standalone "add my keys" app). Declaring these now means the
// app can PATCH in verificationMethod entries without having to
// also rewrite the @context.
//
// verificationMethod: NO @type:@id — values are inline verification
// method *objects* (id/type/controller/publicKey…), not just IRI
// references. @container:@set so a single entry stays an array.
// authentication / assertionMethod: @type:@id — values reference a
// verificationMethod entry by its IRI. @container:@set for arrays.
// publicKeyJwk: @type:@json so the JWK object round-trips as a
// literal JSON value (rdf:JSON datatype). Note: JSS's Turtle
// conneg layer doesn't yet emit @type:@json literals (tracked as
// a Phase B blocker in the PR description); declaring here is
// forward-looking and spec-correct.
'controller': { '@id': 'cid:controller', '@type': '@id' },
'verificationMethod': { '@id': 'cid:verificationMethod', '@container': '@set' },
'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' }
},
'@id': webId,
'@type': ['foaf:Person', 'schema:Person'],
'foaf:name': name,
'isPrimaryTopicOf': '',
'mainEntityOfPage': '',
// CID v1 self-control: the WebID is its own controller. Phase A of
// #386 ships this triple even with no verificationMethods yet, so a
// future Phase B "add-keys" app PATCHing in verificationMethod
// entries doesn't have to also wire up controllership separately.
'controller': webId,
'inbox': `${pod}inbox/`,
'storage': pod,
'oidcIssuer': issuer,
Expand Down
47 changes: 47 additions & 0 deletions test/webid.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,53 @@ describe('WebID Profile', () => {
assert.ok(jsonLd['@id'], 'Should have @id');
});

// LWS-CID document conformance, Phase A of #386. The profile must be
// structurally a W3C Controlled Identifier document so a future
// PATCH-in-keys app (or server migration) can drop verificationMethod
// entries in without further plumbing. CID v1 vocabulary is declared
// inline rather than via context URL so JSS's conneg layer can
// expand every term without fetching external contexts — the IRIs
// are the same either way.
it('declares all six CID v1 terms in @context (#386 Phase A)', async () => {
const res = await request(profilePath);
const jsonLd = await res.json();
const ctx = jsonLd['@context'];
assert.ok(ctx, '@context required');

// All six CID terms must be declared and expand to the CID v1
// namespace. Accept either prefixed (cid:term) or full-URI
// (https://www.w3.org/ns/cid/v1#term) form.
const cidTerms = ['controller', 'verificationMethod', 'authentication', 'assertionMethod', 'publicKeyJwk', 'publicKeyMultibase'];
for (const term of cidTerms) {
const mapping = ctx[term];
assert.ok(mapping, `@context must define \`${term}\``);
const id = typeof mapping === 'string' ? mapping : mapping['@id'];
assert.match(id, new RegExp(`^(cid:${term}|https://www\\.w3\\.org/ns/cid/v1#${term})$`),
`${term} must map to the CID v1 namespace`);
}

// Container/type flags Phase B relies on:
// verificationMethod values are inline objects, NOT IRIs — must
// NOT have @type:@id (would force string-only) and SHOULD have
// @container:@set so a single entry is still an array.
assert.notStrictEqual(ctx.verificationMethod['@type'], '@id',
'verificationMethod values are objects, not IRIs');
assert.strictEqual(ctx.verificationMethod['@container'], '@set');
// authentication / assertionMethod reference verificationMethod
// entries by IRI, so @type:@id is correct.
assert.strictEqual(ctx.authentication['@type'], '@id');
assert.strictEqual(ctx.assertionMethod['@type'], '@id');
// JWK is a literal JSON value (rdf:JSON datatype) per JSON-LD 1.1.
assert.strictEqual(ctx.publicKeyJwk['@type'], '@json');
});

it('declares self-control via controller === @id (#386 Phase A)', async () => {
const res = await request(profilePath);
const jsonLd = await res.json();
assert.strictEqual(jsonLd.controller, jsonLd['@id'],
'profile must declare itself as its own controller per CID v1');
});

it('should have correct WebID URI', async () => {
const res = await request(profilePath);
const jsonLd = await res.json();
Expand Down