Skip to content

Commit 212684d

Browse files
feat(endpoint-auth): accept client metadata
1 parent 7a36846 commit 212684d

File tree

3 files changed

+95
-23
lines changed

3 files changed

+95
-23
lines changed

helpers/mock-agent/endpoint-auth.js

+11
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ export const mockClient = () => {
1111

1212
const origin = "https://auth-endpoint.example";
1313

14+
// Client metadata
15+
agent
16+
.get(origin)
17+
.intercept({ path: "/id" })
18+
.reply(200, {
19+
client_id: `${origin}/id`,
20+
client_name: "Client with metadata",
21+
client_uri: origin,
22+
logo_uri: `${origin}/logo.png`,
23+
});
24+
1425
// Client information (h-x-app)
1526
agent
1627
.get(origin)

packages/endpoint-auth/lib/client.js

+66-23
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,27 @@
11
import { mf2 } from "microformats-parser";
22

33
/**
4-
* Get client information
5-
* @param {string} client_id - Client URL
6-
* @returns {Promise<object>} Information about the client
7-
* @see {@link https://indieauth.spec.indieweb.org/#client-information-discovery}
4+
* Get client information from application Microformat
5+
* @param {string} body - Response body
6+
* @param {object} client - Fallback client information
7+
* @returns {object} Client information
8+
* @deprecated since 11 July 2024
9+
* @see {@link https://indieauth.spec.indieweb.org/20220212/#application-information}
810
*/
9-
export const getClientInformation = async (client_id) => {
10-
let client = {
11-
name: new URL(client_id).host,
12-
url: client_id,
13-
};
14-
15-
const clientResponse = await fetch(client_id);
16-
if (!clientResponse.ok) {
17-
return client;
18-
}
19-
20-
const body = await clientResponse.text();
21-
22-
// If response contains microformats, use available derived values
23-
const { items } = mf2(body, { baseUrl: client_id });
11+
export const getApplicationInformation = (body, client) => {
12+
const { items } = mf2(body, { baseUrl: client.url });
2413
for (const item of items) {
2514
const { properties, type } = item;
2615

2716
if (/^h-(?:x-)?app$/.test(type[0])) {
28-
// If no URL property, use `client_id`
17+
// If no URL property, use baseUrl
2918
if (!properties.url) {
30-
properties.url = [client_id];
19+
properties.url = [client.url];
3120
}
3221

33-
// If has URL property, only continue if matches `client_id`
34-
if (!properties.url?.includes(client_id)) {
22+
// Check that URL property matches `client_id`. Note that this isn’t for
23+
// authentication, but to ensure only relevant client metadata is returned
24+
if (!properties.url?.includes(client.url)) {
3525
continue;
3626
}
3727

@@ -48,3 +38,56 @@ export const getClientInformation = async (client_id) => {
4838

4939
return client;
5040
};
41+
42+
/**
43+
* Get client information from client metadata
44+
* @param {string} body - Response body
45+
* @param {object} client - Fallback client information
46+
* @returns {object} Client information
47+
* @see {@link https://indieauth.spec.indieweb.org/#client-metadata}
48+
*/
49+
export const getClientMetadata = (body, client) => {
50+
const json = JSON.parse(body);
51+
52+
// Client metadata MUST include `client_id`
53+
if (!Object.hasOwn(json, "client_id")) {
54+
throw new Error("Client metadata JSON not valid");
55+
}
56+
57+
return {
58+
...client,
59+
logo: json.logo_uri,
60+
name: json.client_name || client.name,
61+
url: json.client_uri || client.url,
62+
};
63+
};
64+
65+
/**
66+
* Get client information
67+
* @param {string} clientId - Client ID
68+
* @returns {Promise<object>} Information about the client
69+
* @see {@link https://indieauth.spec.indieweb.org/#client-information-discovery}
70+
*/
71+
export const getClientInformation = async (clientId) => {
72+
let client = {
73+
id: clientId,
74+
name: new URL(clientId).host,
75+
url: new URL(clientId).href,
76+
};
77+
78+
const clientResponse = await fetch(clientId);
79+
if (!clientResponse.ok) {
80+
// Use information derived from clientId
81+
return client;
82+
}
83+
84+
const body = await clientResponse.text();
85+
86+
try {
87+
// Use information from client JSON metadata
88+
return getClientMetadata(body, client);
89+
} catch {
90+
// Use information from client HTML microformats (deprecated)
91+
return getApplicationInformation(body, client);
92+
}
93+
};

packages/endpoint-auth/test/unit/client.js

+18
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,24 @@ import { getClientInformation } from "../../lib/client.js";
66
await mockAgent("endpoint-auth");
77

88
describe("endpoint-auth/lib/client", () => {
9+
it("Gets client information (from metadata)", async () => {
10+
const result = await getClientInformation(
11+
"https://auth-endpoint.example/id",
12+
);
13+
14+
assert.deepEqual(result, {
15+
id: "https://auth-endpoint.example/id",
16+
logo: "https://auth-endpoint.example/logo.png",
17+
name: "Client with metadata",
18+
url: "https://auth-endpoint.example",
19+
});
20+
});
21+
922
it("Gets client information (has h-x-app microformat)", async () => {
1023
const result = await getClientInformation("https://auth-endpoint.example/");
1124

1225
assert.deepEqual(result, {
26+
id: "https://auth-endpoint.example/",
1327
logo: "https://auth-endpoint.example/assets/icon.svg",
1428
name: "Example client",
1529
url: "https://auth-endpoint.example/",
@@ -20,6 +34,7 @@ describe("endpoint-auth/lib/client", () => {
2034
const result = await getClientInformation("https://simple-client.example/");
2135

2236
assert.deepEqual(result, {
37+
id: "https://simple-client.example/",
2338
name: "Simple client example",
2439
url: "https://simple-client.example/",
2540
});
@@ -31,6 +46,7 @@ describe("endpoint-auth/lib/client", () => {
3146
);
3247

3348
assert.deepEqual(result, {
49+
id: "https://auth-endpoint.example/mf2",
3450
name: "auth-endpoint.example",
3551
url: "https://auth-endpoint.example/mf2",
3652
});
@@ -42,6 +58,7 @@ describe("endpoint-auth/lib/client", () => {
4258
);
4359

4460
assert.deepEqual(result, {
61+
id: "https://auth-endpoint.example/no-mf2",
4562
name: "auth-endpoint.example",
4663
url: "https://auth-endpoint.example/no-mf2",
4764
});
@@ -53,6 +70,7 @@ describe("endpoint-auth/lib/client", () => {
5370
);
5471

5572
assert.deepEqual(result, {
73+
id: "https://auth-endpoint.example/404",
5674
name: "auth-endpoint.example",
5775
url: "https://auth-endpoint.example/404",
5876
});

0 commit comments

Comments
 (0)