Skip to content

Commit 199ecf0

Browse files
committed
refactor(identity): stop normalizing service array
1 parent e870852 commit 199ecf0

File tree

5 files changed

+180
-120
lines changed

5 files changed

+180
-120
lines changed

.changeset/cruel-rockets-yell.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@atcute/identity': patch
3+
---
4+
5+
stop normalizing service array

packages/identity/identity/lib/typedefs.test.ts

+89-99
Original file line numberDiff line numberDiff line change
@@ -29,34 +29,30 @@ describe('didDocument', () => {
2929
],
3030
});
3131

32-
expect(doc).toMatchInlineSnapshot(`
33-
{
34-
"@context": [
35-
"https://www.w3.org/ns/did/v1",
36-
"https://w3id.org/security/multikey/v1",
37-
"https://w3id.org/security/suites/secp256k1-2019/v1",
38-
],
39-
"alsoKnownAs": [
40-
"at://mary.my.id",
41-
],
42-
"id": "did:plc:ia76kvnndjutgedggx2ibrem",
43-
"service": [
44-
{
45-
"id": "did:plc:ia76kvnndjutgedggx2ibrem#atproto_pds",
46-
"serviceEndpoint": "https://porcini.us-east.host.bsky.network",
47-
"type": "AtprotoPersonalDataServer",
48-
},
49-
],
50-
"verificationMethod": [
51-
{
52-
"controller": "did:plc:ia76kvnndjutgedggx2ibrem",
53-
"id": "did:plc:ia76kvnndjutgedggx2ibrem#atproto",
54-
"publicKeyMultibase": "zQ3shuqiNQXNGKBBbNvPhcaZy8DjP3BF3yhmSeAjFXQjgPJrG",
55-
"type": "Multikey",
56-
},
57-
],
58-
}
59-
`);
32+
expect(doc).toEqual({
33+
'@context': [
34+
'https://www.w3.org/ns/did/v1',
35+
'https://w3id.org/security/multikey/v1',
36+
'https://w3id.org/security/suites/secp256k1-2019/v1',
37+
],
38+
alsoKnownAs: ['at://mary.my.id'],
39+
id: 'did:plc:ia76kvnndjutgedggx2ibrem',
40+
service: [
41+
{
42+
id: '#atproto_pds',
43+
serviceEndpoint: 'https://porcini.us-east.host.bsky.network',
44+
type: 'AtprotoPersonalDataServer',
45+
},
46+
],
47+
verificationMethod: [
48+
{
49+
controller: 'did:plc:ia76kvnndjutgedggx2ibrem',
50+
id: 'did:plc:ia76kvnndjutgedggx2ibrem#atproto',
51+
publicKeyMultibase: 'zQ3shuqiNQXNGKBBbNvPhcaZy8DjP3BF3yhmSeAjFXQjgPJrG',
52+
type: 'Multikey',
53+
},
54+
],
55+
});
6056
});
6157

6258
it('parses a did:plc document containing a labeler', () => {
@@ -96,45 +92,41 @@ describe('didDocument', () => {
9692
],
9793
});
9894

99-
expect(doc).toMatchInlineSnapshot(`
100-
{
101-
"@context": [
102-
"https://www.w3.org/ns/did/v1",
103-
"https://w3id.org/security/multikey/v1",
104-
"https://w3id.org/security/suites/secp256k1-2019/v1",
105-
],
106-
"alsoKnownAs": [
107-
"at://pronouns.diy",
108-
],
109-
"id": "did:plc:wkoofae5uytcm7bjncmev6n6",
110-
"service": [
111-
{
112-
"id": "did:plc:wkoofae5uytcm7bjncmev6n6#atproto_pds",
113-
"serviceEndpoint": "https://pds.bsky.mom",
114-
"type": "AtprotoPersonalDataServer",
115-
},
116-
{
117-
"id": "did:plc:wkoofae5uytcm7bjncmev6n6#atproto_labeler",
118-
"serviceEndpoint": "https://api.pronouns.diy",
119-
"type": "AtprotoLabeler",
120-
},
121-
],
122-
"verificationMethod": [
123-
{
124-
"controller": "did:plc:wkoofae5uytcm7bjncmev6n6",
125-
"id": "did:plc:wkoofae5uytcm7bjncmev6n6#atproto",
126-
"publicKeyMultibase": "zQ3sho8kubdqeS5wbxPDpNBBqg2tvJTKF1jovJKzQzhu4S8fH",
127-
"type": "Multikey",
128-
},
129-
{
130-
"controller": "did:plc:wkoofae5uytcm7bjncmev6n6",
131-
"id": "did:plc:wkoofae5uytcm7bjncmev6n6#atproto_label",
132-
"publicKeyMultibase": "zQ3shQo2ZK9ZwNRxkEM1sSkpJKfx1NN6WWcvtMTDyJeCwPB7o",
133-
"type": "Multikey",
134-
},
135-
],
136-
}
137-
`);
95+
expect(doc).toEqual({
96+
'@context': [
97+
'https://www.w3.org/ns/did/v1',
98+
'https://w3id.org/security/multikey/v1',
99+
'https://w3id.org/security/suites/secp256k1-2019/v1',
100+
],
101+
alsoKnownAs: ['at://pronouns.diy'],
102+
id: 'did:plc:wkoofae5uytcm7bjncmev6n6',
103+
service: [
104+
{
105+
id: '#atproto_pds',
106+
serviceEndpoint: 'https://pds.bsky.mom',
107+
type: 'AtprotoPersonalDataServer',
108+
},
109+
{
110+
id: '#atproto_labeler',
111+
serviceEndpoint: 'https://api.pronouns.diy',
112+
type: 'AtprotoLabeler',
113+
},
114+
],
115+
verificationMethod: [
116+
{
117+
controller: 'did:plc:wkoofae5uytcm7bjncmev6n6',
118+
id: 'did:plc:wkoofae5uytcm7bjncmev6n6#atproto',
119+
publicKeyMultibase: 'zQ3sho8kubdqeS5wbxPDpNBBqg2tvJTKF1jovJKzQzhu4S8fH',
120+
type: 'Multikey',
121+
},
122+
{
123+
controller: 'did:plc:wkoofae5uytcm7bjncmev6n6',
124+
id: 'did:plc:wkoofae5uytcm7bjncmev6n6#atproto_label',
125+
publicKeyMultibase: 'zQ3shQo2ZK9ZwNRxkEM1sSkpJKfx1NN6WWcvtMTDyJeCwPB7o',
126+
type: 'Multikey',
127+
},
128+
],
129+
});
138130
});
139131

140132
it('parses a did:web document', () => {
@@ -169,37 +161,35 @@ describe('didDocument', () => {
169161
],
170162
});
171163

172-
expect(doc).toMatchInlineSnapshot(`
173-
{
174-
"@context": [
175-
"https://www.w3.org/ns/did/v1",
176-
"https://w3id.org/security/multikey/v1",
177-
"https://w3id.org/security/suites/secp256k1-2019/v1",
178-
],
179-
"alsoKnownAs": [
180-
"at://didd.uk",
181-
"did:plc:kv7sv4lynbv5s6gdhn5r5vcw",
182-
"web+ap://bsky.brid.gy/@ducky.ws",
183-
"web+ap://fedia.social/@theducky",
184-
"https://t.me/theducky",
185-
],
186-
"id": "did:web:didd.uk",
187-
"service": [
188-
{
189-
"id": "did:web:didd.uk#atproto_pds",
190-
"serviceEndpoint": "https://zio.blue",
191-
"type": "AtprotoPersonalDataServer",
192-
},
193-
],
194-
"verificationMethod": [
195-
{
196-
"controller": "did:web:didd.uk",
197-
"id": "did:web:didd.uk#atproto",
198-
"publicKeyMultibase": "zQ3shYRepkfnXhDjKBmvBVNtu2tswxPjjTDgKWTUcuFdt7xtH",
199-
"type": "Multikey",
200-
},
201-
],
202-
}
203-
`);
164+
expect(doc).toEqual({
165+
'@context': [
166+
'https://www.w3.org/ns/did/v1',
167+
'https://w3id.org/security/multikey/v1',
168+
'https://w3id.org/security/suites/secp256k1-2019/v1',
169+
],
170+
alsoKnownAs: [
171+
'at://didd.uk',
172+
'did:plc:kv7sv4lynbv5s6gdhn5r5vcw',
173+
'web+ap://bsky.brid.gy/@ducky.ws',
174+
'web+ap://fedia.social/@theducky',
175+
'https://t.me/theducky',
176+
],
177+
id: 'did:web:didd.uk',
178+
service: [
179+
{
180+
id: '#atproto_pds',
181+
serviceEndpoint: 'https://zio.blue',
182+
type: 'AtprotoPersonalDataServer',
183+
},
184+
],
185+
verificationMethod: [
186+
{
187+
controller: 'did:web:didd.uk',
188+
id: 'did:web:didd.uk#atproto',
189+
publicKeyMultibase: 'zQ3shYRepkfnXhDjKBmvBVNtu2tswxPjjTDgKWTUcuFdt7xtH',
190+
type: 'Multikey',
191+
},
192+
],
193+
});
204194
});
205195
});

packages/identity/identity/lib/typedefs.ts

+11-14
Original file line numberDiff line numberDiff line change
@@ -114,25 +114,26 @@ export const didDocument: v.Type<t.DidDocument> = v
114114
.chain((input) => {
115115
const { id: did, service: services } = input;
116116

117-
let newServices: t.Service[] | undefined;
117+
if (services?.length) {
118+
const len = services.length;
119+
const identifiers = new Array(len);
118120

119-
if (services) {
120-
for (let i = 0, len = services.length; i < len; i++) {
121+
for (let i = 0; i < len; i++) {
121122
const service = services[i];
122123

123124
let id = service.id;
124125
if (id[0] === '#') {
125126
id = did + id;
126-
127-
if (newServices !== undefined) {
128-
newServices[i] = { ...service, id };
129-
} else {
130-
newServices = services.with(i, { ...service, id });
131-
}
132127
}
133128

129+
identifiers[i] = id;
130+
}
131+
132+
for (let i = 0; i < len; i++) {
133+
const id = identifiers[i];
134+
134135
for (let j = 0; j < i; j++) {
135-
if (id === (newServices ?? services)[j].id) {
136+
if (id === identifiers[j]) {
136137
return v.err({
137138
message: `duplicate "${id}" service`,
138139
path: ['service', i, 'id'],
@@ -142,9 +143,5 @@ export const didDocument: v.Type<t.DidDocument> = v
142143
}
143144
}
144145

145-
if (newServices !== undefined) {
146-
return v.ok({ ...input, service: newServices });
147-
}
148-
149146
return v.ok(input);
150147
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { describe, it, expect } from 'bun:test';
2+
3+
import { didDocument } from './typedefs.js';
4+
import {
5+
getAtprotoLabelerVerificationMaterial,
6+
getAtprotoVerificationMaterial,
7+
getLabelerEndpoint,
8+
getPdsEndpoint,
9+
} from './utils.js';
10+
11+
const PRONOUNS_LABELER_DID_DOC = didDocument.parse({
12+
'@context': [
13+
'https://www.w3.org/ns/did/v1',
14+
'https://w3id.org/security/multikey/v1',
15+
'https://w3id.org/security/suites/secp256k1-2019/v1',
16+
],
17+
id: 'did:plc:wkoofae5uytcm7bjncmev6n6',
18+
alsoKnownAs: ['at://pronouns.diy'],
19+
verificationMethod: [
20+
{
21+
id: 'did:plc:wkoofae5uytcm7bjncmev6n6#atproto',
22+
type: 'Multikey',
23+
controller: 'did:plc:wkoofae5uytcm7bjncmev6n6',
24+
publicKeyMultibase: 'zQ3sho8kubdqeS5wbxPDpNBBqg2tvJTKF1jovJKzQzhu4S8fH',
25+
},
26+
{
27+
id: 'did:plc:wkoofae5uytcm7bjncmev6n6#atproto_label',
28+
type: 'Multikey',
29+
controller: 'did:plc:wkoofae5uytcm7bjncmev6n6',
30+
publicKeyMultibase: 'zQ3shQo2ZK9ZwNRxkEM1sSkpJKfx1NN6WWcvtMTDyJeCwPB7o',
31+
},
32+
],
33+
service: [
34+
{
35+
id: '#atproto_pds',
36+
type: 'AtprotoPersonalDataServer',
37+
serviceEndpoint: 'https://pds.bsky.mom',
38+
},
39+
{
40+
id: '#atproto_labeler',
41+
type: 'AtprotoLabeler',
42+
serviceEndpoint: 'https://api.pronouns.diy',
43+
},
44+
],
45+
});
46+
47+
describe('getAtprotoServiceEndpoint', () => {
48+
it('grabs a PDS', () => {
49+
expect(getPdsEndpoint(PRONOUNS_LABELER_DID_DOC)).toBe('https://pds.bsky.mom');
50+
});
51+
52+
it('grabs a labeler', () => {
53+
expect(getLabelerEndpoint(PRONOUNS_LABELER_DID_DOC)).toBe('https://api.pronouns.diy');
54+
});
55+
});
56+
57+
describe('getVerificationMaterial', () => {
58+
it('grabs PDS signing keys', () => {
59+
expect(getAtprotoVerificationMaterial(PRONOUNS_LABELER_DID_DOC)).toEqual({
60+
type: 'Multikey',
61+
publicKeyMultibase: 'zQ3sho8kubdqeS5wbxPDpNBBqg2tvJTKF1jovJKzQzhu4S8fH',
62+
});
63+
});
64+
65+
it('grabs labeler signing keys', () => {
66+
expect(getAtprotoLabelerVerificationMaterial(PRONOUNS_LABELER_DID_DOC)).toEqual({
67+
type: 'Multikey',
68+
publicKeyMultibase: 'zQ3shQo2ZK9ZwNRxkEM1sSkpJKfx1NN6WWcvtMTDyJeCwPB7o',
69+
});
70+
});
71+
});

packages/identity/identity/lib/utils.ts

+4-7
Original file line numberDiff line numberDiff line change
@@ -71,23 +71,20 @@ export const getAtprotoServiceEndpoint = (
7171
return;
7272
}
7373

74-
const expectedId = doc.id + predicate.id;
75-
const expectedType = predicate.type;
76-
7774
for (let idx = 0, len = services.length; idx < len; idx++) {
7875
const { id, type, serviceEndpoint } = services[idx];
7976

80-
if (id !== expectedId) {
77+
if (id !== predicate.id && id !== doc.id + predicate.id) {
8178
continue;
8279
}
8380

84-
if (expectedType !== undefined) {
81+
if (predicate.type !== undefined) {
8582
if (Array.isArray(type)) {
86-
if (!type.includes(expectedType)) {
83+
if (!type.includes(predicate.type)) {
8784
continue;
8885
}
8986
} else {
90-
if (type !== expectedType) {
87+
if (type !== predicate.type) {
9188
continue;
9289
}
9390
}

0 commit comments

Comments
 (0)