Skip to content

Commit

Permalink
feat(endpoint-auth): accept client metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
paulrobertlloyd committed Aug 25, 2024
1 parent 7a36846 commit cab4fb1
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 23 deletions.
11 changes: 11 additions & 0 deletions helpers/mock-agent/endpoint-auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,17 @@ export const mockClient = () => {

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

// Client metadata
agent
.get(origin)
.intercept({ path: "/id" })
.reply(200, {
client_id: `${origin}/id`,
client_name: "Client with metadata",
client_uri: origin,
logo_uri: `${origin}/logo.png`,
});

// Client information (h-x-app)
agent
.get(origin)
Expand Down
89 changes: 66 additions & 23 deletions packages/endpoint-auth/lib/client.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,27 @@
import { mf2 } from "microformats-parser";

/**
* Get client information
* @param {string} client_id - Client URL
* @returns {Promise<object>} Information about the client
* @see {@link https://indieauth.spec.indieweb.org/#client-information-discovery}
* Get client information from application Microformat
* @param {string} body - Response body
* @param {object} client - Fallback client information
* @returns {object} Client information
* @deprecated since 11 July 2024
* @see {@link https://indieauth.spec.indieweb.org/20220212/#application-information}
*/
export const getClientInformation = async (client_id) => {
let client = {
name: new URL(client_id).host,
url: client_id,
};

const clientResponse = await fetch(client_id);
if (!clientResponse.ok) {
return client;
}

const body = await clientResponse.text();

// If response contains microformats, use available derived values
const { items } = mf2(body, { baseUrl: client_id });
export const getApplicationInformation = (body, client) => {
const { items } = mf2(body, { baseUrl: client.url });
for (const item of items) {
const { properties, type } = item;

if (/^h-(?:x-)?app$/.test(type[0])) {
// If no URL property, use `client_id`
// If no URL property, use baseUrl
if (!properties.url) {
properties.url = [client_id];
properties.url = [client.url];
}

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

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

return client;
};

/**
* Get client information from client metadata
* @param {string} body - Response body
* @param {object} client - Fallback client information
* @returns {object} Client information
* @see {@link https://indieauth.spec.indieweb.org/#client-metadata}
*/
export const getClientMetadata = (body, client) => {
const json = JSON.parse(body);

// Client metadata MUST include `client_id`
if (!Object.hasOwn(json, "client_id")) {
throw new Error("Client metadata JSON not valid");
}

return {
...client,
logo: json.logo_uri,
name: json.client_name || client.name,
url: json.client_uri || client.url,
};
};

/**
* Get client information
* @param {string} clientId - Client ID
* @returns {Promise<object>} Information about the client
* @see {@link https://indieauth.spec.indieweb.org/#client-information-discovery}
*/
export const getClientInformation = async (clientId) => {
let client = {
id: clientId,
name: new URL(clientId).host,
url: new URL(clientId).href,
};

const clientResponse = await fetch(clientId);
if (!clientResponse.ok) {
// Use information derived from clientId
return client;
}

const body = await clientResponse.text();

try {
// Use information from client JSON metadata
return getClientMetadata(body, client);
} catch {
// Use information from client HTML microformats (deprecated)
return getApplicationInformation(body, client);
}
};
12 changes: 12 additions & 0 deletions packages/endpoint-auth/test/unit/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ import { getClientInformation } from "../../lib/client.js";
await mockAgent("endpoint-auth");

describe("endpoint-auth/lib/client", () => {
it("Gets client information (from metadata)", async () => {
const result = await getClientInformation(
"https://auth-endpoint.example/id",
);

assert.deepEqual(result, {
logo: "https://auth-endpoint.example/logo.png",
name: "Client with metadata",
url: "https://auth-endpoint.example",
});
});

it("Gets client information (has h-x-app microformat)", async () => {
const result = await getClientInformation("https://auth-endpoint.example/");

Expand Down

0 comments on commit cab4fb1

Please sign in to comment.