// @ts-ignore import { mf2 } from "https://esm.sh/microformats-parser@1.4.1?pin=v96" import { MF2 } from "./micropub_api.js" import base64 from "./base64.js" /* const { mf2 }: { mf2: (html: string, options: { baseUrl: string, experimental?: { lang?: boolean, textContent?: boolean } }) => { items: MF2[], rels: {[key: string]: string[]}, "rel-urls": {[key: string]: { rels: string[], text?: string }} } } = // @ts-ignore await import("https://esm.sh/microformats-parser@1.4.1?pin=v96"); */ interface IndieauthMetadata { authorization_endpoint: string, token_endpoint: string, issuer: string, introspection_endpoint?: string, introspection_endpoint_auth_methods_supported?: ("Bearer")[], revocation_endpoint?: string, revocation_endpoint_auth_methods_supported?: ["none"], scopes_supported?: string[], response_types_supported: ["code"], grant_types_supported: ("authorization_code" | "refresh_token")[] code_challenge_methods_supported: ("S256")[] authorization_response_iss_parameter_supported: true, userinfo_endpoint?: string } interface MF2ParsedData { items: MF2[], rels: {[key: string]: string[]}, "rel-urls": {[key: string]: { rels: string[], text?: string }} } export interface IndiewebEndpoints { authorization_endpoint: URL, token_endpoint: URL, userinfo_endpoint: URL | null, revocation_endpoint: URL | null, micropub: URL, } export function create_verifier() { const array = new Uint8Array(64) crypto.getRandomValues(array) return array.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '') } export async function create_challenge(verifier: string): Promise { return await crypto.subtle.digest('SHA-256', Uint8Array.from(verifier, c => c.charCodeAt(0))) .then((buf) => base64.encode(new Uint8Array(buf))) .then(s => { return s .replaceAll("+", "-") .replaceAll("/", "_") .replaceAll(/=$/g, "") }) } export async function discover_endpoints(me: URL): Promise { const response = await fetch(me); const data: MF2ParsedData = mf2(await response.text(), { baseUrl: me.toString() }); let endpoints: Partial = {}; if ("micropub" in data.rels) { endpoints.micropub = new URL(data.rels.micropub[0]) } else { return null } if ("indieauth_metadata" in data.rels) { const metadata_response = await fetch(data.rels.indieauth_metadata[0], { headers: { "Accept": "application/json" } }); const metadata = await metadata_response.json() as IndieauthMetadata; endpoints.authorization_endpoint = new URL(metadata.authorization_endpoint) endpoints.token_endpoint = new URL(metadata.token_endpoint) if (metadata.userinfo_endpoint != null) { endpoints.userinfo_endpoint = new URL(metadata.userinfo_endpoint) } else { endpoints.userinfo_endpoint = null } if (metadata.revocation_endpoint != null) { endpoints.revocation_endpoint = new URL(metadata.revocation_endpoint) } else { endpoints.revocation_endpoint = null } return endpoints as IndiewebEndpoints } else if ( "authorization_endpoint" in data.rels && "token_endpoint" in data.rels ) { endpoints.authorization_endpoint = new URL(data.rels.authorization_endpoint[0]) endpoints.token_endpoint = new URL(data.rels.token_endpoint[0]) endpoints.userinfo_endpoint = null endpoints.revocation_endpoint = null return endpoints as IndiewebEndpoints } else { return null } }